shaka-player 3.2.1 → 3.3.1

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 (213) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -47,6 +47,7 @@ shaka.offline.StoredContentUtils = class {
47
47
  expiration: Infinity,
48
48
  tracks: tracks,
49
49
  appMetadata: metadata,
50
+ isIncomplete: false,
50
51
  };
51
52
 
52
53
  return content;
@@ -87,6 +88,7 @@ shaka.offline.StoredContentUtils = class {
87
88
  expiration: manifestDB.expiration,
88
89
  tracks: tracks,
89
90
  appMetadata: metadata,
91
+ isIncomplete: (manifestDB.isIncomplete || false),
90
92
  };
91
93
 
92
94
  return content;
package/lib/player.js CHANGED
@@ -22,6 +22,7 @@ goog.require('shaka.media.PlayRateController');
22
22
  goog.require('shaka.media.Playhead');
23
23
  goog.require('shaka.media.PlayheadObserverManager');
24
24
  goog.require('shaka.media.PreferenceBasedCriteria');
25
+ goog.require('shaka.media.QualityObserver');
25
26
  goog.require('shaka.media.RegionObserver');
26
27
  goog.require('shaka.media.RegionTimeline');
27
28
  goog.require('shaka.media.SegmentIndex');
@@ -36,6 +37,7 @@ goog.require('shaka.text.UITextDisplayer');
36
37
  goog.require('shaka.text.WebVttGenerator');
37
38
  goog.require('shaka.util.AbortableOperation');
38
39
  goog.require('shaka.util.BufferUtils');
40
+ goog.require('shaka.util.CmcdManager');
39
41
  goog.require('shaka.util.ConfigUtils');
40
42
  goog.require('shaka.util.Error');
41
43
  goog.require('shaka.util.EventManager');
@@ -106,6 +108,32 @@ goog.requireType('shaka.routing.Payload');
106
108
  */
107
109
 
108
110
 
111
+ /**
112
+ * @event shaka.Player.DownloadFailed
113
+ * @description Fired when a download has failed, for any reason.
114
+ * 'downloadfailed'
115
+ * @property {!shaka.extern.Request} request
116
+ * @property {?shaka.util.Error} error
117
+ * @param {number} httpResponseCode
118
+ * @param {boolean} aborted
119
+ * @exportDoc
120
+ */
121
+
122
+
123
+ /**
124
+ * @event shaka.Player.DownloadHeadersReceived
125
+ * @description Fired when the networking engine has received the headers for
126
+ * a download, but before the body has been downloaded.
127
+ * If the HTTP plugin being used does not track this information, this event
128
+ * will default to being fired when the body is received, instead.
129
+ * @property {!Object.<string, string>} headers
130
+ * @property {!shaka.extern.Request} request
131
+ * @property {!shaka.net.NetworkingEngine.RequestType} type
132
+ * 'downloadheadersreceived'
133
+ * @exportDoc
134
+ */
135
+
136
+
109
137
  /**
110
138
  * @event shaka.Player.DrmSessionUpdateEvent
111
139
  * @description Fired when the CDM has accepted the license response.
@@ -147,6 +175,21 @@ goog.requireType('shaka.routing.Payload');
147
175
  * @exportDoc
148
176
  */
149
177
 
178
+ /**
179
+ * @event shaka.Player.MediaQualityChangedEvent
180
+ * @description Fired when the media quality changes at the playhead.
181
+ * That may be caused by an adaptation change or a DASH period transition.
182
+ * Separate events are emitted for audio and video contentTypes.
183
+ * This is supported for only DASH streams at this time.
184
+ * @property {string} type
185
+ * 'mediaqualitychanged'
186
+ * @property {shaka.extern.MediaQualityInfo} mediaQuality
187
+ * Information about media quality at the playhead position.
188
+ * @property {number} position
189
+ * The playhead position.
190
+ * @exportDoc
191
+ */
192
+
150
193
 
151
194
  /**
152
195
  * @event shaka.Player.BufferingEvent
@@ -441,6 +484,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
441
484
  /** @private {shaka.media.RegionTimeline} */
442
485
  this.regionTimeline_ = null;
443
486
 
487
+ /** @private {shaka.util.CmcdManager} */
488
+ this.cmcdManager_ = null;
489
+
490
+ /** @private {shaka.media.QualityObserver} */
491
+ this.qualityObserver_ = null;
492
+
444
493
  /** @private {shaka.media.StreamingEngine} */
