shaka-player 3.2.2 → 3.3.2

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 (188) 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/.github/workflows/build_and_test.yaml +73 -0
  5. package/.github/workflows/release.yaml +53 -0
  6. package/AUTHORS +6 -5
  7. package/CHANGELOG.md +314 -1
  8. package/CONTRIBUTORS +4 -2
  9. package/build/checkversion.py +1 -1
  10. package/build/conformance.textproto +183 -169
  11. package/build/test.py +6 -0
  12. package/build/types/core +3 -0
  13. package/build/types/offline +1 -0
  14. package/build/types/polyfill +1 -0
  15. package/build/types/ui +2 -0
  16. package/demo/common/assets.js +18 -0
  17. package/demo/common/message_ids.js +13 -1
  18. package/demo/config.js +31 -1
  19. package/demo/index.html +1 -1
  20. package/demo/locales/en.json +12 -0
  21. package/demo/locales/source.json +48 -0
  22. package/dist/controls.css +26 -2
  23. package/dist/controls.css.map +1 -1
  24. package/dist/demo.compiled.debug.js +19 -16
  25. package/dist/demo.compiled.debug.map +3 -3
  26. package/dist/demo.compiled.js +19 -16
  27. package/dist/demo.compiled.map +3 -3
  28. package/dist/deps.js +21 -14
  29. package/dist/locales.js +31 -29
  30. package/dist/receiver.compiled.debug.js +11 -9
  31. package/dist/receiver.compiled.debug.map +3 -3
  32. package/dist/receiver.compiled.js +11 -9
  33. package/dist/receiver.compiled.map +3 -3
  34. package/dist/shaka-player.compiled.d.ts +232 -18
  35. package/dist/shaka-player.compiled.debug.d.ts +232 -18
  36. package/dist/shaka-player.compiled.debug.externs.js +293 -17
  37. package/dist/shaka-player.compiled.debug.js +481 -422
  38. package/dist/shaka-player.compiled.debug.map +4 -4
  39. package/dist/shaka-player.compiled.externs.js +293 -17
  40. package/dist/shaka-player.compiled.js +846 -817
  41. package/dist/shaka-player.compiled.map +4 -4
  42. package/dist/shaka-player.ui.d.ts +250 -30
  43. package/dist/shaka-player.ui.debug.d.ts +250 -30
  44. package/dist/shaka-player.ui.debug.externs.js +322 -28
  45. package/dist/shaka-player.ui.debug.js +630 -553
  46. package/dist/shaka-player.ui.debug.map +4 -4
  47. package/dist/shaka-player.ui.externs.js +322 -28
  48. package/dist/shaka-player.ui.js +1048 -1006
  49. package/dist/shaka-player.ui.map +4 -4
  50. package/docs/design/bg-fetch-after.gv +29 -0
  51. package/docs/design/bg-fetch-after.gv.png +0 -0
  52. package/docs/design/bg-fetch-before.gv +18 -0
  53. package/docs/design/bg-fetch-before.gv.png +0 -0
  54. package/docs/design/bg-fetch.md +134 -0
  55. package/docs/tutorials/blob-url.md +15 -0
  56. package/docs/tutorials/index.json +1 -0
  57. package/docs/tutorials/ui-customization.md +66 -0
  58. package/docs/tutorials/ui.md +5 -5
  59. package/externs/cmcd.js +186 -0
  60. package/externs/ima.js +3 -0
  61. package/externs/shaka/ads.js +5 -0
  62. package/externs/shaka/manifest.js +12 -1
  63. package/externs/shaka/manifest_parser.js +10 -1
  64. package/externs/shaka/net.js +20 -4
  65. package/externs/shaka/offline.js +31 -2
  66. package/externs/shaka/player.js +138 -5
  67. package/externs/shaka/text.js +24 -0
  68. package/externs/webcrypto.js +18 -0
  69. package/karma.conf.js +19 -15
  70. package/lib/abr/ewma.js +14 -0
  71. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  72. package/lib/abr/simple_abr_manager.js +6 -0
  73. package/lib/ads/ad_manager.js +1 -0
  74. package/lib/ads/client_side_ad.js +8 -0
  75. package/lib/ads/client_side_ad_manager.js +8 -4
  76. package/lib/ads/server_side_ad.js +7 -0
  77. package/lib/cast/cast_proxy.js +3 -0
  78. package/lib/cast/cast_receiver.js +3 -0
  79. package/lib/cast/cast_utils.js +1 -0
  80. package/lib/dash/dash_parser.js +79 -33
  81. package/lib/dash/segment_base.js +25 -1
  82. package/lib/dash/segment_list.js +18 -9
  83. package/lib/dash/segment_template.js +23 -10
  84. package/lib/hls/hls_parser.js +116 -23
  85. package/lib/hls/manifest_text_parser.js +1 -0
  86. package/lib/media/quality_observer.js +274 -0
  87. package/lib/media/region_observer.js +39 -33
  88. package/lib/media/region_timeline.js +17 -19
  89. package/lib/media/segment_index.js +8 -1
  90. package/lib/media/segment_reference.js +37 -4
  91. package/lib/media/streaming_engine.js +47 -6
  92. package/lib/net/http_fetch_plugin.js +31 -11
  93. package/lib/net/http_plugin_utils.js +1 -0
  94. package/lib/net/http_xhr_plugin.js +40 -14
  95. package/lib/net/networking_engine.js +85 -18
  96. package/lib/offline/download_info.js +68 -0
  97. package/lib/offline/download_manager.js +13 -5
  98. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  99. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  100. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  101. package/lib/offline/storage.js +401 -293
  102. package/lib/offline/stored_content_utils.js +2 -0
  103. package/lib/player.js +254 -27
  104. package/lib/polyfill/media_capabilities.js +30 -15
  105. package/lib/polyfill/random_uuid.js +52 -0
  106. package/lib/text/cue.js +18 -0
  107. package/lib/text/ttml_text_parser.js +109 -60
  108. package/lib/text/ui_text_displayer.js +19 -13
  109. package/lib/util/cmcd_manager.js +672 -0
  110. package/lib/util/error.js +6 -0
  111. package/lib/util/fake_event_target.js +21 -1
  112. package/lib/util/language_utils.js +12 -0
  113. package/lib/util/mutex.js +52 -0
  114. package/lib/util/periods.js +98 -64
  115. package/lib/util/player_configuration.js +18 -0
  116. package/lib/util/stream_utils.js +86 -10
  117. package/lib/util/xml_utils.js +19 -0
  118. package/package.json +3 -6
  119. package/shaka-player.uncompiled.js +3 -0
  120. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  121. package/test/dash/dash_parser_live_unit.js +144 -1
  122. package/test/dash/dash_parser_manifest_unit.js +37 -2
  123. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  124. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  125. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  126. package/test/hls/hls_live_unit.js +2 -0
  127. package/test/hls/hls_parser_unit.js +76 -0
  128. package/test/media/drm_engine_unit.js +3 -3
  129. package/test/media/quality_observer_unit.js +142 -0
  130. package/test/media/region_observer_unit.js +40 -5
  131. package/test/media/region_timeline_unit.js +40 -22
  132. package/test/media/segment_reference_unit.js +16 -1
  133. package/test/media/streaming_engine_integration.js +4 -2
  134. package/test/media/streaming_engine_unit.js +147 -0
  135. package/test/net/http_plugin_unit.js +20 -15
  136. package/test/offline/storage_integration.js +108 -4
  137. package/test/player_integration.js +140 -8
  138. package/test/player_src_equals_integration.js +41 -8
  139. package/test/player_unit.js +19 -9
  140. package/test/test/assets/chapters.vtt +2 -2
  141. package/test/test/assets/chapters2.vtt +10 -0
  142. package/test/test/util/dash_parser_util.js +4 -0
  143. package/test/test/util/fake_ad.js +7 -0
  144. package/test/test/util/fake_drm_engine.js +9 -2
  145. package/test/test/util/manifest_generator.js +3 -2
  146. package/test/test/util/test_scheme.js +14 -1
  147. package/test/test/util/ttml_utils.js +81 -0
  148. package/test/test/util/util.js +10 -7
  149. package/test/text/mp4_ttml_parser_unit.js +11 -18
  150. package/test/text/ttml_text_parser_unit.js +544 -228
  151. package/test/ui/text_displayer_layout_unit.js +8 -7
  152. package/test/ui/ui_unit.js +250 -0
  153. package/test/util/cmcd_manager_unit.js +291 -0
  154. package/test/util/stream_utils_unit.js +36 -30
  155. package/third_party/closure-uri/uri.js +21 -0
  156. package/ui/airplay_button.js +1 -0
  157. package/ui/audio_language_selection.js +7 -0
  158. package/ui/cast_button.js +1 -0
  159. package/ui/context_menu.js +109 -0
  160. package/ui/controls.js +44 -7
  161. package/ui/controls.less +1 -0
  162. package/ui/enums.js +4 -0
  163. package/ui/externs/ui.js +28 -1
  164. package/ui/fast_forward_button.js +14 -5
  165. package/ui/fullscreen_button.js +1 -0
  166. package/ui/less/buttons.less +0 -4
  167. package/ui/less/containers.less +74 -0
  168. package/ui/less/overflow_menu.less +0 -10
  169. package/ui/less/tooltip.less +97 -0
  170. package/ui/locales/en.json +1 -0
  171. package/ui/locales/source.json +4 -0
  172. package/ui/localization.js +12 -36
  173. package/ui/loop_button.js +5 -0
  174. package/ui/mute_button.js +2 -1
  175. package/ui/overflow_menu.js +5 -25
  176. package/ui/pip_button.js +5 -0
  177. package/ui/play_button.js +2 -2
  178. package/ui/playback_rate_selection.js +8 -9
  179. package/ui/resolution_selection.js +7 -0
  180. package/ui/rewind_button.js +15 -6
  181. package/ui/seek_bar.js +4 -2
  182. package/ui/settings_menu.js +36 -4
  183. package/ui/small_play_button.js +1 -0
  184. package/ui/statistics_button.js +253 -0
  185. package/ui/text_selection.js +12 -4
  186. package/ui/ui.js +30 -0
  187. package/ui/volume_bar.js +1 -1
  188. 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
  }
