shaka-player 3.2.0 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/.github/workflows/build_and_test.yaml +73 -0
  2. package/.github/workflows/release.yaml +53 -0
  3. package/AUTHORS +3 -0
  4. package/CHANGELOG.md +182 -1
  5. package/CONTRIBUTORS +3 -0
  6. package/README.md +4 -4
  7. package/build/checkversion.py +1 -1
  8. package/build/generateExterns.js +1 -1
  9. package/build/misspellings.txt +1 -0
  10. package/build/shakaBuildHelpers.py +6 -5
  11. package/build/subprocessWindowsPatch.py +103 -0
  12. package/build/test.py +6 -0
  13. package/demo/common/assets.js +1 -1
  14. package/demo/index.html +1 -1
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.js +1 -1
  19. package/dist/deps.js +8 -8
  20. package/dist/locales.js +37 -37
  21. package/dist/receiver.compiled.debug.js +1 -1
  22. package/dist/receiver.compiled.js +1 -1
  23. package/dist/shaka-player.compiled.d.ts +195 -5
  24. package/dist/shaka-player.compiled.debug.d.ts +195 -5
  25. package/dist/shaka-player.compiled.debug.externs.js +226 -14
  26. package/dist/shaka-player.compiled.debug.js +424 -399
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +226 -14
  29. package/dist/shaka-player.compiled.js +826 -812
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +197 -16
  32. package/dist/shaka-player.ui.debug.d.ts +197 -16
  33. package/dist/shaka-player.ui.debug.externs.js +230 -27
  34. package/dist/shaka-player.ui.debug.js +466 -442
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +230 -27
  37. package/dist/shaka-player.ui.js +946 -932
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/fairplay.md +1 -1
  40. package/docs/tutorials/faq.md +14 -0
  41. package/docs/tutorials/ui.md +5 -5
  42. package/externs/ima.js +4 -0
  43. package/externs/shaka/ads.js +25 -0
  44. package/externs/shaka/player.js +8 -0
  45. package/externs/shaka/text.js +10 -0
  46. package/karma.conf.js +20 -15
  47. package/lib/ads/ad_manager.js +46 -26
  48. package/lib/ads/client_side_ad_manager.js +82 -88
  49. package/lib/ads/server_side_ad_manager.js +45 -13
  50. package/lib/cast/cast_proxy.js +6 -2
  51. package/lib/cast/cast_receiver.js +5 -1
  52. package/lib/cast/cast_sender.js +2 -2
  53. package/lib/cea/sei_processor.js +24 -17
  54. package/lib/dash/dash_parser.js +131 -24
  55. package/lib/dash/segment_list.js +1 -1
  56. package/lib/dash/segment_template.js +1 -1
  57. package/lib/hls/hls_parser.js +74 -17
  58. package/lib/hls/manifest_text_parser.js +1 -0
  59. package/lib/media/closed_caption_parser.js +3 -1
  60. package/lib/media/content_workarounds.js +12 -0
  61. package/lib/media/drm_engine.js +20 -1
  62. package/lib/media/gap_jumping_controller.js +8 -4
  63. package/lib/media/media_source_engine.js +1 -2
  64. package/lib/media/playhead.js +1 -1
  65. package/lib/media/region_observer.js +39 -33
  66. package/lib/media/region_timeline.js +17 -19
  67. package/lib/media/segment_index.js +2 -1
  68. package/lib/media/segment_reference.js +19 -1
  69. package/lib/media/streaming_engine.js +8 -4
  70. package/lib/net/networking_engine.js +6 -1
  71. package/lib/offline/download_manager.js +1 -2
  72. package/lib/offline/storage.js +8 -9
  73. package/lib/player.js +129 -59
  74. package/lib/polyfill/aria.js +12 -2
  75. package/lib/polyfill/encryption_scheme.js +2 -0
  76. package/lib/polyfill/fullscreen.js +2 -0
  77. package/lib/polyfill/mathround.js +2 -0
  78. package/lib/polyfill/media_capabilities.js +73 -21
  79. package/lib/polyfill/mediasource.js +8 -0
  80. package/lib/polyfill/orientation.js +3 -1
  81. package/lib/polyfill/patchedmediakeys_apple.js +6 -4
  82. package/lib/polyfill/patchedmediakeys_ms.js +6 -4
  83. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  84. package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
  85. package/lib/polyfill/pip_webkit.js +2 -0
  86. package/lib/polyfill/storage_estimate.js +2 -0
  87. package/lib/polyfill/video_play_promise.js +2 -0
  88. package/lib/polyfill/videoplaybackquality.js +2 -0
  89. package/lib/polyfill/vttcue.js +2 -0
  90. package/lib/text/cue.js +6 -0
  91. package/lib/text/mp4_ttml_parser.js +3 -0
  92. package/lib/text/text_engine.js +14 -2
  93. package/lib/text/ttml_text_parser.js +135 -87
  94. package/lib/text/ui_text_displayer.js +213 -80
  95. package/lib/text/vtt_text_parser.js +4 -2
  96. package/lib/text/web_vtt_generator.js +21 -12
  97. package/lib/util/fake_event.js +53 -7
  98. package/lib/util/fake_event_target.js +21 -1
  99. package/lib/util/mp4_box_parsers.js +1 -1
  100. package/lib/util/periods.js +4 -1
  101. package/lib/util/platform.js +13 -1
  102. package/lib/util/stream_utils.js +105 -13
  103. package/lib/util/xml_utils.js +50 -12
  104. package/package.json +3 -6
  105. package/roadmap.md +24 -22
  106. package/test/cast/cast_proxy_unit.js +12 -6
  107. package/test/cast/cast_receiver_integration.js +1 -1
  108. package/test/cast/cast_utils_unit.js +1 -1
  109. package/test/cea/mp4_cea_parser_unit.js +26 -0
  110. package/test/dash/dash_parser_live_unit.js +43 -0
  111. package/test/dash/dash_parser_manifest_unit.js +222 -3
  112. package/test/hls/hls_parser_unit.js +79 -1
  113. package/test/media/closed_caption_parser_unit.js +20 -0
  114. package/test/media/drm_engine_integration.js +86 -2
  115. package/test/media/drm_engine_unit.js +57 -0
  116. package/test/media/playhead_unit.js +27 -1
  117. package/test/media/region_observer_unit.js +40 -5
  118. package/test/media/region_timeline_unit.js +40 -22
  119. package/test/media/streaming_engine_integration.js +2 -1
  120. package/test/media/streaming_engine_unit.js +145 -0
  121. package/test/player_integration.js +79 -8
  122. package/test/player_src_equals_integration.js +41 -8
  123. package/test/player_unit.js +14 -4
  124. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  125. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  126. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  127. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  128. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  129. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  130. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  131. package/test/test/assets/3675/dash_0.mpd +30 -0
  132. package/test/test/assets/3675/dash_1.mpd +30 -0
  133. package/test/test/assets/3675/dash_2.mpd +30 -0
  134. package/test/test/assets/3675/dash_3.mpd +30 -0
  135. package/test/test/assets/3675/dash_4.mpd +30 -0
  136. package/test/test/assets/3675/dash_5.mpd +26 -0
  137. package/test/test/assets/3675/init-stream0.m4s +0 -0
  138. package/test/test/assets/chapters.vtt +2 -2
  139. package/test/test/assets/chapters2.vtt +10 -0
  140. package/test/test/assets/empty_caption_video_init.mp4 +0 -0
  141. package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
  142. package/test/test/util/fake_ad_manager.js +11 -1
  143. package/test/test/util/ttml_utils.js +81 -0
  144. package/test/test/util/util.js +10 -7
  145. package/test/text/mp4_ttml_parser_unit.js +11 -18
  146. package/test/text/ttml_text_parser_unit.js +542 -220
  147. package/test/text/ui_text_displayer_unit.js +93 -20
  148. package/test/text/vtt_text_parser_unit.js +17 -1
  149. package/test/text/web_vtt_generator_unit.js +58 -3
  150. package/test/ui/localization_unit.js +15 -14
  151. package/test/ui/text_displayer_layout_unit.js +21 -8
  152. package/test/util/mp4_box_parsers_unit.js +28 -0
  153. package/test/util/stream_utils_unit.js +60 -3
  154. package/ui/cast_button.js +2 -3
  155. package/ui/controls.js +11 -6
  156. package/ui/less/buttons.less +0 -4
  157. package/ui/less/containers.less +6 -7
  158. package/ui/locales/ru.json +6 -6
  159. package/ui/localization.js +40 -87
  160. package/ui/pip_button.js +2 -3
  161. package/ui/seek_bar.js +1 -2
  162. package/ui/text_selection.js +1 -0
