shaka-player 3.1.4 → 3.3.0

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 (214) 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 +6 -5
  5. package/CHANGELOG.md +363 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/README.md +4 -4
  8. package/build/types/core +3 -0
  9. package/build/types/offline +1 -0
  10. package/build/types/polyfill +2 -0
  11. package/build/types/ui +2 -0
  12. package/demo/common/asset.js +9 -7
  13. package/demo/common/assets.js +28 -1
  14. package/demo/common/message_ids.js +15 -2
  15. package/demo/config.js +35 -2
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +14 -1
  18. package/demo/locales/source.json +56 -4
  19. package/demo/main.js +2 -1
  20. package/dist/controls.css +26 -2
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +24 -21
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +24 -21
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +33 -25
  27. package/dist/locales.js +31 -29
  28. package/dist/receiver.compiled.debug.js +14 -12
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +14 -12
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +258 -20
  33. package/dist/shaka-player.compiled.debug.d.ts +258 -20
  34. package/dist/shaka-player.compiled.debug.externs.js +1005 -695
  35. package/dist/shaka-player.compiled.debug.js +720 -640
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1005 -695
  38. package/dist/shaka-player.compiled.js +850 -817
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +274 -21
  41. package/dist/shaka-player.ui.debug.d.ts +274 -21
  42. package/dist/shaka-player.ui.debug.externs.js +766 -429
  43. package/dist/shaka-player.ui.debug.js +863 -765
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +766 -429
  46. package/dist/shaka-player.ui.js +1042 -996
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/bg-fetch-after.gv +29 -0
  49. package/docs/design/bg-fetch-after.gv.png +0 -0
  50. package/docs/design/bg-fetch-before.gv +18 -0
  51. package/docs/design/bg-fetch-before.gv.png +0 -0
  52. package/docs/design/bg-fetch.md +134 -0
  53. package/docs/tutorials/blob-url.md +15 -0
  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/aria.js +6 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +29 -1
  60. package/externs/shaka/manifest.js +18 -2
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +36 -2
  64. package/externs/shaka/player.js +182 -13
  65. package/externs/shaka/text.js +24 -10
  66. package/externs/texttrack.js +3 -0
  67. package/externs/webcrypto.js +18 -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/ad_manager.js +16 -15
  72. package/lib/ads/client_side_ad.js +8 -0
  73. package/lib/ads/client_side_ad_manager.js +14 -6
  74. package/lib/ads/server_side_ad.js +7 -0
  75. package/lib/ads/server_side_ad_manager.js +19 -2
  76. package/lib/cast/cast_proxy.js +8 -0
  77. package/lib/cast/cast_utils.js +7 -1
  78. package/lib/cea/cea608_data_channel.js +56 -56
  79. package/lib/cea/cea608_memory.js +33 -33
  80. package/lib/cea/cea708_service.js +43 -43
  81. package/lib/cea/cea708_window.js +38 -38
  82. package/lib/cea/cea_decoder.js +13 -14
  83. package/lib/cea/cea_utils.js +23 -23
  84. package/lib/cea/dtvcc_packet_builder.js +7 -7
  85. package/lib/cea/i_caption_decoder.js +2 -3
  86. package/lib/cea/i_cea_parser.js +2 -2
  87. package/lib/cea/mp4_cea_parser.js +6 -6
  88. package/lib/dash/dash_parser.js +53 -14
  89. package/lib/dash/segment_base.js +25 -1
  90. package/lib/dash/segment_list.js +18 -9
  91. package/lib/dash/segment_template.js +23 -10
  92. package/lib/hls/hls_parser.js +210 -30
  93. package/lib/media/closed_caption_parser.js +1 -2
  94. package/lib/media/drm_engine.js +162 -192
  95. package/lib/media/quality_observer.js +291 -0
  96. package/lib/media/segment_index.js +9 -1
  97. package/lib/media/segment_reference.js +37 -5
  98. package/lib/media/streaming_engine.js +48 -10
  99. package/lib/net/http_fetch_plugin.js +31 -11
  100. package/lib/net/http_plugin_utils.js +1 -0
  101. package/lib/net/http_xhr_plugin.js +40 -14
  102. package/lib/net/networking_engine.js +82 -18
  103. package/lib/offline/download_info.js +68 -0
  104. package/lib/offline/download_manager.js +14 -7
  105. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  106. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  107. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  108. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  109. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  110. package/lib/offline/manifest_converter.js +3 -1
  111. package/lib/offline/storage.js +404 -297
  112. package/lib/offline/stored_content_utils.js +2 -0
  113. package/lib/player.js +622 -153
  114. package/lib/polyfill/aria.js +82 -0
  115. package/lib/polyfill/random_uuid.js +52 -0
  116. package/lib/routing/walker.js +2 -3
  117. package/lib/text/cue.js +12 -0
  118. package/lib/text/srt_text_parser.js +4 -4
  119. package/lib/text/text_engine.js +2 -2
  120. package/lib/text/ttml_text_parser.js +25 -2
  121. package/lib/text/ui_text_displayer.js +2 -2
  122. package/lib/text/vtt_text_parser.js +2 -2
  123. package/lib/text/web_vtt_generator.js +55 -12
  124. package/lib/util/cmcd_manager.js +826 -0
  125. package/lib/util/error.js +12 -0
  126. package/lib/util/language_utils.js +12 -0
  127. package/lib/util/manifest_parser_utils.js +1 -0
  128. package/lib/util/mp4_box_parsers.js +20 -20
  129. package/lib/util/multi_map.js +8 -0
  130. package/lib/util/mutex.js +52 -0
  131. package/lib/util/periods.js +98 -64
  132. package/lib/util/player_configuration.js +31 -1
  133. package/lib/util/stream_utils.js +140 -85
  134. package/lib/util/xml_utils.js +31 -12
  135. package/package.json +1 -1
  136. package/roadmap.md +24 -22
  137. package/shaka-player.uncompiled.js +4 -0
  138. package/test/cast/cast_utils_unit.js +1 -0
  139. package/test/cea/cea608_memory_unit.js +1 -1
  140. package/test/cea/cea708_service_unit.js +8 -8
  141. package/test/cea/cea708_window_unit.js +6 -6
  142. package/test/cea/cea_decoder_unit.js +1 -1
  143. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  144. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  145. package/test/dash/dash_parser_live_unit.js +101 -1
  146. package/test/dash/dash_parser_manifest_unit.js +78 -2
  147. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  148. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  149. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  150. package/test/hls/hls_live_unit.js +2 -0
  151. package/test/hls/hls_parser_unit.js +252 -0
  152. package/test/media/drm_engine_integration.js +86 -12
  153. package/test/media/drm_engine_unit.js +310 -450
  154. package/test/media/quality_observer_unit.js +139 -0
  155. package/test/media/segment_reference_unit.js +16 -1
  156. package/test/media/streaming_engine_integration.js +2 -1
  157. package/test/media/streaming_engine_unit.js +2 -0
  158. package/test/net/http_plugin_unit.js +20 -15
  159. package/test/offline/manifest_convert_unit.js +2 -0
  160. package/test/offline/storage_integration.js +117 -23
  161. package/test/player_integration.js +151 -0
  162. package/test/player_src_equals_integration.js +52 -0
  163. package/test/player_unit.js +11 -33
  164. package/test/test/assets/chapters.srt +11 -0
  165. package/test/test/assets/chapters.vtt +10 -0
  166. package/test/test/util/cea_utils.js +12 -12
  167. package/test/test/util/dash_parser_util.js +4 -0
  168. package/test/test/util/fake_ad.js +7 -0
  169. package/test/test/util/fake_ad_manager.js +7 -0
  170. package/test/test/util/fake_drm_engine.js +9 -2
  171. package/test/test/util/manifest_generator.js +5 -2
  172. package/test/test/util/manifest_parser_util.js +3 -1
  173. package/test/test/util/test_scheme.js +14 -1
  174. package/test/text/ttml_text_parser_unit.js +47 -0
  175. package/test/text/web_vtt_generator_unit.js +81 -7
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +277 -0
  178. package/test/util/stream_utils_unit.js +118 -10
  179. package/ui/airplay_button.js +2 -1
  180. package/ui/audio_language_selection.js +9 -5
  181. package/ui/cast_button.js +5 -6
  182. package/ui/constants.js +0 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +41 -7
  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 +16 -8
  189. package/ui/fullscreen_button.js +2 -3
  190. package/ui/language_utils.js +1 -1
  191. package/ui/less/buttons.less +0 -4
  192. package/ui/less/containers.less +74 -0
  193. package/ui/less/overflow_menu.less +0 -10
  194. package/ui/less/tooltip.less +97 -0
  195. package/ui/locales/en.json +1 -0
  196. package/ui/locales/source.json +4 -0
  197. package/ui/loop_button.js +6 -3
  198. package/ui/mute_button.js +3 -4
  199. package/ui/overflow_menu.js +7 -28
  200. package/ui/pip_button.js +10 -7
  201. package/ui/play_button.js +2 -2
  202. package/ui/playback_rate_selection.js +11 -15
  203. package/ui/presentation_time.js +1 -3
  204. package/ui/resolution_selection.js +11 -7
  205. package/ui/rewind_button.js +17 -9
  206. package/ui/seek_bar.js +6 -6
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/spacer.js +1 -1
  210. package/ui/statistics_button.js +253 -0
  211. package/ui/text_selection.js +19 -15
  212. package/ui/ui.js +30 -0
  213. package/ui/ui_utils.js +1 -1
  214. package/ui/volume_bar.js +2 -4
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
@@ -217,6 +260,8 @@ goog.requireType('shaka.routing.Payload');
217
260
  * <code>selectAudioLanguage()</code>, or <code>selectTextLanguage()</code>.