@@ -723,11 +781,15 @@ 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();
729
788
  this.networkingEngine_ = null;
730
789
  }
790
+
791
+ // FakeEventTarget implements IReleasable
792
+ super.release();
731
793
  }
732
794
 
733
795
  /**
@@ -1020,6 +1082,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1020
1082
  return this.wrapWalkerListenersWithPromise_(events);
1021
1083
  }
1022
1084
 
1085
+ /**
1086
+ * Provides a way to update the stream start position during the media loading
1087
+ * process. Can for example be called from the <code>manifestparsed</code>
1088
+ * event handler to update the start position based on information in the
1089
+ * manifest.
1090
+ *
1091
+ * @param {number} startTime
1092
+ * @export
1093
+ */
1094
+ updateStartTime(startTime) {
1095
+ this.updatedStartTime_ = startTime;
1096
+ }
1097
+
1023
1098
  /**
1024
1099
  * Tell the player to load the content at <code>assetUri</code> and start
1025
1100
  * playback at <code>startTime</code>. Before calling <code>load</code>,
@@ -1040,6 +1115,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1040
1115
  * @export
1041
1116
  */
1042
1117
  load(assetUri, startTime, mimeType) {
1118
+ this.updatedStartTime_ = null;
1119
+
1043
1120
  // Do not allow the player to be used after |destroy| is called.
1044
1121
  if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
1045
1122
  return Promise.reject(this.createAbortLoadError_());
@@ -1093,6 +1170,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1093
1170
  // before we allow calls to |updateStateHistory|.
1094
1171
  this.stats_ = new shaka.util.Stats();
1095
1172
 
1173
+ // Create the CMCD manager so client data can be attached to all requests
1174
+ this.cmcdManager_ = this.createCmcd_();
1175
+
1096
1176
  // Load's request is a little different, so we can't use our normal
1097
1177
  // listeners-to-promise method. It is the only request where we may skip the
1098
1178
  // request, so we need to set the on skip callback to reject with a specific
@@ -1594,6 +1674,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1594
1674
  goog.asserts.assert(
1595
1675
  this.networkingEngine_,
1596
1676
  'Need networking engine to parse manifest.');
1677
+ goog.asserts.assert(
1678
+ this.cmcdManager_,
1679
+ 'Need CMCD manager to populate manifest request data.');
1597
1680
  goog.asserts.assert(
1598
1681
  this.config_,
1599
1682
  'Need player config to parse manifest.');
@@ -1611,15 +1694,38 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1611
1694
  // initialize it now even through it appears a little out-of-place.
1612
1695
  this.regionTimeline_ =
1613
1696
  new shaka.media.RegionTimeline(() => this.seekRange());
1614
- this.regionTimeline_.setListeners(/* onRegionAdded= */ (region) => {
1697
+ this.regionTimeline_.addEventListener('regionadd', (event) => {
1698
+ /** @type {shaka.extern.TimelineRegionInfo} */
1699
+ const region = event['region'];
1615
1700
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionAdded, region);
1701
+
1616
1702
  if (this.adManager_) {
1617
1703
  this.adManager_.onDashTimedMetadata(region);
1618
1704
  }
1619
1705
  });
1620
1706
 
1707
+ this.qualityObserver_ = null;
1708
+ if (this.config_.streaming.observeQualityChanges) {
1709
+ this.qualityObserver_ = new shaka.media.QualityObserver(
1710
+ () => this.getBufferedInfo());
1711
+
1712
+ this.qualityObserver_.addEventListener('qualitychange', (event) => {
1713
+ /** @type {shaka.extern.MediaQualityInfo} */
1714
+ const mediaQualityInfo = event['quality'];
1715
+ /** @type {number} */
1716
+ const position = event['position'];
1717
+ this.onMediaQualityChange_(mediaQualityInfo, position);
1718
+ });
1719
+ }
1720
+
1621
1721
  const playerInterface = {
1622
1722
  networkingEngine: networkingEngine,
1723
+ modifyManifestRequest: (request, manifestInfo) => {
1724
+ this.cmcdManager_.applyManifestData(request, manifestInfo);
1725
+ },
1726
+ modifySegmentRequest: (request, segmentInfo) => {
1727
+ this.cmcdManager_.applySegmentData(request, segmentInfo);
1728
+ },
1623
1729
  filter: (manifest) => this.filterManifest_(manifest),
1624
1730
  makeTextStreamsForClosedCaptions: (manifest) => {
1625
1731
  return this.makeTextStreamsForClosedCaptions_(manifest);
@@ -1780,6 +1886,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1780
1886
  // something we are now depending on.
1781
1887
  has.startTime = wants.startTime;
1782
1888
 
1889
+ // If updateStartTime() has been called since load() was invoked use the
1890
+ // requested startTime
1891
+ if (this.updatedStartTime_ != null) {
1892
+ has.startTime = this.updatedStartTime_;
1893
+ this.updatedStartTime_ = null;
1894
+ }
1895
+
1783
1896
  // Store a reference to values in |has| after asserting so that closure will
1784
1897
  // know that they will still be non-null between calls to await.
1785
1898
  const mediaElement = has.mediaElement;
@@ -2177,9 +2290,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2177
2290
  this.video_.textTracks, 'change', () => this.onTracksChanged_());
2178
2291
  }
2179
2292
 
2293
+ const extension = shaka.media.ManifestParser.getExtension(has.uri);
2294
+ const mimeType =
2295
+ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
2296
+
2180
2297
  // By setting |src| we are done "loading" with src=. We don't need to set
2181
2298
  // the current time because |playhead| will do that for us.
2182
- has.mediaElement.src = has.uri;
2299
+ has.mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2183
2300
 
2184
2301
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2185
2302
  // no matter the value of the preload attribute. This is harmful on some
@@ -2509,8 +2626,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2509
2626
  this.abrManager_.segmentDownloaded(deltaTimeMs, bytesDownloaded);
2510
2627
  }
2511
2628
  };