package/lib/player.js CHANGED
@@ -598,7 +598,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
598
598
  enterNode: (node, has, wants) => {
599
599
  this.dispatchEvent(this.makeEvent_(
600
600
  /* name= */ shaka.Player.EventName.OnStateChange,
601
- /* data= */ {'state': node.name}));
601
+ /* data= */ (new Map()).set('state', node.name)));
602
602
 
603
603
  const action = actions.get(node);
604
604
  return action(has, wants);
@@ -629,7 +629,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
629
629
  onIdle: (node) => {
630
630
  this.dispatchEvent(this.makeEvent_(
631
631
  /* name= */ shaka.Player.EventName.OnStateIdle,
632
- /* data= */ {'state': node.name}));
632
+ /* data= */ (new Map()).set('state', node.name)));
633
633
  },
634
634
  };
635
635
 
@@ -649,7 +649,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
649
649
 
650
650
  /**
651
651
  * @param {!shaka.Player.EventName} name
652
- * @param {Object=} data
652
+ * @param {Map.<string, Object>=} data
653
653
  * @return {!shaka.util.FakeEvent}
654
654
  * @private
655
655
  */
@@ -728,6 +728,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
728
728
  await this.networkingEngine_.destroy();
729
729
  this.networkingEngine_ = null;
730
730
  }