218
261
  * @property {string} type
219
262
  * 'adaptation'
263
+ * @property {shaka.extern.Track} oldTrack
264
+ * @property {shaka.extern.Track} newTrack
220
265
  * @exportDoc
221
266
  */
222
267
 
@@ -439,6 +484,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
439
484
  /** @private {shaka.media.RegionTimeline} */
440
485
  this.regionTimeline_ = null;
441
486
 
487
+ /** @private {shaka.util.CmcdManager} */
488
+ this.cmcdManager_ = null;
489
+
490
+ /** @private {shaka.media.QualityObserver} */
491
+ this.qualityObserver_ = null;
492
+
442
493
  /** @private {shaka.media.StreamingEngine} */
443
494
  this.streamingEngine_ = null;
444
495
 
@@ -505,6 +556,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
505
556
  /** @private {!Array.<function():(!Promise|undefined)>} */
506
557
  this.cleanupOnUnload_ = [];
507
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
+
508
568
  if (dependencyInjector) {
509
569
  dependencyInjector(this);
510
570
  }
@@ -721,6 +781,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
721
781
  this.config_ = null;
722
782
  this.stats_ = null;
723
783
  this.videoContainer_ = null;
784
+ this.cmcdManager_ = null;
724
785
 
725
786
  if (this.networkingEngine_) {
726
787
  await this.networkingEngine_.destroy();
@@ -1018,6 +1079,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1018
1079
  return this.wrapWalkerListenersWithPromise_(events);
1019
1080
  }
1020
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
+
1021
1095
  /**
1022
1096
  * Tell the player to load the content at <code>assetUri</code> and start
1023
1097
  * playback at <code>startTime</code>. Before calling <code>load</code>,
@@ -1038,6 +1112,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1038
1112
  * @export
1039
1113
  */
1040
1114
  load(assetUri, startTime, mimeType) {
1115
+ this.updatedStartTime_ = null;
1116
+
1041
1117
  // Do not allow the player to be used after |destroy| is called.
1042
1118
  if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
1043
1119
  return Promise.reject(this.createAbortLoadError_());
@@ -1091,6 +1167,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1091
1167
  // before we allow calls to |updateStateHistory|.
1092
1168
  this.stats_ = new shaka.util.Stats();
1093
1169
 
1170
+ // Create the CMCD manager so client data can be attached to all requests
1171
+ this.cmcdManager_ = this.createCmcd_();
1172
+
1094
1173
  // Load's request is a little different, so we can't use our normal
1095
1174
  // listeners-to-promise method. It is the only request where we may skip the
1096
1175
  // request, so we need to set the on skip callback to reject with a specific
@@ -1142,25 +1221,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1142
1221
  if (!mimeType) {
1143
1222
  // Try using the uri extension.
1144
1223
  const extension = shaka.media.ManifestParser.getExtension(uri);
1145
- mimeType = {
1146
- 'mp4': 'video/mp4',
1147
- 'm4v': 'video/mp4',
1148
- 'm4a': 'audio/mp4',
1149
- 'webm': 'video/webm',
1150
- 'weba': 'audio/webm',
1151
- 'mkv': 'video/webm', // Chromium browsers supports it.
1152
- 'ts': 'video/mp2t',
1153
- 'ogv': 'video/ogg',
1154
- 'ogg': 'audio/ogg',
1155
- 'mpg': 'video/mpeg',
1156
- 'mpeg': 'video/mpeg',
1157
- 'm3u8': 'application/x-mpegurl',
1158
- 'mpd': 'application/dash+xml',
1159
- 'mp3': 'audio/mpeg',
1160
- 'aac': 'audio/aac',
1161
- 'flac': 'audio/flac',
1162
- 'wav': 'audio/wav',
1163
- }[extension];
1224
+ mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
1164
1225
  }
1165
1226
 
1166
1227
  // TODO: The load graph system has a design limitation that requires routing
@@ -1610,6 +1671,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1610
1671
  goog.asserts.assert(
1611
1672
  this.networkingEngine_,
1612
1673
  'Need networking engine to parse manifest.');
1674
+ goog.asserts.assert(
1675
+ this.cmcdManager_,
1676
+ 'Need CMCD manager to populate manifest request data.');
1613
1677
  goog.asserts.assert(
1614
1678
  this.config_,
1615
1679
  'Need player config to parse manifest.');
@@ -1633,12 +1697,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1633
1697
  this.adManager_.onDashTimedMetadata(region);
1634
1698
  }
1635
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
+ }
1636
1708
 