2629
+ /** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
2630
+ const onHeadersReceived_ = (headers, request, requestType) => {
2631
+ // Release a 'downloadheadersreceived' event.
2632
+ const name = shaka.Player.EventName.DownloadHeadersReceived;
2633
+ const data = new Map()
2634
+ .set('headers', headers)
2635
+ .set('request', request)
2636
+ .set('requestType', requestType);
2637
+ this.dispatchEvent(this.makeEvent_(name, data));
2638
+ };
2639
+ /** @type {shaka.net.NetworkingEngine.OnDownloadFailed} */
2640
+ const onDownloadFailed_ = (request, error, httpResponseCode, aborted) => {
2641
+ // Release a 'downloadfailed' event.
2642
+ const name = shaka.Player.EventName.DownloadFailed;
2643
+ const data = new Map()
2644
+ .set('request', request)
2645
+ .set('error', error)
2646
+ .set('httpResponseCode', httpResponseCode)
2647
+ .set('aborted', aborted);
2648
+ this.dispatchEvent(this.makeEvent_(name, data));
2649
+ };
2512
2650
 
2513
- return new shaka.net.NetworkingEngine(onProgressUpdated_);
2651
+ return new shaka.net.NetworkingEngine(
2652
+ onProgressUpdated_, onHeadersReceived_, onDownloadFailed_);
2514
2653
  }