731
+
732
+ // FakeEventTarget implements IReleasable
733
+ super.release();
731
734
  }
732
735
 
733
736
  /**
@@ -1270,6 +1273,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1270
1273
  has.mediaElement = null;
1271
1274
  }
1272
1275
 
1276
+ if (this.adManager_) {
1277
+ // The ad manager is specific to the video, so detach it too.
1278
+ this.adManager_.release();
1279
+ }
1280
+
1273
1281
  // Clear our cached copy of the media element.
1274
1282
  this.video_ = null;
1275
1283
 
@@ -1606,18 +1614,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1606
1614
  // initialize it now even through it appears a little out-of-place.
1607
1615
  this.regionTimeline_ =
1608
1616
  new shaka.media.RegionTimeline(() => this.seekRange());
1609
- this.regionTimeline_.setListeners(/* onRegionAdded= */ (region) => {
1617
+ this.regionTimeline_.addEventListener('regionadd', (event) => {
1618
+ /** @type {shaka.extern.TimelineRegionInfo} */
1619
+ const region = event['region'];
1610
1620
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionAdded, region);
1621
+
1611
1622
  if (this.adManager_) {
1612
1623
  this.adManager_.onDashTimedMetadata(region);
1613
1624
  }
1614
1625
  });
1615
1626
 
1616
- // TODO (#1391): Once filterManifest_ is async, remove this eslint disable.
1617
- /* eslint-disable require-await */
1618
1627
  const playerInterface = {
1619
1628
  networkingEngine: networkingEngine,
1620
- filter: async (manifest) => this.filterManifest_(manifest),
1629
+ filter: (manifest) => this.filterManifest_(manifest),
1621
1630
  makeTextStreamsForClosedCaptions: (manifest) => {
1622
1631
  return this.makeTextStreamsForClosedCaptions_(manifest);
1623
1632
  },
@@ -1635,7 +1644,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1635
1644
  this.configure('streaming.lowLatencyMode', true);
1636
1645
  },
1637
1646
  };
1638
- /* eslint-enable require-await */
1639
1647
 
1640
1648
  const startTime = Date.now() / 1000;
1641
1649
 
@@ -2003,7 +2011,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2003
2011
 
2004
2012
  this.drmEngine_.configure(this.config_.drm);
2005
2013
 
2006
- const uri = has.uri || '';
2014
+ const uri = wants.uri || '';
2007
2015
  const extension = shaka.media.ManifestParser.getExtension(uri);
2008
2016
  let mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
2009
2017
  if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
@@ -2058,6 +2066,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2058
2066
  decodingInfos: [],
2059
2067
  };
2060
2068
 
2069
+ this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
2061
2070
  await this.drmEngine_.initForPlayback(
2062
2071
  [variant], /* offlineSessionIds= */ []);
2063
2072
  await this.drmEngine_.attach(has.mediaElement);
@@ -2414,12 +2423,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2414
2423
  goog.asserts.assert(!endTime || startTime <= endTime,
2415
2424
  'Metadata start time should be less or equal to the end time!');
2416
2425
  const eventName = shaka.Player.EventName.Metadata;
2417
- const data = {
2418
- startTime: startTime,
2419
- endTime: endTime,
2420
- metadataType: metadataType,
2421
- payload: payload,
2422
- };
2426
+ const data = new Map()
2427
+ .set('startTime', startTime)
2428
+ .set('endTime', endTime)
2429
+ .set('metadataType', metadataType)
2430
+ .set('payload', payload);
2423
2431
  this.dispatchEvent(this.makeEvent_(eventName, data));
2424
2432
  }
2425
2433
 
@@ -2545,21 +2553,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2545
2553
  // Create the region observer. This will allow us to notify the app when we
2546
2554
  // move in and out of timeline regions.
2547
2555
  const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2548
- const onEnterRegion = (region, seeking) => {
2556
+
2557
+ regionObserver.addEventListener('enter', (event) => {
2558
+ /** @type {shaka.extern.TimelineRegionInfo} */
2559
+ const region = event['region'];
2549
2560
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2550
- };
2551
- const onExitRegion = (region, seeking) => {
2561
+ });
2562
+
2563
+ regionObserver.addEventListener('exit', (event) => {
2564
+ /** @type {shaka.extern.TimelineRegionInfo} */
2565
+ const region = event['region'];
2552
2566
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2553
- };
2554
- const onSkipRegion = (region, seeking) => {
2567
+ });
2568
+
2569
+ regionObserver.addEventListener('skip', (event) => {
2570
+ /** @type {shaka.extern.TimelineRegionInfo} */
2571
+ const region = event['region'];
2572
+ /** @type {boolean} */
2573
+ const seeking = event['seeking'];
2555
2574
  // If we are seeking, we don't want to surface the enter/exit events since
2556
2575
  // they didn't play through them.
2557
2576
  if (!seeking) {
2558
2577
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2559
2578
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2560
2579
  }
2561
- };
2562
- regionObserver.setListeners(onEnterRegion, onExitRegion, onSkipRegion);
2580
+ });
2563
2581
 