1637
- // TODO (#1391): Once filterManifest_ is async, remove this eslint disable.
1638
- /* eslint-disable require-await */
1639
1709
  const playerInterface = {
1640
1710
  networkingEngine: networkingEngine,
1641
- filter: async (manifest) => this.filterManifest_(manifest),
1711
+ modifyManifestRequest: (request, manifestInfo) => {
1712
+ this.cmcdManager_.applyManifestData(request, manifestInfo);
1713
+ },
1714
+ modifySegmentRequest: (request, segmentInfo) => {
1715
+ this.cmcdManager_.applySegmentData(request, segmentInfo);
1716
+ },
1717
+ filter: (manifest) => this.filterManifest_(manifest),
1642
1718
  makeTextStreamsForClosedCaptions: (manifest) => {
1643
1719
  return this.makeTextStreamsForClosedCaptions_(manifest);
1644
1720
  },
@@ -1656,7 +1732,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1656
1732
  this.configure('streaming.lowLatencyMode', true);
1657
1733
  },
1658
1734
  };
1659
- /* eslint-enable require-await */
1660
1735
 
1661
1736
  const startTime = Date.now() / 1000;
1662
1737
 
@@ -1748,17 +1823,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1748
1823
  },
1749
1824
  });
1750
1825
 
1751
- // TODO: remove once MediaCap implementation is complete.
1752
- if (!this.config_.useMediaCapabilities) {
1753
- shaka.util.StreamUtils.filterManifestByMediaSource(this.manifest_);
1754
- }
1755
-
1756
1826
  this.drmEngine_.configure(this.config_.drm);
1757
1827
 
1758
1828
  await this.drmEngine_.initForPlayback(
1759
1829
  this.manifest_.variants,
1760
- this.manifest_.offlineSessionIds,
1761
- this.config_.useMediaCapabilities);
1830
+ this.manifest_.offlineSessionIds);
1762
1831
 
1763
1832
  await this.drmEngine_.attach(has.mediaElement);
1764
1833
 
@@ -1805,6 +1874,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1805
1874
  // something we are now depending on.
1806
1875
  has.startTime = wants.startTime;
1807
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
+
1808
1884
  // Store a reference to values in |has| after asserting so that closure will
1809
1885
  // know that they will still be non-null between calls to await.
1810
1886
  const mediaElement = has.mediaElement;
@@ -1873,7 +1949,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1873
1949
  // If the content is multi-codec and the browser can play more than one of
1874
1950
  // them, choose codecs now before we initialize streaming.
1875
1951
  shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
1876
- this.manifest_, this.config_.preferredAudioChannelCount);
1952
+ this.manifest_,
1953
+ this.config_.preferredVideoCodecs,
1954
+ this.config_.preferredAudioCodecs,
1955
+ this.config_.preferredAudioChannelCount,
1956
+ this.config_.preferredDecodingAttributes);
1877
1957
 
1878
1958
  this.streamingEngine_ = this.createStreamingEngine();
1879
1959
  this.streamingEngine_.configure(this.config_.streaming);
@@ -1912,7 +1992,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1912
1992
  }
1913
1993
 
1914
1994
  // Since the first streams just became active, send an adaptation event.
1915
- this.onAdaptation_();
1995
+ this.onAdaptation_(null,
1996
+ shaka.util.StreamUtils.variantToTrack(initialVariant));
1916
1997
  }
1917
1998
 
1918
1999
  // Decide if text should be shown automatically.
@@ -2025,6 +2106,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2025
2106
 
2026
2107
  this.drmEngine_.configure(this.config_.drm);
2027
2108
 
2109
+ const uri = wants.uri || '';
2110
+ const extension = shaka.media.ManifestParser.getExtension(uri);
2111
+ let mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
2112
+ if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
2113
+ mimeType = 'application/vnd.apple.mpegurl';
2114
+ }
2115
+ if (!mimeType) {
2116
+ mimeType = 'video/mp4';
2117
+ }
2118
+
2028
2119
  // TODO: Instead of feeding DrmEngine with Variants, we should refactor
2029
2120
  // DrmEngine so that it takes a minimal config derived from Variants. In
2030
2121
  // cases like this one or in removal of stored content, the details are
@@ -2045,7 +2136,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2045
2136
  createSegmentIndex: () => Promise.resolve(),
2046
2137
  segmentIndex: null,
2047
2138
  mimeType: wants.mimeType ?
2048
- shaka.util.MimeUtils.getBasicType(wants.mimeType) : 'video/mp4',
2139
+ shaka.util.MimeUtils.getBasicType(wants.mimeType) : mimeType,
2049
2140
  codecs: wants.mimeType ?
2050
2141
  shaka.util.MimeUtils.getCodecs(wants.mimeType) : '',
2051
2142
  encrypted: true,
@@ -2164,8 +2255,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2164
2255
  }
2165
2256
  if (this.video_.textTracks) {
2166
2257
  this.eventManager_.listen(this.video_.textTracks, 'addtrack', (e) => {
2167
- this.onTracksChanged_();
2168
- this.processTimedMetadataSrcEqls_(/** @type {!TrackEvent} */(e));
2258
+ const trackEvent = /** @type {!TrackEvent} */(e);
2259
+ if (trackEvent.track) {
2260
+ const track = trackEvent.track;
2261
+ goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2262
+ switch (track.kind) {
2263
+ case 'metadata':
2264
+ this.processTimedMetadataSrcEqls_(track);
2265
+ break;
2266
+ case 'chapters':
2267
+ this.processChaptersTrack_(track);
2268
+ break;
2269
+ default:
2270
+ this.onTracksChanged_();
2271
+ break;
2272
+ }
2273
+ }
2169
2274
  });
2170
2275
  this.eventManager_.listen(
2171
2276
  this.video_.textTracks, 'removetrack', () => this.onTracksChanged_());
@@ -2173,9 +2278,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2173
2278
  this.video_.textTracks, 'change', () => this.onTracksChanged_());
2174
2279
  }
2175
2280
 
2281
+ const extension = shaka.media.ManifestParser.getExtension(has.uri);
2282
+ const mimeType =
2283
+ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
2284
+
2176
2285
  // By setting |src| we are done "loading" with src=. We don't need to set
2177
2286
  // the current time because |playhead| will do that for us.
2178
- has.mediaElement.src = has.uri;
2287
+ has.mediaElement.src = this.cmcdManager_.appendSrcData(has.uri, mimeType);
2179
2288
 
2180
2289
  // Tizen 3 / WebOS won't load anything unless you call load() explicitly,
2181
2290
  // no matter the value of the preload attribute. This is harmful on some
@@ -2338,13 +2447,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2338
2447
  * We're looking for metadata tracks to process id3 tags. One of the uses is
2339
2448
  * for ad info on LIVE streams
2340
2449
  *
2341
- * @param {!TrackEvent} event
2450
+ * @param {!TextTrack} track
2342
2451
  * @private
2343
2452
  */