2515
2654
 
2516
2655
  /**
@@ -2547,26 +2686,38 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2547
2686
  // Create the region observer. This will allow us to notify the app when we
2548
2687
  // move in and out of timeline regions.
2549
2688
  const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2550
- const onEnterRegion = (region, seeking) => {
2689
+
2690
+ regionObserver.addEventListener('enter', (event) => {
2691
+ /** @type {shaka.extern.TimelineRegionInfo} */
2692
+ const region = event['region'];
2551
2693
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2552
- };
2553
- const onExitRegion = (region, seeking) => {
2694
+ });
2695
+
2696
+ regionObserver.addEventListener('exit', (event) => {
2697
+ /** @type {shaka.extern.TimelineRegionInfo} */
2698
+ const region = event['region'];
2554
2699
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2555
- };
2556
- const onSkipRegion = (region, seeking) => {
2700
+ });
2701
+
2702
+ regionObserver.addEventListener('skip', (event) => {
2703
+ /** @type {shaka.extern.TimelineRegionInfo} */
2704
+ const region = event['region'];
2705
+ /** @type {boolean} */
2706
+ const seeking = event['seeking'];
2557
2707
  // If we are seeking, we don't want to surface the enter/exit events since
2558
2708
  // they didn't play through them.
2559
2709
  if (!seeking) {
2560
2710
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2561
2711
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2562
2712
  }
2563
- };
2564
- regionObserver.setListeners(onEnterRegion, onExitRegion, onSkipRegion);
2713
+ });
2565
2714
 