2564
2582
  // Now that we have all our observers, create a manager for them.
2565
2583
  const manager = new shaka.media.PlayheadObserverManager(this.video_);
@@ -3318,6 +3336,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3318
3336
 
3319
3337
  const tracks = [];
3320
3338
 
3339
+ let activeTracks = 0;
3340
+
3321
3341
  // Convert each variant to a track.
3322
3342
  for (const variant of this.manifest_.variants) {
3323
3343
  if (!shaka.util.StreamUtils.isPlayable(variant)) {
@@ -3326,10 +3346,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3326
3346
 
3327
3347
  const track = shaka.util.StreamUtils.variantToTrack(variant);
3328
3348
  track.active = variant == currentVariant;
3349
+ if (!track.active && activeTracks != 1 && currentVariant != null &&
3350
+ variant.video == currentVariant.video &&
3351
+ variant.audio == currentVariant.audio) {
3352
+ track.active = true;
3353
+ }
3354
+
3355
+ if (track.active) {
3356
+ activeTracks++;
3357
+ }
3329
3358
 
3330
3359
  tracks.push(track);
3331
3360
  }
3332
3361
 
3362
+ goog.asserts.assert(activeTracks <= 1,
3363
+ 'It should only have one active track');
3364
+
3333
3365
  return tracks;
3334
3366
  } else if (this.video_ && this.video_.audioTracks) {
3335
3367
  // Safari's native HLS always shows a single element in videoTracks.
@@ -3438,7 +3470,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3438
3470
  const height = fullImageHeight / rows;
3439
3471
  const totalImages = columns * rows;
3440
3472
  const segmentDuration = reference.trueEndTime - reference.startTime;
3441
- const thumbnailDuration = segmentDuration / totalImages;
3473
+ const thumbnailDuration =
3474
+ reference.getTileDuration() || (segmentDuration / totalImages);
3442
3475
  let thumbnailTime = reference.startTime;
3443
3476
  let positionX = 0;
3444
3477
  let positionY = 0;
@@ -3461,6 +3494,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3461
3494
  positionY = Math.floor(thumbnailPosition / columns) * height;
3462
3495
  }