445
494
  this.streamingEngine_ = null;
446
495
 
@@ -507,6 +556,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
507
556
  /** @private {!Array.<function():(!Promise|undefined)>} */
508
557
  this.cleanupOnUnload_ = [];
509
558
 
559
+ /**
560
+ * This playback start position will be used when
561
+ * <code>updateStartTime()</code> has been called to provide an updated
562
+ * start position during the media loading process.
563
+ *
564
+ * @private {?number}
565
+ */
566
+ this.updatedStartTime_ = null;
567
+
510
568
  if (dependencyInjector) {
511
569
  dependencyInjector(this);
512
570
  }
@@ -598,7 +656,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
598
656
  enterNode: (node, has, wants) => {
599
657
  this.dispatchEvent(this.makeEvent_(
600
658
  /* name= */ shaka.Player.EventName.OnStateChange,
601
- /* data= */ {'state': node.name}));
659
+ /* data= */ (new Map()).set('state', node.name)));
602
660
 
603
661
  const action = actions.get(node);
604
662
  return action(has, wants);
@@ -629,7 +687,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
629
687
  onIdle: (node) => {
630
688
  this.dispatchEvent(this.makeEvent_(
631
689
  /* name= */ shaka.Player.EventName.OnStateIdle,
632
- /* data= */ {'state': node.name}));
690
+ /* data= */ (new Map()).set('state', node.name)));
633
691
  },
634
692
  };
635
693
 
@@ -649,7 +707,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
649
707
 
650
708
  /**
651
709
  * @param {!shaka.Player.EventName} name
652
- * @param {Object=} data
710
+ * @param {Map.<string, Object>=} data
653
711
  * @return {!shaka.util.FakeEvent}
654
712
  * @private
655
713
  */
@@ -723,6 +781,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
723
781
  this.config_ = null;
724
782
  this.stats_ = null;
725
783
  this.videoContainer_ = null;
784
+ this.cmcdManager_ = null;
726
785
 
727
786
  if (this.networkingEngine_) {
728
787
  await this.networkingEngine_.destroy();
@@ -1020,6 +1079,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1020
1079
  return this.wrapWalkerListenersWithPromise_(events);
1021
1080
  }
1022
1081
 
1082
+ /**
1083
+ * Provides a way to update the stream start position during the media loading
1084
+ * process. Can for example be called from the <code>manifestparsed</code>
1085
+ * event handler to update the start position based on information in the
1086
+ * manifest.
1087
+ *
1088
+ * @param {number} startTime
1089
+ * @export
1090
+ */
1091
+ updateStartTime(startTime) {
1092
+ this.updatedStartTime_ = startTime;
1093
+ }
1094
+
1023
1095
  /**
1024
1096
  * Tell the player to load the content at <code>assetUri</code> and start
1025
1097
  * playback at <code>startTime</code>. Before calling <code>load</code>,
@@ -1040,6 +1112,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1040
1112
  * @export
1041
1113
  */
1042
1114
  load(assetUri, startTime, mimeType) {
1115
+ this.updatedStartTime_ = null;
1116
+
1043
1117
  // Do not allow the player to be used after |destroy| is called.
1044
1118
  if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
1045
1119
  return Promise.reject(this.createAbortLoadError_());
@@ -1093,6 +1167,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1093
1167
  // before we allow calls to |updateStateHistory|.
1094
1168
  this.stats_ = new shaka.util.Stats();
1095
1169
 
1170
+ // Create the CMCD manager so client data can be attached to all requests
1171
+ this.cmcdManager_ = this.createCmcd_();
1172
+
1096
1173
  // Load's request is a little different, so we can't use our normal
1097
1174
  // listeners-to-promise method. It is the only request where we may skip the
1098
1175
  // request, so we need to set the on skip callback to reject with a specific
@@ -1594,6 +1671,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1594
1671
  goog.asserts.assert(
1595
1672
  this.networkingEngine_,
1596
1673
  'Need networking engine to parse manifest.');
1674
+ goog.asserts.assert(
1675
+ this.cmcdManager_,
1676
+ 'Need CMCD manager to populate manifest request data.');
1597
1677
  goog.asserts.assert(
1598
1678
  this.config_,
1599
1679
  'Need player config to parse manifest.');
@@ -1617,9 +1697,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1617
1697
  this.adManager_.onDashTimedMetadata(region);
1618
1698
  }