2344
- processTimedMetadataSrcEqls_(event) {
2345
- const track = event.track;
2346
- goog.asserts.assert(track instanceof TextTrack, 'Wrong track type!');
2347
-
2453
+ processTimedMetadataSrcEqls_(track) {
2348
2454
  if (track.kind != 'metadata') {
2349
2455
  return;
2350
2456
  }
@@ -2424,6 +2530,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2424
2530
  this.dispatchEvent(this.makeEvent_(eventName, data));
2425
2531
  }
2426
2532
 
2533
+ /**
2534
+ * We're looking for chapters tracks to process the chapters.
2535
+ *
2536
+ * @param {?TextTrack} track
2537
+ * @private
2538
+ */
2539
+ processChaptersTrack_(track) {
2540
+ if (!track || track.kind != 'chapters') {
2541
+ return;
2542
+ }
2543
+
2544
+ // Hidden mode is required for the cuechange event to launch correctly and
2545
+ // get the cues and the activeCues
2546
+ track.mode = 'hidden';
2547
+
2548
+ // In Safari the initial assignment does not always work, so we schedule
2549
+ // this process to be repeated several times to ensure that it has been put
2550
+ // in the correct mode.
2551
+ new shaka.util.Timer(() => {
2552
+ const chaptersTracks = this.getChaptersTracks_();
2553
+ for (const chaptersTrack of chaptersTracks) {
2554
+ chaptersTrack.mode = 'hidden';
2555
+ }
2556
+ }).tickNow().tickAfter(/* seconds= */ 0.5);
2557
+ }
2558
+
2427
2559
  /**
2428
2560
  * Take a series of variants and ensure that they only contain one type of
2429
2561
  * variant. The different options are:
@@ -2482,7 +2614,30 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2482
2614
  this.abrManager_.segmentDownloaded(deltaTimeMs, bytesDownloaded);
2483
2615
  }
2484
2616
  };
2485
- return new shaka.net.NetworkingEngine(onProgressUpdated_);
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
+ };
2638
+
2639
+ return new shaka.net.NetworkingEngine(
2640
+ onProgressUpdated_, onHeadersReceived_, onDownloadFailed_);
2486
2641
  }
2487
2642
 
2488
2643
  /**
@@ -2538,7 +2693,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2538
2693
  // Now that we have all our observers, create a manager for them.
2539
2694
  const manager = new shaka.media.PlayheadObserverManager(this.video_);
2540
2695
  manager.manage(regionObserver);
2541
-
2696
+ if (this.qualityObserver_) {
2697
+ manager.manage(this.qualityObserver_);
2698
+ }
2542
2699
  return manager;
2543
2700
  }
2544
2701
 
@@ -2654,6 +2811,27 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2654
2811
  mediaElement, closedCaptionsParser, textDisplayer, onMetadata);
2655
2812
  }
2656
2813
 
2814
+ /**
2815
+ * Create a new CMCD manager.
2816
+ *
2817
+ * @private
2818
+ */
2819
+ createCmcd_() {
2820
+ const abr = this.abrManager_;
2821
+
2822
+ /** @type {shaka.util.CmcdManager.PlayerInterface} */
2823
+ const playerInterface = {
2824
+ getBandwidthEstimate: () => abr ? abr.getBandwidthEstimate() : NaN,
2825
+ getBufferedInfo: () => this.getBufferedInfo(),
2826
+ getCurrentTime: () => this.video_ ? this.video_.currentTime : 0,
2827
+ getManifest: () => this.manifest_,
2828
+ getPlaybackRate: () => this.getPlaybackRate(),
2829
+ isLive: () => this.isLive(),
2830
+ };
2831
+
2832
+ return new shaka.util.CmcdManager(playerInterface, this.config_.cmcd);
2833
+ }
2834
+
2657
2835
  /**
2658
2836
  * Creates a new instance of StreamingEngine. This can be replaced by tests
2659
2837
  * to create fake instances instead.
@@ -2663,19 +2841,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2663
2841
  createStreamingEngine() {
2664
2842
  goog.asserts.assert(
2665
2843
  this.playhead_ && this.abrManager_ && this.mediaSourceEngine_ &&
2666
- this.manifest_,
2844
+ this.cmcdManager_ && this.manifest_,
2667
2845
  'Must not be destroyed');
2668
2846
 
2669
2847
  /** @type {shaka.media.StreamingEngine.PlayerInterface} */
2670
2848
  const playerInterface = {
2671
2849
  getPresentationTime: () => this.playhead_.getTime(),
2672
2850
  getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
2851
+ modifySegmentRequest: (request, segmentInfo) => {
2852
+ this.cmcdManager_.applySegmentData(request, segmentInfo);
2853
+ },
2673
2854
  mediaSourceEngine: this.mediaSourceEngine_,
2674
2855
  netEngine: this.networkingEngine_,
2675
2856
  onError: (error) => this.onError_(error),
2676
2857
  onEvent: (event) => this.dispatchEvent(event),
2677
2858
  onManifestUpdate: () => this.onManifestUpdate_(),
2678
2859
  onSegmentAppended: () => this.onSegmentAppended_(),
2860
+ onInitSegmentAppended: (position, initSegment) => {
2861
+ const mediaQuality = initSegment.getMediaQuality();
2862
+ if (mediaQuality && this.qualityObserver_) {
2863
+ this.qualityObserver_.addMediaQualityChange(mediaQuality, position);
2864
+ }
2865
+ },
2679
2866
  };
2680
2867
 
2681
2868
  return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
@@ -2868,6 +3055,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2868
3055
  return this.config_;
2869
3056
  }
2870
3057
 