3463
3496
  return {
3497
+ imageHeight: fullImageHeight,
3498
+ imageWidth: fullImageWidth,
3464
3499
  height: height,
3465
3500
  positionX: positionX,
3466
3501
  positionY: positionY,
@@ -3862,20 +3897,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3862
3897
  const LanguageUtils = shaka.util.LanguageUtils;
3863
3898
  const inputlanguage = LanguageUtils.normalize(language);
3864
3899
  const chaptersTracks = this.getChaptersTracks_();
3865
- const chaptersTrack = chaptersTracks
3866
- .find((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3867
- if (!chaptersTrack || !chaptersTrack.cues) {
3900
+ const chaptersTracksWithLanguage = chaptersTracks
3901
+ .filter((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3902
+ if (!chaptersTracksWithLanguage || !chaptersTracksWithLanguage.length) {
3868
3903
  return [];
3869
3904
  }
3870
3905
  const chapters = [];
3871
- for (const cue of chaptersTrack.cues) {
3872
- /** @type {shaka.extern.Chapter} */
3873
- const chapter = {
3874
- title: cue.text,
3875
- startTime: cue.startTime,
3876
- endTime: cue.endTime,
3877
- };
3878
- chapters.push(chapter);
3906
+ for (const chaptersTrack of chaptersTracksWithLanguage) {
3907
+ if (chaptersTrack && chaptersTrack.cues) {
3908
+ for (const cue of chaptersTrack.cues) {
3909
+ /** @type {shaka.extern.Chapter} */
3910
+ const chapter = {
3911
+ title: cue.text,
3912
+ startTime: cue.startTime,
3913
+ endTime: cue.endTime,
3914
+ };
3915
+ chapters.push(chapter);
3916
+ }
3917
+ }
3879
3918
  }
3880
3919
  return chapters;
3881
3920
  }
@@ -4373,12 +4412,20 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4373
4412
  mimeType = await this.getTextMimetype_(uri);
4374
4413
  }
4375
4414
 
4415
+ let adCuePoints = [];
4416
+ if (this.adManager_) {
4417
+ try {
4418
+ adCuePoints = this.adManager_.getServerSideCuePoints();
4419
+ } catch (error) {}
4420
+ }
4421
+
4376
4422
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4377
4423
  if (forced) {
4378
4424
  // See: https://github.com/whatwg/html/issues/4472
4379
4425
  kind = 'forced';
4380
4426
  }
4381
- await this.addSrcTrackElement_(uri, language, kind, mimeType, label);
4427
+ await this.addSrcTrackElement_(uri, language, kind, mimeType, label || '',
4428
+ adCuePoints);
4382
4429
  const textTracks = this.getTextTracks();
4383
4430
  const srcTrack = textTracks.find((t) => {
4384
4431
  return t.language == language &&
@@ -4408,6 +4455,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4408
4455
  shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM);
4409
4456
  }
4410
4457
 
4458
+ if (adCuePoints.length) {
4459
+ goog.asserts.assert(
4460
+ this.networkingEngine_, 'Need networking engine.');
4461
+ const data = await this.getTextData_(uri,
4462
+ this.networkingEngine_,
4463
+ this.config_.streaming.retryParameters);
4464
+ const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints);
4465
+ const blob = new Blob([vvtText], {type: 'text/vtt'});
4466
+ uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4467
+ mimeType = 'text/vtt';
4468
+ }
4469
+
4411
4470
  /** @type {shaka.extern.Stream} */
4412
4471
  const stream = {
4413
4472
  id: this.nextExternalStreamId_++,
@@ -4480,8 +4539,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4480
4539
  if (!mimeType) {
4481
4540
  mimeType = await this.getTextMimetype_(uri);
4482
4541
  }
4542
+ let adCuePoints = [];
4543
+ if (this.adManager_) {
4544
+ try {
4545
+ adCuePoints = this.adManager_.getServerSideCuePoints();
4546
+ } catch (error) {}
4547
+ }
4483
4548
  await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4484
- mimeType);
4549
+ mimeType, /* label= */ '', adCuePoints);
4485
4550
  const chaptersTracks = this.getChaptersTracks();
4486
4551
  const chaptersTrack = chaptersTracks.find((t) => {
4487
4552
  return t.language == language;
@@ -4544,17 +4609,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4544
4609
  * @param {string} language
4545
4610
  * @param {string} kind
4546
4611
  * @param {string} mimeType
4547
- * @param {string=} label
4612
+ * @param {string} label
4613
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4548
4614
  * @private
4549
4615
  */
4550
- async addSrcTrackElement_(uri, language, kind, mimeType, label) {
4551
- if (mimeType != 'text/vtt') {
4616
+ async addSrcTrackElement_(uri, language, kind, mimeType, label,
4617
+ adCuePoints) {
4618
+ if (mimeType != 'text/vtt' || adCuePoints.length) {
4552
4619
  goog.asserts.assert(
4553
4620
  this.networkingEngine_, 'Need networking engine.');
4554
4621
  const data = await this.getTextData_(uri,
4555
4622
  this.networkingEngine_,
4556
4623
  this.config_.streaming.retryParameters);
4557
- const vvtText = this.convertToWebVTT_(data, mimeType);
4624
+ const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints);
4558
4625
  const blob = new Blob([vvtText], {type: 'text/vtt'});
4559
4626
  uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4560
4627
  mimeType = 'text/vtt';
@@ -4562,7 +4629,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4562
4629
  const trackElement =
4563
4630
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4564
4631
  trackElement.src = uri;
4565
- trackElement.label = label || '';
4632
+ trackElement.label = label;
4566
4633
  trackElement.kind = kind;
4567
4634
  trackElement.srclang = language;
4568
4635
  // Because we're pulling in the text track file via Javascript, the
@@ -4601,10 +4668,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4601
4668
  *
4602
4669
  * @param {BufferSource} buffer
4603
4670
  * @param {string} mimeType
4671
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4604
4672
  * @return {string}
4605
4673
  * @private
4606
4674
  */
4607
- convertToWebVTT_(buffer, mimeType) {
4675
+ convertToWebVTT_(buffer, mimeType, adCuePoints) {
4608
4676
  const factory = shaka.text.TextEngine.findParser(mimeType);
4609
4677
  if (factory) {
4610
4678
  const obj = factory();
@@ -4615,7 +4683,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4615
4683
  };
4616
4684
  const data = shaka.util.BufferUtils.toUint8(buffer);
4617
4685
  const cues = obj.parseMedia(data, time);
4618
- return shaka.text.WebVttGenerator.convert(cues);
4686
+ return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
4619
4687
  }
4620
4688
  throw new shaka.util.Error(
4621
4689
  shaka.util.Error.Severity.CRITICAL,
@@ -4992,7 +5060,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4992
5060
  // Surface the buffering event so that the app knows if/when we are
4993
5061
  // buffering.
4994
5062
  const eventName = shaka.Player.EventName.Buffering;
4995
- this.dispatchEvent(this.makeEvent_(eventName, {'buffering': isBuffering}));
5063
+ const data = (new Map()).set('buffering', isBuffering);
5064
+ this.dispatchEvent(this.makeEvent_(eventName, data));
4996
5065
  }
4997
5066
 
4998
5067
  /**
@@ -5354,10 +5423,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5354
5423
  onAdaptation_(from, to) {
5355
5424
  // Delay the 'adaptation' event so that StreamingEngine has time to absorb
5356
5425
  // the changes before the user tries to query it.
5357
- const event = this.makeEvent_(shaka.Player.EventName.Adaptation, {
5358
- oldTrack: from,
5359
- newTrack: to,
5360
- });
5426
+ const data = new Map()
5427
+ .set('oldTrack', from)
5428
+ .set('newTrack', to);
5429
+ const event = this.makeEvent_(shaka.Player.EventName.Adaptation, data);
5361
5430
  this.delayDispatchEvent_(event);
5362
5431
  }
5363
5432
 
@@ -5381,10 +5450,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5381
5450
  onVariantChanged_(from, to) {
5382
5451
  // Delay the 'variantchanged' event so StreamingEngine has time to absorb
5383
5452
  // the changes before the user tries to query it.
5384
- const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, {
5385
- oldTrack: from,
5386
- newTrack: to,
5387
- });
5453
+ const data = new Map()
5454
+ .set('oldTrack', from)
5455
+ .set('newTrack', to);
5456
+ const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, data);
5388
5457
  this.delayDispatchEvent_(event);
5389
5458
  }
5390
5459
 
@@ -5407,10 +5476,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5407
5476
 
5408
5477
  /** @private */
5409
5478
  onAbrStatusChanged_() {
5410
- const event = this.makeEvent_(shaka.Player.EventName.AbrStatusChanged, {
5411
- newStatus: this.config_.abr.enabled,
5412
- });
5413
- this.delayDispatchEvent_(event);
5479
+ const data = (new Map()).set('newStatus', this.config_.abr.enabled);
5480
+ this.delayDispatchEvent_(this.makeEvent_(
5481
+ shaka.Player.EventName.AbrStatusChanged, data));
5414
5482
  }
5415
5483
 
5416
5484
  /**
@@ -5427,7 +5495,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5427
5495
  }
5428
5496
 
5429
5497
  const eventName = shaka.Player.EventName.Error;
5430
- const event = this.makeEvent_(eventName, {'detail': error});
5498
+ const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
5431
5499
  this.dispatchEvent(event);
5432
5500
  if (event.defaultPrevented) {
5433
5501
  error.handled = true;
@@ -5456,7 +5524,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5456
5524
  eventElement: region.eventElement,
5457
5525
  };
5458
5526
 
5459
- this.dispatchEvent(this.makeEvent_(eventName, {detail: clone}));
5527
+ const data = (new Map()).set('detail', clone);
5528
+ this.dispatchEvent(this.makeEvent_(eventName, data));
5460
5529
  }
5461
5530
 
5462
5531
  /**
@@ -6285,7 +6354,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6285
6354
  * @define {string} A version number taken from git at compile time.
6286
6355
  * @export
6287
6356
  */
6288
- shaka.Player.version = 'v3.2.0-uncompiled';
6357
+ shaka.Player.version = 'v3.2.4-uncompiled'; // x-release-please-version
6289
6358
 
6290
6359
  // Initialize the deprecation system using the version string we just set
6291
6360
  // on the player.
@@ -6329,6 +6398,7 @@ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
6329
6398
  'mpg': 'video/mpeg',
6330
6399
  'mpeg': 'video/mpeg',
6331
6400
  'm3u8': 'application/x-mpegurl',
6401
+ 'mpd': 'application/dash+xml',
6332
6402
  'mp3': 'audio/mpeg',
6333
6403
  'aac': 'audio/aac',
6334
6404
  'flac': 'audio/flac',
@@ -16,9 +16,13 @@ goog.require('shaka.polyfill');
16
16
  * for that platform, as it relies on getters and setters.
17
17
  * @see https://w3c.github.io/aria/#ARIAMixin
18
18
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Element
19
+ * @export
19
20
  */
20
21
  shaka.polyfill.Aria = class {
21
- /** Install the polyfill if needed. */
22
+ /**
23
+ * Install the polyfill if needed.
24
+ * @export
25
+ */
22
26
  static install() {
23
27
  // eslint-disable-next-line no-restricted-syntax
24
28
  if (Object.getOwnPropertyDescriptor(Element.prototype, 'ariaHidden')) {
@@ -48,7 +52,13 @@ shaka.polyfill.Aria = class {
48
52
  * @private
49
53
  */
50
54
  static addARIAMixinAttribute_(name) {
51
- const snakeCaseName = name.toLowerCase().replace('aria', 'aria-');
55
+ const baseName = name.toLowerCase().replace(/^aria/, '');
56
+ // NOTE: All the attributes listed in the method above begin with "aria".
57
+ // However, to add extra protection against the possibility of XSS attacks
58
+ // through this method, this enforces "aria-" at the beginning of the
59
+ // snake-case name, even if somehow "aria" were missing from the input.
60
+ const snakeCaseName = `aria-${baseName}`;
61
+
52
62
  /* eslint-disable no-restricted-syntax */
53
63
  Object.defineProperty(Element.prototype, name, {
54
64
  get() {
@@ -13,12 +13,14 @@ goog.require('shaka.polyfill');
13
13
  * @see https://wicg.github.io/encrypted-media-encryption-scheme/
14
14
  * @see https://github.com/w3c/encrypted-media/pull/457
15
15
  * @see https://github.com/google/eme-encryption-scheme-polyfill
16
+ * @export
16
17
  */
17
18
  shaka.polyfill.EncryptionScheme = class {
18
19
  /**
19
20
  * Install the polyfill if needed.
20
21
  *
21
22
  * @suppress {missingRequire}
23
+ * @export
22
24
  */
23
25
  static install() {
24
26
  EncryptionSchemePolyfills.install();
@@ -14,10 +14,12 @@ goog.require('shaka.polyfill');
14
14
  * Many browsers have prefixed fullscreen methods on Element and document.
15
15
  * See {@link https://mzl.la/2K0xcHo Using fullscreen mode} on MDN for more
16
16
  * information.
17
+ * @export
17
18
  */
18
19
  shaka.polyfill.Fullscreen = class {
19
20
  /**
20
21
  * Install the polyfill if needed.
22
+ * @export
21
23
  */
22
24
  static install() {
23
25
  if (!window.Document) {
@@ -11,10 +11,12 @@ goog.require('shaka.polyfill');
11
11
  /**
12
12
  * @summary A polyfill to patch math round bug on some browsers.
13
13
  * @see https://stackoverflow.com/q/12830742
14
+ * @export
14
15
  */
15
16
  shaka.polyfill.MathRound = class {
16
17
  /**
17
18
  * Install the polyfill if needed.
19
+ * @export
18
20
  */
19
21
  static install() {
20
22
  shaka.log.debug('mathRound.install');
@@ -15,31 +15,49 @@ goog.require('shaka.util.Platform');
15
15
  * @summary A polyfill to provide navigator.mediaCapabilities on all browsers.
16
16
  * This is necessary for Tizen 3, Xbox One and possibly others we have yet to
17
17
  * discover.
18
+ * @export
18
19
  */
19
20
  shaka.polyfill.MediaCapabilities = class {
20
21
  /**
21
22
  * Install the polyfill if needed.
23
+ * @suppress {const}
24
+ * @export
22
25
  */
23
26
  static install() {
24
- shaka.log.debug('MediaCapabilities: install');
25
-
26
27
  // Since MediaCapabilities is not fully supported on Chromecast yet, we
27
28
  // should always install polyfill for Chromecast.
28
29
  // TODO: re-evaluate MediaCapabilities in the future versions of Chromecast.
29
- if (!shaka.util.Platform.isChromecast() && navigator.mediaCapabilities) {
30
- shaka.log.debug(
30
+ // Since MediaCapabilities implementation is buggy in Apple browsers, we
31
+ // should always install polyfill for Apple browsers.
32
+ // See: https://github.com/google/shaka-player/issues/3530
33
+ // TODO: re-evaluate MediaCapabilities in the future versions of Apple
34
+ // Browsers.
35
+ // Since MediaCapabilities implementation is buggy in PS5 browsers, we
36
+ // should always install polyfill for PS5 browsers.
37
+ // See: https://github.com/google/shaka-player/issues/3582
38
+ // TODO: re-evaluate MediaCapabilities in the future versions of PS5
39
+ // Browsers.
40
+ if (!shaka.util.Platform.isChromecast() &&
41
+ !shaka.util.Platform.isApple() &&
42
+ !shaka.util.Platform.isPS5() &&
43
+ navigator.mediaCapabilities) {
44
+ shaka.log.info(
31
45
  'MediaCapabilities: Native mediaCapabilities support found.');
32
46
  return;
33
- } else if (!window.MediaSource) {
34
- shaka.log.debug(
35
- 'MediaSource must be available to install mediaCapabilities ',
36
- 'polyfill.');
37
- return;
38
47
  }
39
48
 
49
+ shaka.log.info('MediaCapabilities: install');
50
+
40
51
  if (!navigator.mediaCapabilities) {
41
52
  navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
42
53
  }
54
+
55
+ // Keep the patched MediaCapabilities object from being garbage-collected in
56
+ // Safari.
57
+ // See https://github.com/google/shaka-player/issues/3696#issuecomment-1009472718
58
+ shaka.polyfill.MediaCapabilities.originalMcap =
59
+ navigator.mediaCapabilities;
60
+
43
61
  navigator.mediaCapabilities.decodingInfo =
44
62
  shaka.polyfill.MediaCapabilities.decodingInfo_;
45
63
  }
@@ -62,21 +80,45 @@ shaka.polyfill.MediaCapabilities = class {
62
80
  return res;
63
81
  }
64
82
 
65
- // Use 'MediaSource.isTypeSupported' to check if the stream is supported.
66
- if (mediaDecodingConfig['video']) {
67
- const contentType = mediaDecodingConfig['video'].contentType;
68
- const isSupported = MediaSource.isTypeSupported(contentType);
69
- if (!isSupported) {
83
+ if (mediaDecodingConfig.type == 'media-source') {
84
+ if (!shaka.util.Platform.supportsMediaSource()) {
70
85
  return res;
71
86
  }
72
- }
87
+ // Use 'MediaSource.isTypeSupported' to check if the stream is supported.
88
+ if (mediaDecodingConfig['video']) {
89
+ const contentType = mediaDecodingConfig['video'].contentType;
90
+ const isSupported = MediaSource.isTypeSupported(contentType);
91
+ if (!isSupported) {
92
+ return res;
93
+ }
94
+ }
73
95
 
74
- if (mediaDecodingConfig['audio']) {
75
- const contentType = mediaDecodingConfig['audio'].contentType;
76
- const isSupported = MediaSource.isTypeSupported(contentType);
77
- if (!isSupported) {
78
- return res;
96
+ if (mediaDecodingConfig['audio']) {
97
+ const contentType = mediaDecodingConfig['audio'].contentType;
98
+ const isSupported = MediaSource.isTypeSupported(contentType);
99
+ if (!isSupported) {
100
+ return res;
101
+ }
79
102
  }
103
+ } else if (mediaDecodingConfig.type == 'file') {
104
+ if (mediaDecodingConfig['video']) {
105
+ const contentType = mediaDecodingConfig['video'].contentType;
106
+ const isSupported = shaka.util.Platform.supportsMediaType(contentType);
107
+ if (!isSupported) {
108
+ return res;
109
+ }
110
+ }
111
+
112
+ if (mediaDecodingConfig['audio']) {
113
+ const contentType = mediaDecodingConfig['audio'].contentType;
114
+ const isSupported = shaka.util.Platform.supportsMediaType(contentType);
115
+ if (!isSupported) {
116
+ return res;
117
+ }
118
+ }
119
+ } else {
120
+ // Otherwise not supported.
121
+ return res;
80
122
  }
81
123
 
82
124
  if (!mediaDecodingConfig.keySystemConfiguration) {
@@ -118,7 +160,7 @@ shaka.polyfill.MediaCapabilities = class {
118
160
  sessionTypes: mediaCapkeySystemConfig.sessionTypes,
119
161
  };
120
162
 
121
- // Only add the audio video capablities if they have valid data.
163
+ // Only add the audio video capabilities if they have valid data.
122
164
  // Otherwise the query will fail.
123
165
  if (audioCapabilities.length) {
124
166
  mediaKeySystemConfig.audioCapabilities = audioCapabilities;
@@ -145,6 +187,16 @@ shaka.polyfill.MediaCapabilities = class {
145
187
  }
146
188
  };
147
189
 
190
+ /**
191
+ * A copy of the MediaCapabilities instance, to prevent Safari from
192
+ * garbage-collecting the polyfilled method on it. We make it public and export
193
+ * it to ensure that it is not stripped out by the compiler.
194
+ *
195
+ * @type {MediaCapabilities}
196
+ * @export
197
+ */
198
+ shaka.polyfill.MediaCapabilities.originalMcap = null;
199
+
148
200
  // Install at a lower priority than MediaSource polyfill, so that we have
149
201
  // MediaSource available first.
150
202
  shaka.polyfill.register(shaka.polyfill.MediaCapabilities.install, -1);
@@ -13,10 +13,12 @@ goog.require('shaka.util.Platform');
13
13
 
14
14
  /**
15
15
  * @summary A polyfill to patch MSE bugs.
16
+ * @export
16
17
  */
17
18
  shaka.polyfill.MediaSource = class {
18
19
  /**
19
20
  * Install the polyfill if needed.
21
+ * @export
20
22
  */
21
23
  static install() {
22
24
  shaka.log.debug('MediaSource.install');
@@ -206,6 +208,12 @@ shaka.polyfill.MediaSource = class {
206
208
  static patchVp09_() {
207
209
  const originalIsTypeSupported = MediaSource.isTypeSupported;
208
210
 
211
+ if (shaka.util.Platform.isWebOS()) {
212
+ // Don't do this on LG webOS as otherwise it is unable
213
+ // to play vp09 at all.
214
+ return;
215
+ }
216
+
209
217
  MediaSource.isTypeSupported = (mimeType) => {
210
218
  // Split the MIME type into its various parameters.
211
219
  const pieces = mimeType.split(/ *; */);