1619
1699
  });
1700
+ this.qualityObserver_ = null;
1701
+ if (this.config_.streaming.observeQualityChanges) {
1702
+ this.qualityObserver_ = new shaka.media.QualityObserver(
1703
+ () => this.getBufferedInfo());
1704
+ this.qualityObserver_.setListeners((mediaQualityInfo, position) => {
1705
+ this.onMediaQualityChange_(mediaQualityInfo, position);
1706
+ });
1707
+ }
1620
1708
 
1621
1709
  const playerInterface = {
1622
1710
  networkingEngine: networkingEngine,
1711
+ modifyManifestRequest: (request, manifestInfo) => {
1712
+ this.cmcdManager_.applyManifestData(request, manifestInfo);
1713
+ },
1714
+ modifySegmentRequest: (request, segmentInfo) => {
1715
+ this.cmcdManager_.applySegmentData(request, segmentInfo);
1716
+ },
1623
1717
  filter: (manifest) => this.filterManifest_(manifest),
1624
1718
  makeTextStreamsForClosedCaptions: (manifest) => {
1625
1719
  return this.makeTextStreamsForClosedCaptions_(manifest);
@@ -1780,6 +1874,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1780
1874
  // something we are now depending on.
1781
1875
  has.startTime = wants.startTime;
1782
1876
 
1877
+ // If updateStartTime() has been called since load() was invoked use the
1878
+ // requested startTime
1879
+ if (this.updatedStartTime_ != null) {
1880
+ has.startTime = this.updatedStartTime_;
1881
+ this.updatedStartTime_ = null;
1882
+ }
1883
+
1783
1884
  // Store a reference to values in |has| after asserting so that closure will
1784
1885
  // know that they will still be non-null between calls to await.
1785
1886
  const mediaElement = has.mediaElement;
@@ -2177,9 +2278,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2177
2278
  this.video_.textTracks, 'change', () => this.onTracksChanged_());
2178
2279
  }
2179
2280
 
2281
+ const extension = shaka.media.ManifestParser.getExtension(has.uri);
2282
+ const mimeType =
2283
+ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
2284
+
2180
2285
  // By setting |src| we are done "loading" with src=. We don't need to set
2181
2286
  // the current time because |playhead| will do that for us.
2182
- has.mediaElement.src = has.uri;
2287
+ has.mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2183
2288
 
2184
2289
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2185
2290
  // no matter the value of the preload attribute. This is harmful on some
@@ -2417,12 +2522,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2417
2522
  goog.asserts.assert(!endTime || startTime <= endTime,
2418
2523
  'Metadata start time should be less or equal to the end time!');
2419
2524
  const eventName = shaka.Player.EventName.Metadata;
2420
- const data = {
2421
- startTime: startTime,
2422
- endTime: endTime,
2423
- metadataType: metadataType,
2424
- payload: payload,
2425
- };
2525
+ const data = new Map()
2526
+ .set('startTime', startTime)
2527
+ .set('endTime', endTime)
2528
+ .set('metadataType', metadataType)
2529
+ .set('payload', payload);
2426
2530
  this.dispatchEvent(this.makeEvent_(eventName, data));
2427
2531
  }
2428
2532
 
@@ -2510,8 +2614,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2510
2614
  this.abrManager_.segmentDownloaded(deltaTimeMs, bytesDownloaded);
2511
2615
  }
2512
2616
  };
2617
+ /** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
2618
+ const onHeadersReceived_ = (headers, request, requestType) => {
2619
+ // Release a 'downloadheadersreceived' event.
2620
+ const name = shaka.Player.EventName.DownloadHeadersReceived;
2621
+ const data = new Map()
2622
+ .set('headers', headers)
2623
+ .set('request', request)
2624
+ .set('requestType', requestType);
2625
+ this.dispatchEvent(this.makeEvent_(name, data));
2626
+ };
2627
+ /** @type {shaka.net.NetworkingEngine.OnDownloadFailed} */
2628
+ const onDownloadFailed_ = (request, error, httpResponseCode, aborted) => {
2629
+ // Release a 'downloadfailed' event.
2630
+ const name = shaka.Player.EventName.DownloadFailed;
2631
+ const data = new Map()
2632
+ .set('request', request)
2633
+ .set('error', error)
2634
+ .set('httpResponseCode', httpResponseCode)
2635
+ .set('aborted', aborted);
2636
+ this.dispatchEvent(this.makeEvent_(name, data));
2637
+ };
2513
2638
 