3058
+ /**
3059
+ * Returns the ratio of video length buffered compared to buffering Goal
3060
+ * @return {number}
3061
+ * @export
3062
+ */
3063
+ getBufferFullness() {
3064
+ if (this.video_) {
3065
+ const bufferedLength = this.video_.buffered.length;
3066
+ const bufferedEnd =
3067
+ bufferedLength ? this.video_.buffered.end(bufferedLength - 1) : 0;
3068
+ const bufferingGoal = this.getConfiguration().streaming.bufferingGoal;
3069
+ const lengthToBeBuffered = Math.min(this.video_.currentTime +
3070
+ bufferingGoal, this.seekRange().end);
3071
+
3072
+ if (bufferedEnd >= lengthToBeBuffered) {
3073
+ return 1;
3074
+ } else if (bufferedEnd <= this.video_.currentTime) {
3075
+ return 0;
3076
+ } else if (bufferedEnd < lengthToBeBuffered) {
3077
+ return ((bufferedEnd - this.video_.currentTime) /
3078
+ (lengthToBeBuffered - this.video_.currentTime));
3079
+ }
3080
+ }
3081
+ return 0;
3082
+ }
3083
+
2871
3084
  /**
2872
3085
  * Reset configuration to default.
2873
3086
  * @export
@@ -3078,6 +3291,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3078
3291
  return {'start': 0, 'end': 0};
3079
3292
  }
3080
3293
 
3294
+ /**
3295
+ * Go to live in a live stream.
3296
+ *
3297
+ * @export
3298
+ */
3299
+ goToLive() {
3300
+ if (this.isLive()) {
3301
+ this.video_.currentTime = this.seekRange().end;
3302
+ } else {
3303
+ shaka.log.warning('goToLive is for live streams!');
3304
+ }
3305
+ }
3306
+
3081
3307
  /**
3082
3308
  * Get the key system currently used by EME. If EME is not being used, this
3083
3309
  * will return an empty string. If the player has not loaded content, this
@@ -3102,6 +3328,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3102
3328
  return this.drmEngine_ ? this.drmEngine_.getDrmInfo() : null;
3103
3329
  }
3104
3330
 
3331
+
3332
+ /**
3333
+ * Get the drm engine.
3334
+ * This method should only be used for testing. Applications SHOULD NOT
3335
+ * use this in production.
3336
+ *
3337
+ * @return {?shaka.media.DrmEngine}
3338
+ */
3339
+ getDrmEngine() {
3340
+ return this.drmEngine_;
3341
+ }
3342
+
3343
+
3105
3344
  /**
3106
3345
  * Get the next known expiration time for any EME session. If the session
3107
3346
  * never expires, this will return <code>Infinity</code>. If there are no EME
@@ -3356,9 +3595,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3356
3595
  return null;
3357
3596
  }
3358
3597
  const reference = imageStream.segmentIndex.get(referencePosition);
3598
+ const tilesLayout =
3599
+ reference.getTilesLayout() || imageStream.tilesLayout;
3359
3600
  // This expression is used to detect one or more numbers (0-9) followed
3360
3601
  // by an x and after one or more numbers (0-9)
3361
- const match = /(\d+)x(\d+)/.exec(imageStream.tilesLayout);
3602
+ const match = /(\d+)x(\d+)/.exec(tilesLayout);
3362
3603
  if (!match) {
3363
3604
  shaka.log.warning('Tiles layout does not contain a valid format ' +
3364
3605
  ' (columns x rows)');
@@ -3598,36 +3839,34 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3598
3839
  new shaka.media.PreferenceBasedCriteria(language, role || '',
3599
3840
  /* channelCount= */ 0, /* label= */ '');
3600
3841
 
3601
- if (!this.config_.abr.enabled) {
3602
- const diff = (a, b) => {
3603
- if (!a.video && !b.video) {
3604
- return 0;
3605
- } else if (!a.video || !b.video) {
3606
- return Infinity;
3607
- } else {
3608
- return Math.abs((a.video.height || 0) - (b.video.height || 0)) +
3842
+ const diff = (a, b) => {
3843
+ if (!a.video && !b.video) {
3844
+ return 0;
3845
+ } else if (!a.video || !b.video) {
3846
+ return Infinity;
3847
+ } else {
3848
+ return Math.abs((a.video.height || 0) - (b.video.height || 0)) +
3609
3849
  Math.abs((a.video.width || 0) - (b.video.width || 0));
3610
- }
3611
- };
3850
+ }
3851
+ };
3612
3852
  // Find the variant whose size is closest to the active variant. This
3613
3853
  // ensures we stay at about the same resolution when just changing the
3614
3854
  // language/role.
3615
- const active = this.streamingEngine_.getCurrentVariant();
3616
- const set =
3855
+ const active = this.streamingEngine_.getCurrentVariant();
3856
+ const set =
3617
3857
  this.currentAdaptationSetCriteria_.create(this.manifest_.variants);
3618
- let bestVariant = null;
3619
- for (const curVariant of set.values()) {
3620
- if (!bestVariant ||
3858
+ let bestVariant = null;
3859
+ for (const curVariant of set.values()) {
3860
+ if (!bestVariant ||
3621
3861
  diff(bestVariant, active) > diff(curVariant, active)) {
3622
- bestVariant = curVariant;
3623
- }
3624
- }
3625
- if (bestVariant) {
3626
- const track = shaka.util.StreamUtils.variantToTrack(bestVariant);
3627
- this.selectVariantTrack(track, /* clearBuffer= */ true);
3628
- return;
3862
+ bestVariant = curVariant;
3629
3863
  }
3630
3864
  }
3865
+ if (bestVariant) {
3866
+ const track = shaka.util.StreamUtils.variantToTrack(bestVariant);
3867
+ this.selectVariantTrack(track, /* clearBuffer= */ true);
3868
+ return;
3869
+ }
3631
3870
 
3632
3871
  // If we haven't switched yet, just use ABR to find a new track.
3633
3872
  this.chooseVariantAndSwitch_();
@@ -3771,6 +4010,51 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3771
4010
  return expected;
3772
4011
  }
3773
4012
 
4013
+ /**
4014
+ * Return a list of chapters tracks.
4015
+ *
4016
+ * @return {!Array.<shaka.extern.Track>}
4017
+ * @export
4018
+ */
4019
+ getChaptersTracks() {
4020
+ if (this.video_ && this.video_.src && this.video_.textTracks) {
4021
+ const textTracks = this.getChaptersTracks_();
4022
+ const StreamUtils = shaka.util.StreamUtils;
4023
+ return textTracks.map((text) => StreamUtils.html5TextTrackToTrack(text));
4024
+ } else {
4025
+ return [];
4026
+ }
4027
+ }
4028
+
4029
+ /**
4030
+ * This returns the list of chapters.
4031
+ *
4032
+ * @param {string} language
4033
+ * @return {!Array.<shaka.extern.Chapter>}
4034
+ * @export
4035
+ */
4036
+ getChapters(language) {
4037
+ const LanguageUtils = shaka.util.LanguageUtils;
4038
+ const inputlanguage = LanguageUtils.normalize(language);
4039
+ const chaptersTracks = this.getChaptersTracks_();
4040
+ const chaptersTrack = chaptersTracks
4041
+ .find((t) => LanguageUtils.normalize(t.language) == inputlanguage);
4042
+ if (!chaptersTrack || !chaptersTrack.cues) {
4043
+ return [];
4044
+ }
4045
+ const chapters = [];
4046
+ for (const cue of chaptersTrack.cues) {
4047
+ /** @type {shaka.extern.Chapter} */
4048
+ const chapter = {
4049
+ title: cue.text,
4050
+ startTime: cue.startTime,
4051
+ endTime: cue.endTime,
4052
+ };
4053
+ chapters.push(chapter);
4054
+ }
4055
+ return chapters;
4056
+ }
4057
+
3774
4058
  /**
3775
4059
  * Ignore the TextTracks with the 'metadata' or 'chapters' kind, or the one
3776
4060
  * generated by the SimpleTextDisplayer.
@@ -3799,6 +4083,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3799
4083
  .filter((t) => t.kind == 'metadata');
3800
4084
  }
3801
4085
 
4086
+ /**
4087
+ * Get the TextTracks with the 'chapters' kind.
4088
+ *
4089
+ * @return {!Array.<TextTrack>}
4090
+ * @private
4091
+ */
4092
+ getChaptersTracks_() {
4093
+ goog.asserts.assert(this.video_.textTracks,
4094
+ 'TextTracks should be valid.');
4095
+ return Array.from(this.video_.textTracks)
4096
+ .filter((t) => t.kind == 'chapters');
4097
+ }
4098
+
3802
4099
  /**
3803
4100
  * Enable or disable the text displayer. If the player is in an unloaded
3804
4101
  * state, the request will be applied next time content is loaded.
@@ -4100,16 +4397,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4100
4397
  if (!mimeType) {
4101
4398
  // Try using the uri extension.
4102
4399
  const extension = shaka.media.ManifestParser.getExtension(uri);
4103
- mimeType = {
4104
- 'sbv': 'text/x-subviewer',
4105
- 'srt': 'text/srt',
4106
- 'vtt': 'text/vtt',
4107
- 'webvtt': 'text/vtt',
4108
- 'ttml': 'application/ttml+xml',
4109
- 'lrc': 'application/x-subtitle-lrc',
4110
- 'ssa': 'text/x-ssa',
4111
- 'ass': 'text/x-ssa',
4112
- }[extension];
4400
+ mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
4113
4401
 
4114
4402
  if (!mimeType) {
4115
4403
  shaka.log.error(
@@ -4138,7 +4426,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4138
4426
  }
4139
4427
  const trackElement =
4140
4428
  /** @type {!HTMLTrackElement} */(document.createElement('track'));
4141
- trackElement.src = uri;
4429
+ trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4142
4430
  trackElement.label = label || '';
4143
4431
  trackElement.kind = kind;
4144
4432
  trackElement.srclang = language;
@@ -4257,74 +4545,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4257
4545
  }
