shaka-player 3.2.1 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +9 -5
  5. package/CHANGELOG.md +358 -0
  6. package/CONTRIBUTORS +7 -2
  7. package/build/conformance.textproto +183 -169
  8. package/build/generateExterns.js +1 -1
  9. package/build/types/core +3 -0
  10. package/build/types/offline +1 -0
  11. package/build/types/polyfill +1 -0
  12. package/build/types/ui +2 -0
  13. package/demo/common/assets.js +19 -1
  14. package/demo/common/message_ids.js +13 -1
  15. package/demo/config.js +31 -1
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +12 -0
  18. package/demo/locales/source.json +48 -0
  19. package/dist/controls.css +26 -2
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +20 -17
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +20 -17
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +19 -12
  26. package/dist/locales.js +39 -37
  27. package/dist/receiver.compiled.debug.js +12 -10
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +12 -10
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +231 -18
  32. package/dist/shaka-player.compiled.debug.d.ts +231 -18
  33. package/dist/shaka-player.compiled.debug.externs.js +279 -8
  34. package/dist/shaka-player.compiled.debug.js +512 -449
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +279 -8
  37. package/dist/shaka-player.compiled.js +854 -821
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +247 -19
  40. package/dist/shaka-player.ui.debug.d.ts +247 -19
  41. package/dist/shaka-player.ui.debug.externs.js +307 -9
  42. package/dist/shaka-player.ui.debug.js +662 -580
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +307 -9
  45. package/dist/shaka-player.ui.js +1047 -1000
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/bg-fetch-after.gv +29 -0
  48. package/docs/design/bg-fetch-after.gv.png +0 -0
  49. package/docs/design/bg-fetch-before.gv +18 -0
  50. package/docs/design/bg-fetch-before.gv.png +0 -0
  51. package/docs/design/bg-fetch.md +134 -0
  52. package/docs/tutorials/blob-url.md +15 -0
  53. package/docs/tutorials/fairplay.md +1 -1
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/cmcd.js +186 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +5 -0
  60. package/externs/shaka/manifest.js +12 -1
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +31 -2
  64. package/externs/shaka/player.js +138 -5
  65. package/externs/shaka/text.js +24 -0
  66. package/externs/webcrypto.js +18 -0
  67. package/karma.conf.js +1 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/client_side_ad.js +8 -0
  72. package/lib/ads/client_side_ad_manager.js +54 -90
  73. package/lib/ads/server_side_ad.js +7 -0
  74. package/lib/ads/server_side_ad_manager.js +7 -10
  75. package/lib/cast/cast_proxy.js +3 -2
  76. package/lib/cast/cast_receiver.js +2 -1
  77. package/lib/cast/cast_sender.js +2 -2
  78. package/lib/cast/cast_utils.js +1 -0
  79. package/lib/dash/dash_parser.js +141 -16
  80. package/lib/dash/segment_base.js +25 -1
  81. package/lib/dash/segment_list.js +19 -10
  82. package/lib/dash/segment_template.js +24 -11
  83. package/lib/hls/hls_parser.js +146 -40
  84. package/lib/media/drm_engine.js +7 -0
  85. package/lib/media/gap_jumping_controller.js +4 -2
  86. package/lib/media/media_source_engine.js +1 -2
  87. package/lib/media/quality_observer.js +291 -0
  88. package/lib/media/segment_index.js +8 -1
  89. package/lib/media/segment_reference.js +37 -4
  90. package/lib/media/streaming_engine.js +49 -7
  91. package/lib/net/http_fetch_plugin.js +31 -11
  92. package/lib/net/http_plugin_utils.js +1 -0
  93. package/lib/net/http_xhr_plugin.js +40 -14
  94. package/lib/net/networking_engine.js +83 -19
  95. package/lib/offline/download_info.js +68 -0
  96. package/lib/offline/download_manager.js +13 -5
  97. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  98. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  99. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  100. package/lib/offline/storage.js +393 -284
  101. package/lib/offline/stored_content_utils.js +2 -0
  102. package/lib/player.js +249 -43
  103. package/lib/polyfill/media_capabilities.js +36 -15
  104. package/lib/polyfill/mediasource.js +6 -0
  105. package/lib/polyfill/orientation.js +1 -1
  106. package/lib/polyfill/patchedmediakeys_apple.js +4 -4
  107. package/lib/polyfill/patchedmediakeys_ms.js +4 -4
  108. package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
  109. package/lib/polyfill/random_uuid.js +52 -0
  110. package/lib/text/cue.js +18 -0
  111. package/lib/text/mp4_ttml_parser.js +3 -0
  112. package/lib/text/text_engine.js +14 -2
  113. package/lib/text/ttml_text_parser.js +114 -60
  114. package/lib/text/ui_text_displayer.js +95 -41
  115. package/lib/util/cmcd_manager.js +672 -0
  116. package/lib/util/error.js +6 -0
  117. package/lib/util/fake_event.js +53 -7
  118. package/lib/util/language_utils.js +12 -0
  119. package/lib/util/mutex.js +52 -0
  120. package/lib/util/periods.js +102 -65
  121. package/lib/util/platform.js +13 -1
  122. package/lib/util/player_configuration.js +18 -0
  123. package/lib/util/stream_utils.js +99 -10
  124. package/lib/util/xml_utils.js +19 -0
  125. package/package.json +2 -2
  126. package/shaka-player.uncompiled.js +3 -0
  127. package/test/cast/cast_proxy_unit.js +12 -6
  128. package/test/cast/cast_utils_unit.js +1 -1
  129. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  130. package/test/dash/dash_parser_live_unit.js +101 -1
  131. package/test/dash/dash_parser_manifest_unit.js +222 -2
  132. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  133. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  134. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  135. package/test/hls/hls_live_unit.js +2 -0
  136. package/test/hls/hls_parser_unit.js +7 -1
  137. package/test/media/closed_caption_parser_unit.js +6 -1
  138. package/test/media/drm_engine_unit.js +47 -3
  139. package/test/media/quality_observer_unit.js +139 -0
  140. package/test/media/segment_reference_unit.js +16 -1
  141. package/test/media/streaming_engine_integration.js +4 -2
  142. package/test/media/streaming_engine_unit.js +147 -0
  143. package/test/net/http_plugin_unit.js +20 -15
  144. package/test/offline/storage_integration.js +108 -4
  145. package/test/player_integration.js +178 -8
  146. package/test/player_src_equals_integration.js +41 -8
  147. package/test/player_unit.js +19 -9
  148. package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
  149. package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
  150. package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
  151. package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
  152. package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
  153. package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
  154. package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
  155. package/test/test/assets/3675/dash_0.mpd +30 -0
  156. package/test/test/assets/3675/dash_1.mpd +30 -0
  157. package/test/test/assets/3675/dash_2.mpd +30 -0
  158. package/test/test/assets/3675/dash_3.mpd +30 -0
  159. package/test/test/assets/3675/dash_4.mpd +30 -0
  160. package/test/test/assets/3675/dash_5.mpd +26 -0
  161. package/test/test/assets/3675/init-stream0.m4s +0 -0
  162. package/test/test/assets/chapters.vtt +2 -2
  163. package/test/test/assets/chapters2.vtt +10 -0
  164. package/test/test/util/dash_parser_util.js +4 -0
  165. package/test/test/util/fake_ad.js +7 -0
  166. package/test/test/util/fake_ad_manager.js +1 -1
  167. package/test/test/util/fake_drm_engine.js +9 -2
  168. package/test/test/util/manifest_generator.js +3 -2
  169. package/test/test/util/test_scheme.js +14 -1
  170. package/test/test/util/ttml_utils.js +81 -0
  171. package/test/text/mp4_ttml_parser_unit.js +11 -18
  172. package/test/text/ttml_text_parser_unit.js +552 -227
  173. package/test/text/ui_text_displayer_unit.js +29 -17
  174. package/test/ui/localization_unit.js +15 -14
  175. package/test/ui/text_displayer_layout_unit.js +13 -1
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +291 -0
  178. package/test/util/stream_utils_unit.js +45 -4
  179. package/third_party/closure-uri/uri.js +21 -0
  180. package/ui/airplay_button.js +1 -0
  181. package/ui/audio_language_selection.js +7 -0
  182. package/ui/cast_button.js +3 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +45 -13
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +14 -5
  189. package/ui/fullscreen_button.js +1 -0
  190. package/ui/less/buttons.less +0 -4
  191. package/ui/less/containers.less +77 -7
  192. package/ui/less/overflow_menu.less +0 -10
  193. package/ui/less/tooltip.less +97 -0
  194. package/ui/locales/en.json +1 -0
  195. package/ui/locales/ru.json +6 -6
  196. package/ui/locales/source.json +4 -0
  197. package/ui/localization.js +28 -51
  198. package/ui/loop_button.js +5 -0
  199. package/ui/mute_button.js +2 -1
  200. package/ui/overflow_menu.js +5 -25
  201. package/ui/pip_button.js +7 -3
  202. package/ui/play_button.js +2 -2
  203. package/ui/playback_rate_selection.js +8 -9
  204. package/ui/resolution_selection.js +7 -0
  205. package/ui/rewind_button.js +15 -6
  206. package/ui/seek_bar.js +4 -2
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/statistics_button.js +253 -0
  210. package/ui/text_selection.js +12 -4
  211. package/ui/ui.js +30 -0
  212. package/ui/volume_bar.js +1 -1
  213. package/externs/media_capabilities.js +0 -16