2514
- return new shaka.net.NetworkingEngine(onProgressUpdated_);
2639
+ return new shaka.net.NetworkingEngine(
2640
+ onProgressUpdated_, onHeadersReceived_, onDownloadFailed_);
2515
2641
  }
2516
2642
 
2517
2643
  /**
@@ -2567,7 +2693,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2567
2693
  // Now that we have all our observers, create a manager for them.
2568
2694
  const manager = new shaka.media.PlayheadObserverManager(this.video_);
2569
2695
  manager.manage(regionObserver);
2570
-
2696
+ if (this.qualityObserver_) {
2697
+ manager.manage(this.qualityObserver_);
2698
+ }
2571
2699
  return manager;
2572
2700
  }
2573
2701
 
@@ -2683,6 +2811,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2683
2811
  mediaElement, closedCaptionsParser, textDisplayer, onMetadata);
2684
2812
  }
2685
2813
 
2814
+ /**
2815
+ * Create a new CMCD manager.
2816
+ *
2817
+ * @private
2818
+ */
2819
+ createCmcd_() {
2820
+ /** @type {shaka.util.CmcdManager.PlayerInterface} */
2821
+ const playerInterface = {
2822
+ getBandwidthEstimate: () => this.abrManager_ ?
2823
+ this.abrManager_.getBandwidthEstimate() : NaN,
2824
+ getBufferedInfo: () => this.getBufferedInfo(),
2825
+ getCurrentTime: () => this.video_ ? this.video_.currentTime : 0,
2826
+ getVariantTracks: () => this.getVariantTracks(),
2827
+ getPlaybackRate: () => this.getPlaybackRate(),
2828
+ isLive: () => this.isLive(),
2829
+ };
2830
+
2831
+ return new shaka.util.CmcdManager(playerInterface, this.config_.cmcd);
2832
+ }
2833
+
2686
2834
  /**
2687
2835
  * Creates a new instance of StreamingEngine. This can be replaced by tests
2688
2836
  * to create fake instances instead.
@@ -2692,19 +2840,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2692
2840
  createStreamingEngine() {
2693
2841
  goog.asserts.assert(
2694
2842
  this.playhead_ && this.abrManager_ && this.mediaSourceEngine_ &&
2695
- this.manifest_,
2843
+ this.cmcdManager_ && this.manifest_,
2696
2844
  'Must not be destroyed');
2697
2845
 
2698
2846
  /** @type {shaka.media.StreamingEngine.PlayerInterface} */
2699
2847
  const playerInterface = {
2700
2848
  getPresentationTime: () => this.playhead_.getTime(),
2701
2849
  getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
2850
+ modifySegmentRequest: (request, segmentInfo) => {
2851
+ this.cmcdManager_.applySegmentData(request, segmentInfo);
2852
+ },
2702
2853
  mediaSourceEngine: this.mediaSourceEngine_,
2703
2854
  netEngine: this.networkingEngine_,
2704
2855
  onError: (error) => this.onError_(error),
2705
2856
  onEvent: (event) => this.dispatchEvent(event),
2706
2857
  onManifestUpdate: () => this.onManifestUpdate_(),
2707
2858
  onSegmentAppended: () => this.onSegmentAppended_(),
2859
+ onInitSegmentAppended: (position, initSegment) => {
2860
+ const mediaQuality = initSegment.getMediaQuality();
2861
+ if (mediaQuality && this.qualityObserver_) {
2862
+ this.qualityObserver_.addMediaQualityChange(mediaQuality, position);
2863
+ }
2864
+ },
2708
2865
  };
2709
2866
 
2710
2867
  return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
@@ -3455,7 +3612,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3455
3612
  const height = fullImageHeight / rows;
3456
3613
  const totalImages = columns * rows;
3457
3614
  const segmentDuration = reference.trueEndTime - reference.startTime;
3458
- const thumbnailDuration = segmentDuration / totalImages;
3615
+ const thumbnailDuration =
3616
+ reference.getTileDuration() || (segmentDuration / totalImages);
3459
3617
  let thumbnailTime = reference.startTime;
3460
3618
  let positionX = 0;
3461
3619
  let positionY = 0;