2566
2715
  // Now that we have all our observers, create a manager for them.
2567
2716
  const manager = new shaka.media.PlayheadObserverManager(this.video_);
2568
2717
  manager.manage(regionObserver);
2569
-
2718
+ if (this.qualityObserver_) {
2719
+ manager.manage(this.qualityObserver_);
2720
+ }
2570
2721
  return manager;
2571
2722
  }
2572
2723
 
@@ -2682,6 +2833,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2682
2833
  mediaElement, closedCaptionsParser, textDisplayer, onMetadata);
2683
2834
  }
2684
2835
 
2836
+ /**
2837
+ * Create a new CMCD manager.
2838
+ *
2839
+ * @private
2840
+ */
2841
+ createCmcd_() {
2842
+ /** @type {shaka.util.CmcdManager.PlayerInterface} */
2843
+ const playerInterface = {
2844
+ getBandwidthEstimate: () => this.abrManager_ ?
2845
+ this.abrManager_.getBandwidthEstimate() : NaN,
2846
+ getBufferedInfo: () => this.getBufferedInfo(),
2847
+ getCurrentTime: () => this.video_ ? this.video_.currentTime : 0,
2848
+ getVariantTracks: () => this.getVariantTracks(),
2849
+ getPlaybackRate: () => this.getPlaybackRate(),
2850
+ isLive: () => this.isLive(),
2851
+ };
2852
+
2853
+ return new shaka.util.CmcdManager(playerInterface, this.config_.cmcd);
2854
+ }
2855
+
2685
2856
  /**
2686
2857
  * Creates a new instance of StreamingEngine. This can be replaced by tests
2687
2858
  * to create fake instances instead.
@@ -2691,19 +2862,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2691
2862
  createStreamingEngine() {
2692
2863
  goog.asserts.assert(
2693
2864
  this.playhead_ && this.abrManager_ && this.mediaSourceEngine_ &&
2694
- this.manifest_,
2865
+ this.cmcdManager_ && this.manifest_,
2695
2866
  'Must not be destroyed');
2696
2867
 
2697
2868
  /** @type {shaka.media.StreamingEngine.PlayerInterface} */