@@ -25,6 +25,7 @@ goog.require('shaka.net.DataUriPlugin');
25
25
  goog.require('shaka.net.NetworkingEngine');
26
26
  goog.require('shaka.util.ArrayUtils');
27
27
  goog.require('shaka.util.BufferUtils');
28
+ goog.require('shaka.util.CmcdManager');
28
29
  goog.require('shaka.util.DataViewReader');
29
30
  goog.require('shaka.util.Error');
30
31
  goog.require('shaka.util.FakeEvent');
@@ -332,7 +333,8 @@ shaka.hls.HlsParser = class {
332
333
  const segments = await this.createSegments_(
333
334
  streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
334
335
  stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
335
- streamInfo.discontinuityToMediaSequence);
336
+ streamInfo.discontinuityToMediaSequence, stream.codecs,
337
+ stream.bandwidth);
336
338
 
337
339
  stream.segmentIndex.mergeAndEvict(
338
340
  segments, this.presentationTimeline_.getSegmentAvailabilityStart());
@@ -418,18 +420,16 @@ shaka.hls.HlsParser = class {
418
420
  const uri = tag.getAttributeValue('URI');
419
421
  const language = tag.getAttributeValue('LANGUAGE');
420
422
  const value = tag.getAttributeValue('VALUE');
421
- const data = {
422
- 'id': id,
423
- };
423
+ const data = (new Map()).set('id', id);
424
424
  if (uri) {
425
- data.uri =
426
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri);
425
+ data.set('uri',
426
+ shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
427
427
  }
428
428
  if (language) {
429
- data.language = language;
429
+ data.set('language', language);
430
430
  }
431
431
  if (value) {
432
- data.value = value;
432
+ data.set('value', value);
433
433
  }
434
434
  const event = new shaka.util.FakeEvent('sessiondata', data);
435
435
  if (this.playerInterface_) {
@@ -534,6 +534,30 @@ shaka.hls.HlsParser = class {
534
534
  }
535
535
  }
536
536
 
537
+ // Now that the content has been fit, notify segments.
538
+ this.segmentsToNotifyByStream_ = [];
539
+ const streamsToNotify = [];
540
+ for (const variant of variants) {
541
+ for (const stream of [variant.video, variant.audio]) {
542
+ if (stream) {
543
+ streamsToNotify.push(stream);
544
+ }
545
+ }
546
+ }
547
+ await Promise.all(streamsToNotify.map(async (stream) => {
548
+ await stream.createSegmentIndex();
549
+ }));
550
+ for (const stream of streamsToNotify) {
551
+ this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
552
+ }
553
+ this.notifySegments_();
554
+
555
+ // This asserts that the live edge is being calculated from segment times.
556
+ // For VOD and event streams, this check should still pass.
557
+ goog.asserts.assert(
558
+ !this.presentationTimeline_.usingPresentationStartTime(),
559
+ 'We should not be using the presentation start time in HLS!');
560
+
537
561
  this.manifest_ = {
538
562
  presentationTimeline: this.presentationTimeline_,
539
563
  variants,
@@ -741,7 +765,7 @@ shaka.hls.HlsParser = class {
741
765
  const videoRange = tag.getAttributeValue('VIDEO-RANGE');
742
766
 
743
767
  const streamInfos = await this.createStreamInfosForVariantTag_(tag,
744
- resolution, frameRate);
768
+ resolution, frameRate, bandwidth);
745
769
 
746
770
  if (streamInfos) {
747
771
  goog.asserts.assert(streamInfos.audio.length ||
@@ -775,10 +799,11 @@ shaka.hls.HlsParser = class {
775
799
  * @param {!shaka.hls.Tag} tag
776
800
  * @param {?string} resolution
777
801
  * @param {?string} frameRate
802
+ * @param {number} bandwidth
778
803
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
779
804
  * @private
780
805
  */
781
- async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
806
+ async createStreamInfosForVariantTag_(tag, resolution, frameRate, bandwidth) {
782
807
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
783
808
  /** @type {!Array.<string>} */
784
809
  let allCodecs = this.getCodecsForVariantTag_(tag);
@@ -848,7 +873,8 @@ shaka.hls.HlsParser = class {
848
873
  let streamInfo;
849
874
  if (!ignoreStream) {
850
875
  streamInfo =
851
- await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
876
+ await this.createStreamInfoFromVariantTag_(tag, allCodecs, type,
877
+ bandwidth);
852
878
  }
853
879
  if (streamInfo) {
854
880
  res[streamInfo.stream.type] = [streamInfo];
@@ -871,15 +897,12 @@ shaka.hls.HlsParser = class {
871
897
  */
872
898
  getCodecsForVariantTag_(tag) {
873
899
  // These are the default codecs to assume if none are specified.
874
- // The video codec is H.264, with baseline profile and level 3.0.
875
- // http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
876
- // The audio codec is "low-complexity" AAC.
877
900
  const defaultCodecsArray = [];
878
901
  if (!this.config_.disableVideo) {
879
- defaultCodecsArray.push('avc1.42E01E');
902
+ defaultCodecsArray.push(this.config_.hls.defaultVideoCodec);
880
903
  }
881
904
  if (!this.config_.disableAudio) {
882
- defaultCodecsArray.push('mp4a.40.2');
905
+ defaultCodecsArray.push(this.config_.hls.defaultAudioCodec);
883
906
  }
884
907
  const defaultCodecs = defaultCodecsArray.join(',');
885
908
 
@@ -1184,7 +1207,7 @@ shaka.hls.HlsParser = class {
1184
1207
  // "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
1185
1208
  // only be selected explicitly by the user, and we don't have a way to
1186
1209
  // represent that in our model.
1187
- const defaultAttrValue = tag.getAttribute('DEFAULT');
1210
+ const defaultAttrValue = tag.getAttributeValue('DEFAULT');
1188
1211
  const primary = defaultAttrValue == 'YES';
1189
1212
 
1190
1213
  const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
@@ -1258,6 +1281,30 @@ shaka.hls.HlsParser = class {
1258
1281
  if (this.uriToStreamInfosMap_.has(verbatimImagePlaylistUri)) {
1259
1282
  return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1260
1283
  }
1284
+
1285
+ // Parse misc attributes.
1286
+ const resolution = tag.getAttributeValue('RESOLUTION');
1287
+ if (resolution) {
1288
+ // The RESOLUTION tag represents the resolution of a single thumbnail, not
1289
+ // of the entire sheet at once (like we expect in the output).
1290
+ // So multiply by the layout size.
1291
+
1292
+ const reference = streamInfo.stream.segmentIndex.get(0);
1293
+ const layout = reference.getTilesLayout();
1294
+ if (layout) {
1295
+ streamInfo.stream.width =
1296
+ Number(resolution.split('x')[0]) * Number(layout.split('x')[0]);
1297
+ streamInfo.stream.height =
1298
+ Number(resolution.split('x')[1]) * Number(layout.split('x')[1]);
1299
+ // TODO: What happens if there are multiple grids, with different
1300
+ // layout sizes, inside this image stream?
1301
+ }
1302
+ }
1303
+ const bandwidth = tag.getAttributeValue('BANDWIDTH');
1304
+ if (bandwidth) {
1305
+ streamInfo.stream.bandwidth = Number(bandwidth);
1306
+ }
1307
+
1261
1308
  this.uriToStreamInfosMap_.set(verbatimImagePlaylistUri, streamInfo);
1262
1309
  return streamInfo;
1263
1310
  }
@@ -1268,10 +1315,11 @@ shaka.hls.HlsParser = class {
1268
1315
  * @param {!shaka.hls.Tag} tag
1269
1316
  * @param {!Array.<string>} allCodecs
1270
1317
  * @param {string} type
1318
+ * @param {number} bandwidth
1271
1319
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1272
1320
  * @private
1273
1321
  */
1274
- async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
1322
+ async createStreamInfoFromVariantTag_(tag, allCodecs, type, bandwidth) {
1275
1323
  goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
1276
1324
  'Should only be called on variant tags!');
1277
1325
  const verbatimMediaPlaylistUri = this.variableSubstitution_(
@@ -1287,7 +1335,7 @@ shaka.hls.HlsParser = class {
1287
1335
  codecs, type, /* language= */ 'und', /* primary= */ false,
1288
1336
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1289
1337
  /* characteristics= */ null, /* forced= */ false,
1290
- /* spatialAudio= */ false);
1338
+ /* spatialAudio= */ false, bandwidth);
1291
1339
  if (streamInfo == null) {
1292
1340
  return null;
1293
1341
  }
@@ -1314,12 +1362,13 @@ shaka.hls.HlsParser = class {
1314
1362
  * @param {?string} characteristics
1315
1363
  * @param {boolean} forced
1316
1364
  * @param {boolean} spatialAudio
1365
+ * @param {(number|undefined)} bandwidth
1317
1366
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1318
1367
  * @private
1319
1368
  */
1320
1369
  async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1321
1370
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1322
- spatialAudio) {
1371
+ spatialAudio, bandwidth = undefined) {
1323
1372
  // TODO: Refactor, too many parameters
1324
1373
  let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1325
1374
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1437,7 +1486,7 @@ shaka.hls.HlsParser = class {
1437
1486
  try {
1438
1487
  segments = await this.createSegments_(verbatimMediaPlaylistUri,
1439
1488
  playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
1440
- discontinuityToMediaSequence);
1489
+ discontinuityToMediaSequence, codecs, bandwidth);
1441
1490
  } catch (error) {
1442
1491
  if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
1443
1492
  shaka.log.alwaysWarn('Skipping unsupported HLS stream',
@@ -1617,12 +1666,6 @@ shaka.hls.HlsParser = class {
1617
1666
  }
1618
1667
 
1619
1668
  this.notifySegments_();
1620
-
1621
- // This asserts that the live edge is being calculated from segment times.
1622
- // For VOD and event streams, this check should still pass.
1623
- goog.asserts.assert(
1624
- !this.presentationTimeline_.usingPresentationStartTime(),
1625
- 'We should not be using the presentation start time in HLS!');
1626
1669
  }
1627
1670
 
1628
1671
  /**
@@ -1795,6 +1838,7 @@ shaka.hls.HlsParser = class {
1795
1838
  }
1796
1839
 
1797
1840
  let tilesLayout = '';
1841
+ let tileDuration = null;
1798
1842
  if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
1799
1843
  // By default in HLS the tilesLayout is 1x1
1800
1844
  tilesLayout = '1x1';
@@ -1802,6 +1846,10 @@ shaka.hls.HlsParser = class {
1802
1846
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-TILES');
1803
1847
  if (tilesTag) {
1804
1848
  tilesLayout = tilesTag.getRequiredAttrValue('LAYOUT');
1849
+ const duration = tilesTag.getAttributeValue('DURATION');
1850
+ if (duration) {
1851
+ tileDuration = Number(duration);
1852
+ }
1805
1853
  }
1806
1854
  }
1807
1855
 
@@ -1817,6 +1865,7 @@ shaka.hls.HlsParser = class {
1817
1865
  /* appendWindowEnd= */ Infinity,
1818
1866
  partialSegmentRefs,
1819
1867
  tilesLayout,
1868
+ tileDuration,
1820
1869
  );
1821
1870
  }
1822
1871
 
@@ -1871,11 +1920,14 @@ shaka.hls.HlsParser = class {
1871
1920
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1872
1921
  * @param {!Map.<string, string>} variables
1873
1922
  * @param {!Map.<number, number>} discontinuityToMediaSequence
1923
+ * @param {string} codecs
1924
+ * @param {(number|undefined)} bandwidth
1874
1925
  * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
1875
1926
  * @private
1876
1927
  */
1877
1928
  async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1878
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence) {
1929
+ mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1930
+ codecs, bandwidth) {
1879
1931
  /** @type {Array.<!shaka.hls.Segment>} */
1880
1932
  const hlsSegments = playlist.segments;
1881
1933
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1918,7 +1970,7 @@ shaka.hls.HlsParser = class {
1918
1970
  this.playlistStartTime_ = await this.getStartTime_(
1919
1971
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1920
1972
  position, /* isDiscontinuity= */ false,
1921
- hlsSegments[0], variables, type);
1973
+ hlsSegments[0], variables, type, codecs, bandwidth);
1922
1974
  }
1923
1975
  firstStartTime = this.playlistStartTime_;
1924
1976
  }
@@ -1971,7 +2023,8 @@ shaka.hls.HlsParser = class {
1971
2023
  // eslint-disable-next-line no-await-in-loop
1972
2024
  timestampOffset = await this.getTimestampOffset_(
1973
2025
  discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
1974
- mimeType, position, item, variables, startTime, type);
2026
+ mimeType, position, item, variables, startTime, type, codecs,
2027
+ bandwidth);
1975
2028
  }
1976
2029
 
1977
2030
  // If the stream is low latency and the user has not configured the
@@ -2009,9 +2062,6 @@ shaka.hls.HlsParser = class {
2009
2062
  }
2010
2063
  }
2011
2064
 
2012
- this.segmentsToNotifyByStream_.push(references);
2013
- this.notifySegments_();
2014
-
2015
2065
  return references;
2016
2066
  }
2017
2067
 
@@ -2028,13 +2078,16 @@ shaka.hls.HlsParser = class {
2028
2078
  * @param {!Map.<string, string>} variables
2029
2079
  * @param {number} startTime
2030
2080
  * @param {string} type
2081
+ * @param {string} codecs
2082
+ * @param {number|undefined} bandwidth
2031
2083
  * @return {!Promise.<number>}
2032
2084
  * @throws {shaka.util.Error}
2033
2085
  * @private
2034
2086
  */
2035
2087
  async getTimestampOffset_(discontintuitySequenceNum,
2036
2088
  verbatimMediaPlaylistUri, initSegmentRef,
2037
- mimeType, mediaSequenceNumber, segment, variables, startTime, type) {
2089
+ mimeType, mediaSequenceNumber, segment, variables, startTime, type,
2090
+ codecs, bandwidth) {
2038
2091
  let timestampOffset = 0;
2039
2092
  if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
2040
2093
  timestampOffset =
@@ -2043,7 +2096,7 @@ shaka.hls.HlsParser = class {
2043
2096
  const mediaStartTime = await this.getStartTime_(
2044
2097
  verbatimMediaPlaylistUri, initSegmentRef, mimeType,
2045
2098
  mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
2046
- variables, type);
2099
+ variables, type, codecs, bandwidth);
2047
2100
  timestampOffset = startTime - mediaStartTime;
2048
2101
  shaka.log.v1('Segment timestampOffset =', timestampOffset);
2049
2102
  this.discontinuityToTso_.set(
@@ -2057,10 +2110,14 @@ shaka.hls.HlsParser = class {
2057
2110
  * segment if we have to.
2058
2111
  *
2059
2112
  * @param {!shaka.media.AnySegmentReference} reference
2113
+ * @param {string} type
2114
+ * @param {string} mimeType
2115
+ * @param {string} codecs
2116
+ * @param {number|undefined} bandwidth
2060
2117
  * @return {!Promise.<shaka.extern.Response>}
2061
2118
  * @private
2062
2119
  */
2063
- async fetchStartOfSegment_(reference) {
2120
+ async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
2064
2121
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2065
2122
 
2066
2123
  // Create two requests:
@@ -2074,6 +2131,20 @@ shaka.hls.HlsParser = class {
2074
2131
  reference.endByte,
2075
2132
  this.config_.retryParameters);
2076
2133
 
2134
+ // We can only add partial CMCD data here because the stream
2135
+ // and manifest objects are still being created
2136
+ this.playerInterface_.modifySegmentRequest(
2137
+ fullRequest,
2138
+ {
2139
+ type: type,
2140
+ init: reference instanceof shaka.media.InitSegmentReference,
2141
+ duration: reference.endTime - reference.startTime,
2142
+ mimeType: mimeType,
2143
+ codecs: codecs,
2144
+ bandwidth: bandwidth,
2145
+ },
2146
+ );
2147
+
2077
2148
  if (this.config_.hls.useFullSegmentsForStartTime) {
2078
2149
  return this.makeNetworkRequest_(fullRequest, requestType);
2079
2150
  }
@@ -2084,6 +2155,18 @@ shaka.hls.HlsParser = class {
2084
2155
  reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
2085
2156
  this.config_.retryParameters);
2086
2157
 
2158
+ this.playerInterface_.modifySegmentRequest(
2159
+ partialRequest,
2160
+ {
2161
+ type: type,
2162
+ init: reference instanceof shaka.media.InitSegmentReference,
2163
+ duration: reference.endTime - reference.startTime,
2164
+ mimeType: mimeType,
2165
+ codecs: codecs,
2166
+ bandwidth: bandwidth,
2167
+ },
2168
+ );
2169
+
2087
2170
  // TODO(vaage): The need to do fall back requests is not likely to be unique
2088
2171
  // to here. It would be nice if the fallback(s) could be included into
2089
2172
  // the same abortable operation as the original request.
@@ -2131,12 +2214,14 @@ shaka.hls.HlsParser = class {
2131
2214
  * @param {!shaka.hls.Segment} segment
2132
2215
  * @param {!Map.<string, string>} variables
2133
2216
  * @param {string} type
2217
+ * @param {string} codecs
2218
+ * @param {number|undefined} bandwidth
2134
2219
  * @return {!Promise.<number>}
2135
2220
  * @private
2136
2221
  */
2137
2222
  async getStartTime_(
2138
2223
  verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2139
- isDiscontinuity, segment, variables, type) {
2224
+ isDiscontinuity, segment, variables, type, codecs, bandwidth) {
2140
2225
  const segmentRef = this.createSegmentReference_(
2141
2226
  initSegmentRef,
2142
2227
  /* previousReference= */ null,
@@ -2204,10 +2289,22 @@ shaka.hls.HlsParser = class {
2204
2289
  if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
2205
2290
  // We also need the init segment to get the correct timescale. But if the
2206
2291
  // stream is self-initializing, use the same response for both.
2207
- const fetches = [this.fetchStartOfSegment_(segmentRef)];
2292
+ const fetches = [this.fetchStartOfSegment_(
2293
+ segmentRef,
2294
+ type,
2295
+ mimeType,
2296
+ codecs,
2297
+ bandwidth,
2298
+ )];
2208
2299
 
2209
2300
  if (initSegmentRef) {
2210
- fetches.push(this.fetchStartOfSegment_(initSegmentRef));
2301
+ fetches.push(this.fetchStartOfSegment_(
2302
+ initSegmentRef,
2303
+ type,
2304
+ mimeType,
2305
+ codecs,
2306
+ bandwidth,
2307
+ ));
2211
2308
  }
2212
2309
 
2213
2310
  const responses = await Promise.all(fetches);
@@ -2223,7 +2320,13 @@ shaka.hls.HlsParser = class {
2223
2320
  }
2224
2321
 
2225
2322
  if (mimeType == 'video/mp2t') {
2226
- const response = await this.fetchStartOfSegment_(segmentRef);
2323
+ const response = await this.fetchStartOfSegment_(
2324
+ segmentRef,
2325
+ type,
2326
+ mimeType,
2327
+ codecs,
2328
+ bandwidth,
2329
+ );
2227
2330
  goog.asserts.assert(response.data, 'Should have a response body!');
2228
2331
  return this.getStartTimeFromTsSegment_(
2229
2332
  verbatimMediaPlaylistUri, response.uri, response.data);
@@ -2601,6 +2704,9 @@ shaka.hls.HlsParser = class {
2601
2704
  const request = shaka.net.NetworkingEngine.makeRequest(
2602
2705
  [absoluteUri], this.config_.retryParameters);
2603
2706
 
2707
+ const format = shaka.util.CmcdManager.StreamingFormat.HLS;
2708
+ this.playerInterface_.modifyManifestRequest(request, {format: format});
2709
+
2604
2710
  return this.makeNetworkRequest_(request, requestType);
2605
2711
  }
2606
2712
 
@@ -1825,9 +1825,16 @@ shaka.media.DrmEngine = class {
1825
1825
  // audio adaptations, so we shouldn't have to worry about checking
1826
1826
  // robustness.
1827
1827
  if (drm1.keySystem == drm2.keySystem) {
1828
+ /** @type {Array<shaka.extern.InitDataOverride>} */
1828
1829
  let initData = [];
1829
1830
  initData = initData.concat(drm1.initData || []);
1830
1831
  initData = initData.concat(drm2.initData || []);
1832
+ initData = initData.filter((d, i) => {
1833
+ return d.keyId === undefined || i === initData.findIndex((d2) => {
1834
+ return d2.keyId === d.keyId;
1835
+ });
1836
+ });
1837
+
1831
1838
  const keyIds = drm1.keyIds && drm2.keyIds ?
1832
1839
  new Set([...drm1.keyIds, ...drm2.keyIds]) :
1833
1840
  drm1.keyIds || drm2.keyIds;
@@ -217,8 +217,10 @@ shaka.media.GapJumpingController = class {
217
217
  this.didFireLargeGap_ = true;
218
218
 
219
219
  // Event firing is synchronous.
220
- const event = new shaka.util.FakeEvent(
221
- 'largegap', {'currentTime': currentTime, 'gapSize': jumpSize});
220
+ const data = new Map()
221
+ .set('currentTime', currentTime)
222
+ .set('gapSize', jumpSize);
223
+ const event = new shaka.util.FakeEvent('largegap', data);
222
224
  event.cancelable = true;
223
225
  this.onEvent_(event);
224
226
 
@@ -48,8 +48,7 @@ shaka.media.MediaSourceEngine = class {
48
48
  * @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)=}
49
49
  * onMetadata
50
50
  */
51
- constructor(video, closedCaptionParser, textDisplayer,
52
- onMetadata) {
51
+ constructor(video, closedCaptionParser, textDisplayer, onMetadata) {
53
52
  /** @private {HTMLMediaElement} */
54
53
  this.video_ = video;
55
54