@@ -3478,6 +3636,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3478
3636
  positionY = Math.floor(thumbnailPosition / columns) * height;
3479
3637
  }
3480
3638
  return {
3639
+ imageHeight: fullImageHeight,
3640
+ imageWidth: fullImageWidth,
3481
3641
  height: height,
3482
3642
  positionX: positionX,
3483
3643
  positionY: positionY,
@@ -3879,20 +4039,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3879
4039
  const LanguageUtils = shaka.util.LanguageUtils;
3880
4040
  const inputlanguage = LanguageUtils.normalize(language);
3881
4041
  const chaptersTracks = this.getChaptersTracks_();
3882
- const chaptersTrack = chaptersTracks
3883
- .find((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3884
- if (!chaptersTrack || !chaptersTrack.cues) {
4042
+ const chaptersTracksWithLanguage = chaptersTracks
4043
+ .filter((t) => LanguageUtils.normalize(t.language) == inputlanguage);
4044
+ if (!chaptersTracksWithLanguage || !chaptersTracksWithLanguage.length) {
3885
4045
  return [];
3886
4046
  }
3887
4047
  const chapters = [];
3888
- for (const cue of chaptersTrack.cues) {
3889
- /** @type {shaka.extern.Chapter} */
3890
- const chapter = {
3891
- title: cue.text,
3892
- startTime: cue.startTime,
3893
- endTime: cue.endTime,
3894
- };
3895
- chapters.push(chapter);
4048
+ for (const chaptersTrack of chaptersTracksWithLanguage) {
4049
+ if (chaptersTrack && chaptersTrack.cues) {
4050
+ for (const cue of chaptersTrack.cues) {
4051
+ /** @type {shaka.extern.Chapter} */
4052
+ const chapter = {
4053
+ title: cue.text,
4054
+ startTime: cue.startTime,
4055
+ endTime: cue.endTime,
4056
+ };
4057
+ chapters.push(chapter);
4058
+ }
4059
+ }
3896
4060
  }
3897
4061
  return chapters;
3898
4062
  }
@@ -4268,7 +4432,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4268
4432
  }
4269
4433
  const trackElement =
4270
4434
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4271
- trackElement.src = uri;
4435
+ trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4272
4436
  trackElement.label = label || '';
4273
4437
  trackElement.kind = kind;
4274
4438
  trackElement.srclang = language;
@@ -4606,7 +4770,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4606
4770
  }
4607
4771
  const trackElement =
4608
4772
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4609
- trackElement.src = uri;
4773
+ trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4610
4774
  trackElement.label = label;
4611
4775
  trackElement.kind = kind;
4612
4776
  trackElement.srclang = language;
@@ -4635,6 +4799,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4635
4799
  const request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
4636
4800
  request.method = 'GET';
4637
4801
 
4802
+ this.cmcdManager_.applyTextData(request);
4803
+
4638
4804
  const response = await netEngine.request(type, request).promise;
4639
4805
 
4640
4806
  return response.data;
@@ -5032,13 +5198,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5032
5198
 
5033
5199
  if (loaded) {
5034
5200
  this.playRateController_.setBuffering(isBuffering);
5201
+ if (this.cmcdManager_) {
5202
+ this.cmcdManager_.setBuffering(isBuffering);
5203
+ }
5035
5204
  this.updateStateHistory_();
5036
5205
  }
5037
5206
 
5038
5207
  // Surface the buffering event so that the app knows if/when we are
5039
5208
  // buffering.
5040
5209
  const eventName = shaka.Player.EventName.Buffering;
5041
- this.dispatchEvent(this.makeEvent_(eventName, {'buffering': isBuffering}));
5210
+ const data = (new Map()).set('buffering', isBuffering);
5211
+ this.dispatchEvent(this.makeEvent_(eventName, data));
5042
5212
  }
5043
5213
 
5044
5214
  /**
@@ -5400,10 +5570,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5400
5570
  onAdaptation_(from, to) {
5401
5571
  // Delay the 'adaptation' event so that StreamingEngine has time to absorb
5402
5572
  // the changes before the user tries to query it.
5403
- const event = this.makeEvent_(shaka.Player.EventName.Adaptation, {
5404
- oldTrack: from,
5405
- newTrack: to,
5406
- });
5573
+ const data = new Map()
5574
+ .set('oldTrack', from)
5575
+ .set('newTrack', to);
5576
+ const event = this.makeEvent_(shaka.Player.EventName.Adaptation, data);
5407
5577
  this.delayDispatchEvent_(event);
5408
5578
  }
5409
5579
 
@@ -5427,10 +5597,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5427
5597
  onVariantChanged_(from, to) {
5428
5598
  // Delay the 'variantchanged' event so StreamingEngine has time to absorb
5429
5599
  // the changes before the user tries to query it.
5430
- const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, {
5431
- oldTrack: from,
5432
- newTrack: to,
5433
- });
5600
+ const data = new Map()
5601
+ .set('oldTrack', from)
5602
+ .set('newTrack', to);
5603
+ const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, data);
5434
5604
  this.delayDispatchEvent_(event);
5435
5605
  }
5436
5606
 
@@ -5453,10 +5623,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5453
5623
 
5454
5624
  /** @private */
5455
5625
  onAbrStatusChanged_() {
5456
- const event = this.makeEvent_(shaka.Player.EventName.AbrStatusChanged, {
5457
- newStatus: this.config_.abr.enabled,
5458
- });
5459
- this.delayDispatchEvent_(event);
5626
+ const data = (new Map()).set('newStatus', this.config_.abr.enabled);
5627
+ this.delayDispatchEvent_(this.makeEvent_(
5628
+ shaka.Player.EventName.AbrStatusChanged, data));
5460
5629
  }
5461
5630
 
5462
5631
  /**
@@ -5473,7 +5642,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5473
5642
  }
5474
5643
 
5475
5644
  const eventName = shaka.Player.EventName.Error;
5476
- const event = this.makeEvent_(eventName, {'detail': error});
5645
+ const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
5477
5646
  this.dispatchEvent(event);
5478
5647
  if (event.defaultPrevented) {
5479
5648
  error.handled = true;
@@ -5502,7 +5671,40 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5502
5671
  eventElement: region.eventElement,
5503
5672
  };
5504
5673
 
5505
- this.dispatchEvent(this.makeEvent_(eventName, {detail: clone}));
5674
+ const data = (new Map()).set('detail', clone);
5675
+ this.dispatchEvent(this.makeEvent_(eventName, data));
5676
+ }
5677
+
5678
+ /**
5679
+ * When notified of a media quality change we need to emit a
5680
+ * MediaQualityChange event to the app.
5681
+ *
5682
+ * @param {shaka.extern.MediaQualityInfo} mediaQuality
5683
+ * @param {number} position
5684
+ *
5685
+ * @private
5686
+ */
5687
+ onMediaQualityChange_(mediaQuality, position) {
5688
+ // Always make a copy to avoid exposing our internal data to the app.
5689
+ const clone = {
5690
+ bandwidth: mediaQuality.bandwidth,
5691
+ audioSamplingRate: mediaQuality.audioSamplingRate,
5692
+ codecs: mediaQuality.codecs,
5693
+ contentType: mediaQuality.contentType,
5694
+ frameRate: mediaQuality.frameRate,
5695
+ height: mediaQuality.height,
5696
+ mimeType: mediaQuality.mimeType,
5697
+ channelsCount: mediaQuality.channelsCount,
5698
+ pixelAspectRatio: mediaQuality.pixelAspectRatio,
5699
+ width: mediaQuality.width,
5700
+ };
5701
+
5702
+ const data = new Map()
5703
+ .set('mediaQuality', clone)
5704
+ .set('position', position);
5705
+
5706
+ this.dispatchEvent(this.makeEvent_(
5707
+ shaka.Player.EventName.MediaQualityChanged, data));
5506
5708
  }
5507
5709
 
5508
5710
  /**
@@ -6268,6 +6470,8 @@ shaka.Player.EventName = {
6268
6470
  AbrStatusChanged: 'abrstatuschanged',
6269
6471
  Adaptation: 'adaptation',
6270
6472
  Buffering: 'buffering',
6473
+ DownloadFailed: 'downloadfailed',
6474
+ DownloadHeadersReceived: 'downloadheadersreceived',
6271
6475
  DrmSessionUpdate: 'drmsessionupdate',
6272
6476
  Emsg: 'emsg',
6273
6477
  Error: 'error',
@@ -6276,6 +6480,7 @@ shaka.Player.EventName = {
6276
6480
  Loaded: 'loaded',
6277
6481
  Loading: 'loading',
6278
6482
  ManifestParsed: 'manifestparsed',
6483
+ MediaQualityChanged: 'mediaqualitychanged',
6279
6484
  Metadata: 'metadata',
6280
6485
  OnStateChange: 'onstatechange',
6281
6486
  OnStateIdle: 'onstateidle',
@@ -6331,7 +6536,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6331
6536
  * @define {string} A version number taken from git at compile time.
6332
6537
  * @export
6333
6538
  */
6334
- shaka.Player.version = 'v3.2.1-uncompiled';
6539
+ shaka.Player.version = 'v3.3.1-uncompiled';
6335
6540
 
6336
6541
  // Initialize the deprecation system using the version string we just set
6337
6542
  // on the player.
@@ -6375,6 +6580,7 @@ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
6375
6580
  'mpg': 'video/mpeg',
6376
6581
  'mpeg': 'video/mpeg',
6377
6582
  'm3u8': 'application/x-mpegurl',
6583
+ 'mpd': 'application/dash+xml',
6378
6584
  'mp3': 'audio/mpeg',
6379
6585
  'aac': 'audio/aac',
6380
6586
  'flac': 'audio/flac',
@@ -24,8 +24,6 @@ shaka.polyfill.MediaCapabilities = class {
24
24
  * @export
25
25
  */
26
26
  static install() {
27
- shaka.log.debug('MediaCapabilities: install');
28
-
29
27
  // Since MediaCapabilities is not fully supported on Chromecast yet, we
30
28
  // should always install polyfill for Chromecast.
31
29
  // TODO: re-evaluate MediaCapabilities in the future versions of Chromecast.
@@ -34,21 +32,34 @@ shaka.polyfill.MediaCapabilities = class {
34
32
  // See: https://github.com/google/shaka-player/issues/3530
35
33
  // TODO: re-evaluate MediaCapabilities in the future versions of Apple
36
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.
37
40
  if (!shaka.util.Platform.isChromecast() &&
38
- !shaka.util.Platform.isApple() &&
39
- navigator.mediaCapabilities) {
40
- shaka.log.debug(
41
+ !shaka.util.Platform.isApple() &&
42
+ !shaka.util.Platform.isPS5() &&
43
+ navigator.mediaCapabilities) {
44
+ shaka.log.info(
41
45
  'MediaCapabilities: Native mediaCapabilities support found.');
42
- } else {
43
- if (!navigator.mediaCapabilities) {
44
- navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
45
- }
46
- navigator.mediaCapabilities.decodingInfo =
47
- shaka.polyfill.MediaCapabilities.decodingInfo_;
46
+ return;
47
+ }
48
+
49
+ shaka.log.info('MediaCapabilities: install');
50
+
51
+ if (!navigator.mediaCapabilities) {
52
+ navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
48
53
  }
49
- // Workaround because in Apple browsers there is no way to overwrite
50
- // MediaCapabilities.
51
- window.shakaMediaCapabilities = navigator.mediaCapabilities;
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
+
61
+ navigator.mediaCapabilities.decodingInfo =
62
+ shaka.polyfill.MediaCapabilities.decodingInfo_;
52
63
  }
53
64
 
54
65
  /**
@@ -149,7 +160,7 @@ shaka.polyfill.MediaCapabilities = class {
149
160
  sessionTypes: mediaCapkeySystemConfig.sessionTypes,
150
161
  };
151
162
 
152
- // Only add the audio video capablities if they have valid data.
163
+ // Only add the audio video capabilities if they have valid data.
153
164
  // Otherwise the query will fail.
154
165
  if (audioCapabilities.length) {
155
166
  mediaKeySystemConfig.audioCapabilities = audioCapabilities;
@@ -176,6 +187,16 @@ shaka.polyfill.MediaCapabilities = class {
176
187
  }
177
188
  };
178
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
+
179
200
  // Install at a lower priority than MediaSource polyfill, so that we have
180
201
  // MediaSource available first.
181
202
  shaka.polyfill.register(shaka.polyfill.MediaCapabilities.install, -1);
@@ -208,6 +208,12 @@ shaka.polyfill.MediaSource = class {
208
208
  static patchVp09_() {
209
209
  const originalIsTypeSupported = MediaSource.isTypeSupported;
210
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
+
211
217
  MediaSource.isTypeSupported = (mimeType) => {
212
218
  // Split the MIME type into its various parameters.
213
219
  const pieces = mimeType.split(/ *; */);