2698
2869
  const playerInterface = {
2699
2870
  getPresentationTime: () => this.playhead_.getTime(),
2700
2871
  getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
2872
+ modifySegmentRequest: (request, segmentInfo) => {
2873
+ this.cmcdManager_.applySegmentData(request, segmentInfo);
2874
+ },
2701
2875
  mediaSourceEngine: this.mediaSourceEngine_,
2702
2876
  netEngine: this.networkingEngine_,
2703
2877
  onError: (error) => this.onError_(error),
2704
2878
  onEvent: (event) => this.dispatchEvent(event),
2705
2879
  onManifestUpdate: () => this.onManifestUpdate_(),
2706
2880
  onSegmentAppended: () => this.onSegmentAppended_(),
2881
+ onInitSegmentAppended: (position, initSegment) => {
2882
+ const mediaQuality = initSegment.getMediaQuality();
2883
+ if (mediaQuality && this.qualityObserver_) {
2884
+ this.qualityObserver_.addMediaQualityChange(mediaQuality, position);
2885
+ }
2886
+ },
2707
2887
  };
2708
2888
 
2709
2889
  return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
@@ -3454,7 +3634,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3454
3634
  const height = fullImageHeight / rows;
3455
3635
  const totalImages = columns * rows;
3456
3636
  const segmentDuration = reference.trueEndTime - reference.startTime;
3457
- const thumbnailDuration = segmentDuration / totalImages;
3637
+ const thumbnailDuration =
3638
+ reference.getTileDuration() || (segmentDuration / totalImages);
3458
3639
  let thumbnailTime = reference.startTime;
3459
3640
  let positionX = 0;
3460
3641
  let positionY = 0;
@@ -3477,6 +3658,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3477
3658
  positionY = Math.floor(thumbnailPosition / columns) * height;
3478
3659
  }
3479
3660
  return {
3661
+ imageHeight: fullImageHeight,
3662
+ imageWidth: fullImageWidth,
3480
3663
  height: height,
3481
3664
  positionX: positionX,
3482
3665
  positionY: positionY,
@@ -3878,20 +4061,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3878
4061
  const LanguageUtils = shaka.util.LanguageUtils;
3879
4062
  const inputlanguage = LanguageUtils.normalize(language);
3880
4063
  const chaptersTracks = this.getChaptersTracks_();
3881
- const chaptersTrack = chaptersTracks
3882
- .find((t) => LanguageUtils.normalize(t.language) == inputlanguage);
3883
- if (!chaptersTrack || !chaptersTrack.cues) {
4064
+ const chaptersTracksWithLanguage = chaptersTracks
4065
+ .filter((t) => LanguageUtils.normalize(t.language) == inputlanguage);
4066
+ if (!chaptersTracksWithLanguage || !chaptersTracksWithLanguage.length) {
3884
4067
  return [];
3885
4068
  }
3886
4069
  const chapters = [];
3887
- for (const cue of chaptersTrack.cues) {
3888
- /** @type {shaka.extern.Chapter} */
3889
- const chapter = {
3890
- title: cue.text,
3891
- startTime: cue.startTime,
3892
- endTime: cue.endTime,
3893
- };
3894
- chapters.push(chapter);
4070
+ for (const chaptersTrack of chaptersTracksWithLanguage) {
4071
+ if (chaptersTrack && chaptersTrack.cues) {
4072
+ for (const cue of chaptersTrack.cues) {
4073
+ /** @type {shaka.extern.Chapter} */
4074
+ const chapter = {
4075
+ title: cue.text,
4076
+ startTime: cue.startTime,
4077
+ endTime: cue.endTime,
4078
+ };
4079
+ chapters.push(chapter);
4080
+ }
4081
+ }
3895
4082
  }
3896
4083
  return chapters;
3897
4084
  }
@@ -4267,7 +4454,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4267
4454
  }
4268
4455
  const trackElement =
4269
4456
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4270
- trackElement.src = uri;
4457
+ trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4271
4458
  trackElement.label = label || '';
4272
4459
  trackElement.kind = kind;
4273
4460
  trackElement.srclang = language;
@@ -4605,7 +4792,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4605
4792
  }