4258
4546
 
4259
4547
  if (!mimeType) {
4260
- // Try using the uri extension.
4261
- const extension = shaka.media.ManifestParser.getExtension(uri);
4262
- mimeType = {
4263
- 'sbv': 'text/x-subviewer',
4264
- 'srt': 'text/srt',
4265
- 'vtt': 'text/vtt',
4266
- 'webvtt': 'text/vtt',
4267
- 'ttml': 'application/ttml+xml',
4268
- 'lrc': 'application/x-subtitle-lrc',
4269
- 'ssa': 'text/x-ssa',
4270
- 'ass': 'text/x-ssa',
4271
- }[extension];
4272
-
4273
- if (!mimeType) {
4274
- try {
4275
- goog.asserts.assert(
4276
- this.networkingEngine_, 'Need networking engine.');
4277
- // eslint-disable-next-line require-atomic-updates
4278
- mimeType = await shaka.media.ManifestParser.getMimeType(uri,
4279
- this.networkingEngine_,
4280
- this.config_.streaming.retryParameters);
4281
- } catch (error) {}
4282
- }
4548
+ mimeType = await this.getTextMimetype_(uri);
4549
+ }
4283
4550
 
4284
- if (!mimeType) {
4285
- shaka.log.error(
4286
- 'The mimeType has not been provided and it could not be deduced ' +
4287
- 'from its extension.');
4288
- throw new shaka.util.Error(
4289
- shaka.util.Error.Severity.RECOVERABLE,
4290
- shaka.util.Error.Category.TEXT,
4291
- shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
4292
- extension);
4293
- }
4551
+ let adCuePoints = [];
4552
+ if (this.adManager_) {
4553
+ try {
4554
+ adCuePoints = this.adManager_.getServerSideCuePoints();
4555
+ } catch (error) {}
4294
4556
  }
4295
4557
 
4296
4558
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4297
- if (mimeType != 'text/vtt') {
4298
- goog.asserts.assert(
4299
- this.networkingEngine_, 'Need networking engine.');
4300
- const data = await this.getTextData_(uri,
4301
- this.networkingEngine_,
4302
- this.config_.streaming.retryParameters);
4303
- const vvtText = this.convertToWebVTT_(data, mimeType);
4304
- const blob = new Blob([vvtText], {type: 'text/vtt'});
4305
- uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4306
- mimeType = 'text/vtt';
4307
- }
4308
4559
  if (forced) {
4309
4560
  // See: https://github.com/whatwg/html/issues/4472
4310
4561
  kind = 'forced';
4311
4562
  }
4312
- const trackElement =
4313
- /** @type {!HTMLTrackElement} */(document.createElement('track'));
4314
- trackElement.src = uri;
4315
- trackElement.label = label || '';
4316
- trackElement.kind = kind;
4317
- trackElement.srclang = language;
4318
- // Because we're pulling in the text track file via Javascript, the
4319
- // same-origin policy applies. If you'd like to have a player served
4320
- // from one domain, but the text track served from another, you'll
4321
- // need to enable CORS in order to do so. In addition to enabling CORS
4322
- // on the server serving the text tracks, you will need to add the
4323
- // crossorigin attribute to the video element itself.
4324
- if (!this.video_.getAttribute('crossorigin')) {
4325
- this.video_.setAttribute('crossorigin', 'anonymous');
4326
- }
4327
- this.video_.appendChild(trackElement);
4563
+ await this.addSrcTrackElement_(uri, language, kind, mimeType, label || '',
4564
+ adCuePoints);
4328
4565
  const textTracks = this.getTextTracks();
4329
4566
  const srcTrack = textTracks.find((t) => {
4330
4567
  return t.language == language &&
@@ -4354,6 +4591,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4354
4591
  shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM);
4355
4592
  }
4356
4593
 
4594
+ if (adCuePoints.length) {
4595
+ goog.asserts.assert(
4596
+ this.networkingEngine_, 'Need networking engine.');
4597
+ const data = await this.getTextData_(uri,
4598
+ this.networkingEngine_,
4599
+ this.config_.streaming.retryParameters);
4600
+ const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints);
4601
+ const blob = new Blob([vvtText], {type: 'text/vtt'});
4602
+ uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4603
+ mimeType = 'text/vtt';
4604
+ }
4605
+
4357
4606
  /** @type {shaka.extern.Stream} */
4358
4607
  const stream = {
4359
4608
  id: this.nextExternalStreamId_++,
@@ -4399,6 +4648,138 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4399
4648
  return shaka.util.StreamUtils.textStreamToTrack(stream);
4400
4649
  }
4401
4650
 