4606
4793
  const trackElement =
4607
4794
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4608
- trackElement.src = uri;
4795
+ trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4609
4796
  trackElement.label = label;
4610
4797
  trackElement.kind = kind;
4611
4798
  trackElement.srclang = language;
@@ -4634,6 +4821,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4634
4821
  const request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
4635
4822
  request.method = 'GET';
4636
4823
 
4824
+ this.cmcdManager_.applyTextData(request);
4825
+
4637
4826
  const response = await netEngine.request(type, request).promise;
4638
4827
 
4639
4828
  return response.data;
@@ -5031,6 +5220,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5031
5220
 
5032
5221
  if (loaded) {
5033
5222
  this.playRateController_.setBuffering(isBuffering);
5223
+ if (this.cmcdManager_) {
5224
+ this.cmcdManager_.setBuffering(isBuffering);
5225
+ }
5034
5226
  this.updateStateHistory_();
5035
5227
  }
5036
5228
 
@@ -5505,6 +5697,38 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5505
5697
  this.dispatchEvent(this.makeEvent_(eventName, data));
5506
5698
  }
5507
5699
 
5700
+ /**
5701
+ * When notified of a media quality change we need to emit a
5702
+ * MediaQualityChange event to the app.
5703
+ *
5704
+ * @param {shaka.extern.MediaQualityInfo} mediaQuality
5705
+ * @param {number} position
5706
+ *
5707
+ * @private
5708
+ */
5709
+ onMediaQualityChange_(mediaQuality, position) {
5710
+ // Always make a copy to avoid exposing our internal data to the app.
5711
+ const clone = {
5712
+ bandwidth: mediaQuality.bandwidth,
5713
+ audioSamplingRate: mediaQuality.audioSamplingRate,
5714
+ codecs: mediaQuality.codecs,
5715
+ contentType: mediaQuality.contentType,
5716
+ frameRate: mediaQuality.frameRate,
5717
+ height: mediaQuality.height,
5718
+ mimeType: mediaQuality.mimeType,
5719
+ channelsCount: mediaQuality.channelsCount,
5720
+ pixelAspectRatio: mediaQuality.pixelAspectRatio,
5721
+ width: mediaQuality.width,
5722
+ };
5723
+
5724
+ const data = new Map()
5725
+ .set('mediaQuality', clone)
5726
+ .set('position', position);
5727
+
5728
+ this.dispatchEvent(this.makeEvent_(
5729
+ shaka.Player.EventName.MediaQualityChanged, data));
5730
+ }
5731
+
5508
5732
  /**
5509
5733
  * Turn the media element's error object into a Shaka Player error object.
5510
5734
  *
@@ -6268,6 +6492,8 @@ shaka.Player.EventName = {
6268
6492
  AbrStatusChanged: 'abrstatuschanged',
6269
6493
  Adaptation: 'adaptation',
6270
6494
  Buffering: 'buffering',
6495
+ DownloadFailed: 'downloadfailed',
6496
+ DownloadHeadersReceived: 'downloadheadersreceived',
6271
6497
  DrmSessionUpdate: 'drmsessionupdate',
6272
6498
  Emsg: 'emsg',
6273
6499
  Error: 'error',
@@ -6276,6 +6502,7 @@ shaka.Player.EventName = {
6276
6502
  Loaded: 'loaded',
6277
6503
  Loading: 'loading',
6278
6504
  ManifestParsed: 'manifestparsed',
6505
+ MediaQualityChanged: 'mediaqualitychanged',
6279
6506
  Metadata: 'metadata',
6280
6507
  OnStateChange: 'onstatechange',
6281
6508
  OnStateIdle: 'onstateidle',
@@ -6331,7 +6558,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6331
6558
  * @define {string} A version number taken from git at compile time.
6332
6559
  * @export
6333
6560
  */
6334
- shaka.Player.version = 'v3.2.2-uncompiled';
6561
+ shaka.Player.version = 'v3.3.2-uncompiled'; // x-release-please-version
6335
6562
 
6336
6563
  // Initialize the deprecation system using the version string we just set
6337
6564
  // on the player.
@@ -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.
@@ -40,21 +38,28 @@ shaka.polyfill.MediaCapabilities = class {
40
38
  // TODO: re-evaluate MediaCapabilities in the future versions of PS5
41
39
  // Browsers.
42
40
  if (!shaka.util.Platform.isChromecast() &&
43
- !shaka.util.Platform.isApple() &&
44
- !shaka.util.Platform.isPS5() &&
45
- navigator.mediaCapabilities) {
46
- shaka.log.debug(
41
+ !shaka.util.Platform.isApple() &&
42
+ !shaka.util.Platform.isPS5() &&
43
+ navigator.mediaCapabilities) {
44
+ shaka.log.info(
47
45
  'MediaCapabilities: Native mediaCapabilities support found.');
48
- } else {
49
- if (!navigator.mediaCapabilities) {
50
- navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
51
- }
52
- navigator.mediaCapabilities.decodingInfo =
53
- shaka.polyfill.MediaCapabilities.decodingInfo_;
46
+ return;
47
+ }
48
+
49
+ shaka.log.info('MediaCapabilities: install');
50
+
51
+ if (!navigator.mediaCapabilities) {
52
+ navigator.mediaCapabilities = /** @type {!MediaCapabilities} */ ({});
54
53
  }
55
- // Workaround because in Apple browsers there is no way to overwrite
56
- // MediaCapabilities.
57
- 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_;
58
63
  }
59
64
 
60
65
  /**
@@ -182,6 +187,16 @@ shaka.polyfill.MediaCapabilities = class {
182
187
  }
183
188
  };
184
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
+
185
200
  // Install at a lower priority than MediaSource polyfill, so that we have
186
201
  // MediaSource available first.
187
202
  shaka.polyfill.register(shaka.polyfill.MediaCapabilities.install, -1);
@@ -0,0 +1,52 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2016 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ goog.provide('shaka.polyfill.RandomUUID');
7
+
8
+ goog.require('shaka.log');
9
+ goog.require('shaka.polyfill');
10
+
11
+ /**
12
+ * @summary A polyfill to provide window.crypto.randomUUID in all browsers.
13
+ * @export
14
+ */
15
+ shaka.polyfill.RandomUUID = class {
16
+ /**
17
+ * Install the polyfill if needed.
18
+ * @export
19
+ */
20
+ static install() {
21
+ shaka.log.debug('randomUUID.install');
22
+
23
+ if (!window.crypto) {
24
+ // See: https://caniuse.com/cryptography
25
+ shaka.log.debug(
26
+ 'window.crypto must be available to install randomUUID polyfill.');
27
+ return;
28
+ }
29
+
30
+ if ('randomUUID' in window.crypto) {
31
+ shaka.log.debug(
32
+ 'RandomUUID: Native window.crypto.randomUUID() support found.');
33
+ return;
34
+ }
35
+
36
+ window.crypto.randomUUID = shaka.polyfill.RandomUUID.randomUUID_;
37
+ }
38
+
39
+ /**
40
+ * @return {string}
41
+ * @private
42
+ */
43
+ static randomUUID_() {
44
+ const url = URL.createObjectURL(new Blob());
45
+ const uuid = url.toString();
46
+ URL.revokeObjectURL(url);
47
+ return uuid.substr(uuid.lastIndexOf('/') + 1);
48
+ }
49
+ };
50
+
51
+
52
+ shaka.polyfill.register(shaka.polyfill.RandomUUID.install);
package/lib/text/cue.js CHANGED
@@ -144,6 +144,18 @@ shaka.text.Cue = class {
144
144
  */
145
145
  this.border = '';
146
146
 
147
+ /**
148
+ * @override
149
+ * @exportInterface
150
+ */
151
+ this.textStrokeColor = '';
152
+
153
+ /**
154
+ * @override
155
+ * @exportInterface
156
+ */
157
+ this.textStrokeWidth = '';
158
+
147
159
  /**
148
160
  * @override
149
161
  * @exportInterface
@@ -210,6 +222,12 @@ shaka.text.Cue = class {
210
222
  */
211
223
  this.nestedCues = [];
212
224
 
225
+ /**
226
+ * @override
227
+ * @exportInterface
228
+ */
229
+ this.isContainer = false;
230
+
213
231
  /**
214
232
  * @override
215
233
  * @exportInterface