4651
+ /**
4652
+ * Adds the given chapters track to the loaded manifest. <code>load()</code>
4653
+ * must resolve before calling. The presentation must have a duration.
4654
+ *
4655
+ * This returns the created track.
4656
+ *
4657
+ * @param {string} uri
4658
+ * @param {string} language
4659
+ * @param {string=} mimeType
4660
+ * @return {!Promise.<shaka.extern.Track>}
4661
+ * @export
4662
+ */
4663
+ async addChaptersTrack(uri, language, mimeType) {
4664
+ if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE &&
4665
+ this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) {
4666
+ shaka.log.error(
4667
+ 'Must call load() and wait for it to resolve before adding ' +
4668
+ 'chapters tracks.');
4669
+ throw new shaka.util.Error(
4670
+ shaka.util.Error.Severity.RECOVERABLE,
4671
+ shaka.util.Error.Category.PLAYER,
4672
+ shaka.util.Error.Code.CONTENT_NOT_LOADED);
4673
+ }
4674
+
4675
+ if (!mimeType) {
4676
+ mimeType = await this.getTextMimetype_(uri);
4677
+ }
4678
+ let adCuePoints = [];
4679
+ if (this.adManager_) {
4680
+ try {
4681
+ adCuePoints = this.adManager_.getServerSideCuePoints();
4682
+ } catch (error) {}
4683
+ }
4684
+ await this.addSrcTrackElement_(uri, language, /* kind= */ 'chapters',
4685
+ mimeType, /* label= */ '', adCuePoints);
4686
+ const chaptersTracks = this.getChaptersTracks();
4687
+ const chaptersTrack = chaptersTracks.find((t) => {
4688
+ return t.language == language;
4689
+ });
4690
+ if (chaptersTrack) {
4691
+ const html5ChaptersTracks = this.getChaptersTracks_();
4692
+ for (const html5ChaptersTrack of html5ChaptersTracks) {
4693
+ this.processChaptersTrack_(html5ChaptersTrack);
4694
+ }
4695
+ return chaptersTrack;
4696
+ }
4697
+ // This should not happen, but there are browser implementations that may
4698
+ // not support the Track element.
4699
+ shaka.log.error('Cannot add this text when loaded with src=');
4700
+ throw new shaka.util.Error(
4701
+ shaka.util.Error.Severity.RECOVERABLE,
4702
+ shaka.util.Error.Category.TEXT,
4703
+ shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_SRC_EQUALS);
4704
+ }
4705
+
4706
+ /**
4707
+ * @param {string} uri
4708
+ * @return {!Promise.<string>}
4709
+ * @private
4710
+ */
4711
+ async getTextMimetype_(uri) {
4712
+ // Try using the uri extension.
4713
+ const extension = shaka.media.ManifestParser.getExtension(uri);
4714
+ let mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
4715
+
4716
+ if (mimeType) {
4717
+ return mimeType;
4718
+ }
4719
+
4720
+ try {
4721
+ goog.asserts.assert(
4722
+ this.networkingEngine_, 'Need networking engine.');
4723
+ // eslint-disable-next-line require-atomic-updates
4724
+ mimeType = await shaka.media.ManifestParser.getMimeType(uri,
4725
+ this.networkingEngine_,
4726
+ this.config_.streaming.retryParameters);
4727
+ } catch (error) {}
4728
+
4729
+ if (mimeType) {
4730
+ return mimeType;
4731
+ }
4732
+
4733
+ shaka.log.error(
4734
+ 'The mimeType has not been provided and it could not be deduced ' +
4735
+ 'from its extension.');
4736
+ throw new shaka.util.Error(
4737
+ shaka.util.Error.Severity.RECOVERABLE,
4738
+ shaka.util.Error.Category.TEXT,
4739
+ shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
4740
+ extension);
4741
+ }
4742
+
4743
+ /**
4744
+ * @param {string} uri
4745
+ * @param {string} language
4746
+ * @param {string} kind
4747
+ * @param {string} mimeType
4748
+ * @param {string} label
4749
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4750
+ * @private
4751
+ */
4752
+ async addSrcTrackElement_(uri, language, kind, mimeType, label,
4753
+ adCuePoints) {
4754
+ if (mimeType != 'text/vtt' || adCuePoints.length) {
4755
+ goog.asserts.assert(
4756
+ this.networkingEngine_, 'Need networking engine.');
4757
+ const data = await this.getTextData_(uri,
4758
+ this.networkingEngine_,
4759
+ this.config_.streaming.retryParameters);
4760
+ const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints);
4761
+ const blob = new Blob([vvtText], {type: 'text/vtt'});
4762
+ uri = shaka.media.MediaSourceEngine.createObjectURL(blob);
4763
+ mimeType = 'text/vtt';
4764
+ }
4765
+ const trackElement =
4766
+ /** @type {!HTMLTrackElement} */(document.createElement('track'));
4767
+ trackElement.src = this.cmcdManager_.appendTextTrackData(uri);
4768
+ trackElement.label = label;
4769
+ trackElement.kind = kind;
4770
+ trackElement.srclang = language;
4771
+ // Because we're pulling in the text track file via Javascript, the
4772
+ // same-origin policy applies. If you'd like to have a player served
4773
+ // from one domain, but the text track served from another, you'll
4774
+ // need to enable CORS in order to do so. In addition to enabling CORS
4775
+ // on the server serving the text tracks, you will need to add the
4776
+ // crossorigin attribute to the video element itself.
4777
+ if (!this.video_.getAttribute('crossorigin')) {
4778
+ this.video_.setAttribute('crossorigin', 'anonymous');
4779
+ }
4780
+ this.video_.appendChild(trackElement);
4781
+ }
4782
+
4402
4783
  /**
4403
4784
  * @param {string} uri
4404
4785
  * @param {!shaka.net.NetworkingEngine} netEngine
@@ -4412,6 +4793,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4412
4793
  const request = shaka.net.NetworkingEngine.makeRequest([uri], retryParams);
4413
4794
  request.method = 'GET';
4414
4795
 
4796
+ this.cmcdManager_.applyTextData(request);
4797
+
4415
4798
  const response = await netEngine.request(type, request).promise;
4416
4799
 
4417
4800
  return response.data;
@@ -4423,10 +4806,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4423
4806
  *
4424
4807
  * @param {BufferSource} buffer
4425
4808
  * @param {string} mimeType
4809
+ * @param {!Array.<!shaka.extern.AdCuePoint>} adCuePoints
4426
4810
  * @return {string}
4427
4811
  * @private
4428
4812
  */
4429
- convertToWebVTT_(buffer, mimeType) {
4813
+ convertToWebVTT_(buffer, mimeType, adCuePoints) {
4430
4814
  const factory = shaka.text.TextEngine.findParser(mimeType);
4431
4815
  if (factory) {
4432
4816
  const obj = factory();
@@ -4437,7 +4821,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4437
4821
  };
4438
4822
  const data = shaka.util.BufferUtils.toUint8(buffer);
4439
4823
  const cues = obj.parseMedia(data, time);
4440
- return shaka.text.WebVttGenerator.convert(cues);
4824
+ return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
4441
4825
  }
4442
4826
  throw new shaka.util.Error(
4443
4827
  shaka.util.Error.Severity.CRITICAL,
@@ -4679,8 +5063,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4679
5063
  this.streamingEngine_.getCurrentVariant() : null;
4680
5064
 
4681
5065
  await shaka.util.StreamUtils.filterManifest(
4682
- this.drmEngine_, currentVariant, manifest,
4683
- this.config_.useMediaCapabilities);
5066
+ this.drmEngine_, currentVariant, manifest);
4684
5067
  this.checkPlayableVariants_(manifest);
4685
5068
  }
4686
5069
 
@@ -4809,6 +5192,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4809
5192
 
4810
5193
  if (loaded) {
4811
5194
  this.playRateController_.setBuffering(isBuffering);
5195
+ if (this.cmcdManager_) {
5196
+ this.cmcdManager_.setBuffering(isBuffering);
5197
+ }
4812
5198
  this.updateStateHistory_();
4813
5199
  }
4814
5200
 
@@ -4984,9 +5370,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4984
5370
  if (chosenVariant) {
4985
5371
  this.switchVariant_(chosenVariant, /* fromAdaptation= */ true,
4986
5372
  /* clearBuffers= */ true, /* safeMargin= */ 0);
4987
- // Send an adaptation event so that the UI can show the new
4988
- // language/tracks.
4989
- this.onAdaptation_();
4990
5373
  }
4991
5374
  }
4992
5375
 
@@ -5017,8 +5400,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5017
5400
  oldTrack = shaka.util.StreamUtils.variantToTrack(currentVariant);
5018
5401
  }
5019
5402
  const newTrack = shaka.util.StreamUtils.variantToTrack(variant);
5020
- // Dispatch a 'variantchanged' event
5021
- this.onVariantChanged_(oldTrack, newTrack);
5403
+
5404
+ if (fromAdaptation) {
5405
+ // Dispatch an 'adaptation' event
5406
+ this.onAdaptation_(oldTrack, newTrack);
5407
+ } else {
5408
+ // Dispatch a 'variantchanged' event
5409
+ this.onVariantChanged_(oldTrack, newTrack);
5410
+ }
5022
5411
  }
5023
5412
 
5024
5413
  /**
@@ -5162,19 +5551,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5162
5551
  return;
5163
5552
  }
5164
5553
 
5165
- this.addVariantToSwitchHistory_(variant, /* fromAdaptation= */ true);
5166
- this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
5167
- this.onAdaptation_();
5554
+ this.switchVariant_(variant, /* fromAdaptation= */ true,
5555
+ clearBuffer, safeMargin);
5168
5556
  }
5169
5557
 
5170
5558
  /**
5171
5559
  * Dispatches an 'adaptation' event.
5560
+ * @param {?shaka.extern.Track} from
5561
+ * @param {shaka.extern.Track} to
5172
5562
  * @private
5173
5563
  */
5174
- onAdaptation_() {
5564
+ onAdaptation_(from, to) {
5175
5565
  // Delay the 'adaptation' event so that StreamingEngine has time to absorb
5176
5566
  // the changes before the user tries to query it.
5177
- const event = this.makeEvent_(shaka.Player.EventName.Adaptation);
5567
+ const data = new Map()
5568
+ .set('oldTrack', from)
5569
+ .set('newTrack', to);
5570
+ const event = this.makeEvent_(shaka.Player.EventName.Adaptation, data);
5178
5571
  this.delayDispatchEvent_(event);
5179
5572
  }
5180
5573
 
@@ -5276,6 +5669,38 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5276
5669
  this.dispatchEvent(this.makeEvent_(eventName, data));
5277
5670
  }
5278
5671
 
5672
+ /**
5673
+ * When notified of a media quality change we need to emit a
5674
+ * MediaQualityChange event to the app.
5675
+ *
5676
+ * @param {shaka.extern.MediaQualityInfo} mediaQuality
5677
+ * @param {number} position
5678
+ *
5679
+ * @private
5680
+ */
5681
+ onMediaQualityChange_(mediaQuality, position) {
5682
+ // Always make a copy to avoid exposing our internal data to the app.
5683
+ const clone = {
5684
+ bandwidth: mediaQuality.bandwidth,
5685
+ audioSamplingRate: mediaQuality.audioSamplingRate,
5686
+ codecs: mediaQuality.codecs,
5687
+ contentType: mediaQuality.contentType,
5688
+ frameRate: mediaQuality.frameRate,
5689
+ height: mediaQuality.height,
5690
+ mimeType: mediaQuality.mimeType,
5691
+ channelsCount: mediaQuality.channelsCount,
5692
+ pixelAspectRatio: mediaQuality.pixelAspectRatio,
5693
+ width: mediaQuality.width,
5694
+ };
5695
+
5696
+ const data = new Map()
5697
+ .set('mediaQuality', clone)
5698
+ .set('position', position);
5699
+
5700
+ this.dispatchEvent(this.makeEvent_(
5701
+ shaka.Player.EventName.MediaQualityChanged, data));
5702
+ }
5703
+
5279
5704
  /**
5280
5705
  * Turn the media element's error object into a Shaka Player error object.
5281
5706
  *
@@ -6039,6 +6464,8 @@ shaka.Player.EventName = {
6039
6464
  AbrStatusChanged: 'abrstatuschanged',
6040
6465
  Adaptation: 'adaptation',
6041
6466
  Buffering: 'buffering',
6467
+ DownloadFailed: 'downloadfailed',
6468
+ DownloadHeadersReceived: 'downloadheadersreceived',
6042
6469
  DrmSessionUpdate: 'drmsessionupdate',
6043
6470
  Emsg: 'emsg',
6044
6471
  Error: 'error',
@@ -6047,6 +6474,7 @@ shaka.Player.EventName = {
6047
6474
  Loaded: 'loaded',
6048
6475
  Loading: 'loading',
6049
6476
  ManifestParsed: 'manifestparsed',
6477
+ MediaQualityChanged: 'mediaqualitychanged',
6050
6478
  Metadata: 'metadata',
6051
6479
  OnStateChange: 'onstatechange',
6052
6480
  OnStateIdle: 'onstateidle',
@@ -6102,7 +6530,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6102
6530
  * @define {string} A version number taken from git at compile time.
6103
6531
  * @export
6104
6532
  */
6105
- shaka.Player.version = 'v3.1.4-uncompiled';
6533
+ shaka.Player.version = 'v3.3.0-uncompiled';
6106
6534
 
6107
6535
  // Initialize the deprecation system using the version string we just set
6108
6536
  // on the player.
@@ -6129,6 +6557,47 @@ shaka.Player.supportPlugins_ = {};
6129
6557
  shaka.Player.adManagerFactory_ = null;
6130
6558
 
6131
6559
 
6560
+ /**
6561
+ * @const {!Object.<string, string>}
6562
+ * @private
6563
+ */
6564
+ shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
6565
+ 'mp4': 'video/mp4',
6566
+ 'm4v': 'video/mp4',
6567
+ 'm4a': 'audio/mp4',
6568
+ 'webm': 'video/webm',
6569
+ 'weba': 'audio/webm',
6570
+ 'mkv': 'video/webm', // Chromium browsers supports it.
6571
+ 'ts': 'video/mp2t',
6572
+ 'ogv': 'video/ogg',
6573
+ 'ogg': 'audio/ogg',
6574
+ 'mpg': 'video/mpeg',
6575
+ 'mpeg': 'video/mpeg',
6576
+ 'm3u8': 'application/x-mpegurl',
6577
+ 'mpd': 'application/dash+xml',
6578
+ 'mp3': 'audio/mpeg',
6579
+ 'aac': 'audio/aac',
6580
+ 'flac': 'audio/flac',
6581
+ 'wav': 'audio/wav',
6582
+ };
6583
+
6584
+
6585
+ /**
6586
+ * @const {!Object.<string, string>}
6587
+ * @private
6588
+ */
6589
+ shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
6590
+ 'sbv': 'text/x-subviewer',
6591
+ 'srt': 'text/srt',
6592
+ 'vtt': 'text/vtt',
6593
+ 'webvtt': 'text/vtt',
6594
+ 'ttml': 'application/ttml+xml',
6595
+ 'lrc': 'application/x-subtitle-lrc',
6596
+ 'ssa': 'text/x-ssa',
6597
+ 'ass': 'text/x-ssa',
6598
+ };
6599
+
6600
+
6132
6601
  /**
6133
6602
  * @const {string}
6134
6603
  */