shaka-player 4.5.0 → 4.6.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/AUTHORS +2 -0
  3. package/CHANGELOG.md +98 -0
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTORS +2 -0
  6. package/README.md +38 -32
  7. package/demo/common/asset.js +11 -17
  8. package/demo/common/assets.js +117 -11
  9. package/demo/config.js +28 -1
  10. package/demo/custom.js +52 -38
  11. package/demo/main.js +11 -3
  12. package/demo/search.js +7 -0
  13. package/dist/controls.css +1 -1
  14. package/dist/controls.css.map +1 -1
  15. package/dist/demo.compiled.debug.js +133 -124
  16. package/dist/demo.compiled.debug.map +3 -3
  17. package/dist/demo.compiled.js +133 -124
  18. package/dist/demo.compiled.map +3 -3
  19. package/dist/deps.js +20 -18
  20. package/dist/locales.js +35 -31
  21. package/dist/receiver.compiled.debug.js +58 -50
  22. package/dist/receiver.compiled.debug.map +3 -3
  23. package/dist/receiver.compiled.js +58 -50
  24. package/dist/receiver.compiled.map +3 -3
  25. package/dist/shaka-player.compiled.d.ts +440 -390
  26. package/dist/shaka-player.compiled.debug.d.ts +440 -390
  27. package/dist/shaka-player.compiled.debug.externs.js +162 -77
  28. package/dist/shaka-player.compiled.debug.js +786 -736
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +162 -77
  31. package/dist/shaka-player.compiled.js +1154 -1121
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +491 -437
  34. package/dist/shaka-player.ui.debug.d.ts +491 -437
  35. package/dist/shaka-player.ui.debug.externs.js +167 -77
  36. package/dist/shaka-player.ui.debug.js +877 -811
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +167 -77
  39. package/dist/shaka-player.ui.js +1362 -1318
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/basic-usage.md +2 -1
  43. package/docs/tutorials/errors.md +2 -1
  44. package/docs/tutorials/offline.md +10 -8
  45. package/docs/tutorials/plugins.md +5 -0
  46. package/docs/tutorials/ui-customization.md +13 -0
  47. package/docs/tutorials/ui.md +2 -1
  48. package/docs/tutorials/upgrade.md +3 -0
  49. package/externs/managedmediasource.js +24 -0
  50. package/externs/shaka/abr_manager.js +6 -2
  51. package/externs/shaka/cea.js +7 -0
  52. package/externs/shaka/codecs.js +5 -3
  53. package/externs/shaka/manifest.js +21 -5
  54. package/externs/shaka/manifest_parser.js +17 -0
  55. package/externs/shaka/net.js +2 -2
  56. package/externs/shaka/offline.js +9 -2
  57. package/externs/shaka/player.js +53 -7
  58. package/externs/shaka/text.js +3 -1
  59. package/externs/shaka/transmuxer.js +1 -1
  60. package/lib/abr/simple_abr_manager.js +25 -8
  61. package/lib/ads/media_tailor_ad_manager.js +14 -3
  62. package/lib/cast/cast_receiver.js +1 -1
  63. package/lib/cea/cea_decoder.js +28 -0
  64. package/lib/cea/dummy_caption_decoder.js +5 -0
  65. package/lib/dash/dash_parser.js +249 -83
  66. package/lib/dash/mpd_utils.js +52 -31
  67. package/lib/dash/segment_base.js +4 -5
  68. package/lib/dash/segment_list.js +5 -4
  69. package/lib/dash/segment_template.js +83 -26
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +761 -649
  72. package/lib/hls/hls_utils.js +64 -5
  73. package/lib/hls/manifest_text_parser.js +7 -11
  74. package/lib/media/adaptation_set.js +20 -13
  75. package/lib/media/adaptation_set_criteria.js +70 -18
  76. package/lib/media/closed_caption_parser.js +13 -0
  77. package/lib/media/content_workarounds.js +6 -0
  78. package/lib/media/drm_engine.js +8 -13
  79. package/lib/media/manifest_parser.js +8 -113
  80. package/lib/media/media_source_capabilities.js +10 -3
  81. package/lib/media/media_source_engine.js +75 -21
  82. package/lib/media/play_rate_controller.js +8 -38
  83. package/lib/media/presentation_timeline.js +7 -1
  84. package/lib/media/quality_observer.js +1 -1
  85. package/lib/media/segment_index.js +5 -5
  86. package/lib/media/segment_prefetch.js +92 -24
  87. package/lib/media/segment_reference.js +67 -1
  88. package/lib/media/segment_utils.js +343 -0
  89. package/lib/media/stall_detector.js +0 -3
  90. package/lib/media/streaming_engine.js +41 -21
  91. package/lib/mss/mss_parser.js +18 -2
  92. package/lib/net/networking_engine.js +66 -12
  93. package/lib/net/networking_utils.js +95 -0
  94. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  95. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  96. package/lib/offline/manifest_converter.js +2 -0
  97. package/lib/offline/offline_manifest_parser.js +10 -0
  98. package/lib/offline/storage.js +11 -7
  99. package/lib/player.js +993 -1502
  100. package/lib/polyfill/mediasource.js +22 -1
  101. package/lib/text/mp4_ttml_parser.js +2 -2
  102. package/lib/text/srt_text_parser.js +2 -2
  103. package/lib/text/stub_text_displayer.js +51 -0
  104. package/lib/text/text_engine.js +3 -2
  105. package/lib/text/ttml_text_parser.js +28 -5
  106. package/lib/text/ui_text_displayer.js +4 -0
  107. package/lib/text/vtt_text_parser.js +7 -2
  108. package/lib/transmuxer/aac_transmuxer.js +1 -1
  109. package/lib/transmuxer/ac3_transmuxer.js +1 -1
  110. package/lib/transmuxer/ec3_transmuxer.js +1 -1
  111. package/lib/transmuxer/h264.js +5 -4
  112. package/lib/transmuxer/h265.js +5 -4
  113. package/lib/transmuxer/mp3_transmuxer.js +1 -1
  114. package/lib/transmuxer/mpeg_ts_transmuxer.js +1 -1
  115. package/lib/transmuxer/mss_transmuxer.js +1 -1
  116. package/lib/transmuxer/ts_transmuxer.js +33 -13
  117. package/lib/util/buffer_utils.js +26 -4
  118. package/lib/util/content_steering_manager.js +358 -0
  119. package/lib/util/error.js +2 -6
  120. package/lib/util/fairplay_utils.js +1 -1
  121. package/lib/util/fake_event.js +5 -1
  122. package/lib/util/id3_utils.js +50 -1
  123. package/lib/util/manifest_parser_utils.js +12 -8
  124. package/lib/util/mp4_generator.js +2 -4
  125. package/lib/util/mutex.js +59 -0
  126. package/lib/util/object_utils.js +1 -1
  127. package/lib/util/periods.js +154 -179
  128. package/lib/util/platform.js +4 -3
  129. package/lib/util/player_configuration.js +35 -6
  130. package/lib/util/stream_utils.js +105 -98
  131. package/lib/util/ts_parser.js +124 -73
  132. package/lib/util/uint8array_utils.js +5 -1
  133. package/lib/util/xml_utils.js +5 -0
  134. package/maintained-branches.md +2 -2
  135. package/package.json +1 -1
  136. package/roadmap.md +15 -5
  137. package/shaka-player.uncompiled.js +0 -1
  138. package/test/abr/simple_abr_manager_unit.js +33 -33
  139. package/test/ads/ad_manager_unit.js +3 -2
  140. package/test/cast/cast_receiver_integration.js +3 -4
  141. package/test/cast/cast_utils_unit.js +2 -0
  142. package/test/cea/cea_decoder_unit.js +7 -0
  143. package/test/codec_switching/codec_switching_integration.js +2 -1
  144. package/test/dash/dash_parser_integration.js +2 -1
  145. package/test/dash/dash_parser_live_unit.js +0 -19
  146. package/test/dash/dash_parser_manifest_unit.js +494 -2
  147. package/test/dash/mpd_utils_unit.js +61 -30
  148. package/test/hls/hls_live_unit.js +12 -3
  149. package/test/hls/hls_parser_unit.js +377 -27
  150. package/test/hls/manifest_text_parser_unit.js +32 -99
  151. package/test/media/adaptation_set_criteria_unit.js +172 -21
  152. package/test/media/adaptation_set_unit.js +1 -0
  153. package/test/media/content_workarounds_unit.js +1 -1
  154. package/test/media/drm_engine_unit.js +3 -3
  155. package/test/media/media_source_engine_unit.js +1 -1
  156. package/test/media/play_rate_controller_unit.js +0 -48
  157. package/test/media/segment_index_unit.js +2 -1
  158. package/test/mss/mss_player_integration.js +3 -2
  159. package/test/net/networking_engine_unit.js +3 -3
  160. package/test/{media/manifest_parser_unit.js → net/networking_utils_unit.js} +2 -2
  161. package/test/offline/manifest_convert_unit.js +9 -0
  162. package/test/offline/offline_integration.js +2 -1
  163. package/test/offline/storage_integration.js +21 -9
  164. package/test/player_external.js +4 -2
  165. package/test/player_integration.js +15 -4
  166. package/test/player_load_graph_integration.js +47 -151
  167. package/test/player_src_equals_external.js +1 -0
  168. package/test/player_src_equals_integration.js +71 -4
  169. package/test/player_unit.js +80 -27
  170. package/test/test/util/fake_media_source_engine.js +5 -0
  171. package/test/test/util/fake_networking_engine.js +9 -1
  172. package/test/test/util/fake_segment_prefetch.js +3 -0
  173. package/test/test/util/manifest_generator.js +4 -0
  174. package/test/test/util/manifest_parser_util.js +14 -2
  175. package/test/test/util/offline_utils.js +1 -0
  176. package/test/test/util/simple_fakes.js +2 -2
  177. package/test/test/util/streaming_engine_util.js +3 -0
  178. package/test/test/util/test_scheme.js +6 -0
  179. package/test/test/util/ui_utils.js +4 -3
  180. package/test/text/mp4_ttml_parser_unit.js +4 -4
  181. package/test/text/srt_text_parser_unit.js +1 -1
  182. package/test/text/text_displayer_layout_unit.js +1 -1
  183. package/test/text/text_engine_unit.js +6 -0
  184. package/test/text/ttml_text_parser_unit.js +33 -2
  185. package/test/transmuxer/transmuxer_engine_integration.js +9 -0
  186. package/test/transmuxer/transmuxer_integration.js +33 -3
  187. package/test/ui/ad_ui_unit.js +2 -2
  188. package/test/ui/ui_customization_unit.js +17 -16
  189. package/test/ui/ui_integration.js +30 -20
  190. package/test/ui/ui_unit.js +25 -19
  191. package/test/util/buffer_utils_unit.js +1 -1
  192. package/test/util/content_steering_manager_unit.js +427 -0
  193. package/test/util/id3_utils_unit.js +52 -0
  194. package/test/util/stream_utils_unit.js +17 -113
  195. package/test/util/ts_parser_unit.js +3 -3
  196. package/third_party/closure-uri/uri.js +13 -1
  197. package/ui/audio_language_selection.js +9 -2
  198. package/ui/externs/ui.js +17 -2
  199. package/ui/language_utils.js +43 -8
  200. package/ui/less/range_elements.less +33 -0
  201. package/ui/locales/en.json +1 -0
  202. package/ui/locales/es-419.json +1 -0
  203. package/ui/locales/es.json +1 -0
  204. package/ui/locales/source.json +4 -0
  205. package/ui/localization.js +10 -0
  206. package/ui/remote_button.js +3 -1
  207. package/ui/resolution_selection.js +47 -10
  208. package/ui/seek_bar.js +296 -3
  209. package/ui/text_selection.js +2 -1
  210. package/ui/ui.js +6 -2
  211. package/lib/routing/node.js +0 -23
  212. package/lib/routing/payload.js +0 -47
  213. package/lib/routing/walker.js +0 -587
  214. package/test/routing/walker_unit.js +0 -505
@@ -19,6 +19,7 @@ goog.require('shaka.media.PresentationTimeline');
19
19
  goog.require('shaka.media.SegmentIndex');
20
20
  goog.require('shaka.net.NetworkingEngine');
21
21
  goog.require('shaka.text.TextEngine');
22
+ goog.require('shaka.util.ContentSteeringManager');
22
23
  goog.require('shaka.util.Error');
23
24
  goog.require('shaka.util.Functional');
24
25
  goog.require('shaka.util.LanguageUtils');
@@ -115,6 +116,9 @@ shaka.dash.DashParser = class {
115
116
 
116
117
  /** @private {boolean} */
117
118
  this.lowLatencyMode_ = false;
119
+
120
+ /** @private {?shaka.util.ContentSteeringManager} */
121
+ this.contentSteeringManager_ = null;
118
122
  }
119
123
 
120
124
  /**
@@ -126,6 +130,10 @@ shaka.dash.DashParser = class {
126
130
  'DashManifestConfiguration should not be null!');
127
131
 
128
132
  this.config_ = config;
133
+
134
+ if (this.contentSteeringManager_) {
135
+ this.contentSteeringManager_.configure(this.config_);
136
+ }
129
137
  }
130
138
 
131
139
  /**
@@ -185,6 +193,10 @@ shaka.dash.DashParser = class {
185
193
  this.updateTimer_ = null;
186
194
  }
187
195
 
196
+ if (this.contentSteeringManager_) {
197
+ this.contentSteeringManager_.destroy();
198
+ }
199
+
188
200
  return this.operationManager_.destroy();
189
201
  }
190
202
 
@@ -212,6 +224,43 @@ shaka.dash.DashParser = class {
212
224
  // No-op
213
225
  }
214
226
 
227
+ /**
228
+ * @override
229
+ * @exportInterface
230
+ */
231
+ onInitialVariantChosen(variant) {
232
+ // For live it is necessary that the first time we update the manifest with
233
+ // a shorter time than indicated to take into account that the last segment
234
+ // added could be halfway, for example
235
+ if (this.manifest_ && this.manifest_.presentationTimeline.isLive()) {
236
+ const stream = variant.video || variant.audio;
237
+ if (stream && stream.segmentIndex) {
238
+ const availabilityEnd =
239
+ this.manifest_.presentationTimeline.getSegmentAvailabilityEnd();
240
+ const position = stream.segmentIndex.find(availabilityEnd);
241
+ if (position == null) {
242
+ return;
243
+ }
244
+ const reference = stream.segmentIndex.get(position);
245
+ if (!reference) {
246
+ return;
247
+ }
248
+ this.updatePeriod_ = reference.endTime - availabilityEnd;
249
+ this.setUpdateTimer_(/* offset= */ 0);
250
+ }
251
+ }
252
+ }
253
+
254
+ /**
255
+ * @override
256
+ * @exportInterface
257
+ */
258
+ banLocation(uri) {
259
+ if (this.contentSteeringManager_) {
260
+ this.contentSteeringManager_.banLocation(uri);
261
+ }
262
+ }
263
+
215
264
  /**
216
265
  * Makes a network request for the manifest and parses the resulting data.
217
266
  *
@@ -320,10 +369,68 @@ shaka.dash.DashParser = class {
320
369
  manifestBaseUris = absoluteLocations;
321
370
  }
322
371
 
372
+ let contentSteeringPromise = Promise.resolve();
373
+
374
+ const contentSteering = XmlUtils.findChild(mpd, 'ContentSteering');
375
+ if (contentSteering && this.playerInterface_) {
376
+ const hasPrevContentSteeringManager = !!this.contentSteeringManager_;
377
+ if (!this.contentSteeringManager_) {
378
+ this.contentSteeringManager_ =
379
+ new shaka.util.ContentSteeringManager(this.playerInterface_);
380
+ }
381
+ this.contentSteeringManager_.configure(this.config_);
382
+ this.contentSteeringManager_.setBaseUris(manifestBaseUris);
383
+ this.contentSteeringManager_.setManifestType(
384
+ shaka.media.ManifestParser.DASH);
385
+ const defaultPathwayId =
386
+ contentSteering.getAttribute('defaultServiceLocation');
387
+ this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
388
+ if (!hasPrevContentSteeringManager) {
389
+ const uri = XmlUtils.getContents(contentSteering);
390
+ if (uri) {
391
+ const queryBeforeStart =
392
+ XmlUtils.parseAttr(contentSteering, 'queryBeforeStart',
393
+ XmlUtils.parseBoolean, /* defaultValue= */ false);
394
+ if (queryBeforeStart) {
395
+ contentSteeringPromise =
396
+ this.contentSteeringManager_.requestInfo(uri);
397
+ } else {
398
+ this.contentSteeringManager_.requestInfo(uri);
399
+ }
400
+ }
401
+ }
402
+ }
403
+
323
404
  const uriObjs = XmlUtils.findChildren(mpd, 'BaseURL');
324
- const uris = uriObjs.map(XmlUtils.getContents);
325
- const baseUris = shaka.util.ManifestParserUtils.resolveUris(
326
- manifestBaseUris, uris);
405
+ let calculatedBaseUris;
406
+ let someLocationValid = false;
407
+ if (this.contentSteeringManager_) {
408
+ this.contentSteeringManager_.clearPreviousLocations();
409
+ for (const uriObj of uriObjs) {
410
+ const serviceLocation = uriObj.getAttribute('serviceLocation');
411
+ const uri = XmlUtils.getContents(uriObj);
412
+ if (serviceLocation && uri) {
413
+ this.contentSteeringManager_.addLocation(
414
+ 'BaseURL', serviceLocation, uri);
415
+ someLocationValid = true;
416
+ }
417
+ }
418
+ }
419
+ if (!someLocationValid || !this.contentSteeringManager_) {
420
+ const uris = uriObjs.map(XmlUtils.getContents);
421
+ calculatedBaseUris = shaka.util.ManifestParserUtils.resolveUris(
422
+ manifestBaseUris, uris);
423
+ }
424
+
425
+ const getBaseUris = () => {
426
+ if (this.contentSteeringManager_ && someLocationValid) {
427
+ return this.contentSteeringManager_.getLocations('BaseURL');
428
+ }
429
+ if (calculatedBaseUris) {
430
+ return calculatedBaseUris;
431
+ }
432
+ return [];
433
+ };
327
434
 
328
435
  let availabilityTimeOffset = 0;
329
436
  if (uriObjs && uriObjs.length) {
@@ -439,7 +546,7 @@ shaka.dash.DashParser = class {
439
546
  profiles: profiles.split(','),
440
547
  };
441
548
 
442
- const periodsAndDuration = this.parsePeriods_(context, baseUris, mpd);
549
+ const periodsAndDuration = this.parsePeriods_(context, getBaseUris, mpd);
443
550
  const duration = periodsAndDuration.duration;
444
551
  const periods = periodsAndDuration.periods;
445
552
 
@@ -483,18 +590,14 @@ shaka.dash.DashParser = class {
483
590
 
484
591
  await this.periodCombiner_.combinePeriods(periods, context.dynamic);
485
592
 
593
+ await contentSteeringPromise;
594
+
486
595
  // Set minBufferTime to 0 for low-latency DASH live stream to achieve the
487
596
  // best latency
488
597
  if (this.lowLatencyMode_) {
489
598
  minBufferTime = 0;
490
599
  }
491
600
 
492
- // Set updatePeriod_ to minTotalAvailabilityTimeOffset_ to achieve the best
493
- // latency in LL streams
494
- if (this.lowLatencyMode_ && this.minTotalAvailabilityTimeOffset_) {
495
- this.updatePeriod_ = this.minTotalAvailabilityTimeOffset_;
496
- }
497
-
498
601
  // These steps are not done on manifest update.
499
602
  if (!this.manifest_) {
500
603
  this.manifest_ = {
@@ -515,7 +618,7 @@ shaka.dash.DashParser = class {
515
618
  if (presentationTimeline.usingPresentationStartTime()) {
516
619
  const XmlUtils = shaka.util.XmlUtils;
517
620
  const timingElements = XmlUtils.findChildren(mpd, 'UTCTiming');
518
- const offset = await this.parseUtcTiming_(baseUris, timingElements);
621
+ const offset = await this.parseUtcTiming_(getBaseUris, timingElements);
519
622
  // Detect calls to stop().
520
623
  if (!this.playerInterface_) {
521
624
  return;
@@ -575,8 +678,19 @@ shaka.dash.DashParser = class {
575
678
  const maxPlaybackRate = playbackRateNode ?
576
679
  parseFloat(playbackRateNode.getAttribute('max')) :
577
680
  null;
681
+ const minLatency = latencyNode && latencyNode.getAttribute('min') ?
682
+ parseInt(latencyNode.getAttribute('min'), 10) / 1000 :
683
+ null;
684
+ const minPlaybackRate = playbackRateNode ?
685
+ parseFloat(playbackRateNode.getAttribute('min')) :
686
+ null;
578
687
 
579
- return {maxLatency, maxPlaybackRate};
688
+ return {
689
+ maxLatency,
690
+ maxPlaybackRate,
691
+ minLatency,
692
+ minPlaybackRate,
693
+ };
580
694
  }
581
695
 
582
696
  return null;
@@ -588,7 +702,7 @@ shaka.dash.DashParser = class {
588
702
  * children.
589
703
  *
590
704
  * @param {shaka.dash.DashParser.Context} context
591
- * @param {!Array.<string>} baseUris
705
+ * @param {function():!Array.<string>} getBaseUris
592
706
  * @param {!Element} mpd
593
707
  * @return {{
594
708
  * periods: !Array.<shaka.extern.Period>,
@@ -597,7 +711,7 @@ shaka.dash.DashParser = class {
597
711
  * }}
598
712
  * @private
599
713
  */
600
- parsePeriods_(context, baseUris, mpd) {
714
+ parsePeriods_(context, getBaseUris, mpd) {
601
715
  const XmlUtils = shaka.util.XmlUtils;
602
716
  const presentationDuration = XmlUtils.parseAttr(
603
717
  mpd, 'mediaPresentationDuration', XmlUtils.parseDuration);
@@ -687,7 +801,7 @@ shaka.dash.DashParser = class {
687
801
  node: elem,
688
802
  isLastPeriod: periodDuration == null || !next,
689
803
  };
690
- const period = this.parsePeriod_(context, baseUris, info);
804
+ const period = this.parsePeriod_(context, getBaseUris, info);
691
805
  periods.push(period);
692
806
 
693
807
  if (context.period.id && periodDuration) {
@@ -741,17 +855,17 @@ shaka.dash.DashParser = class {
741
855
  * was done before this was called so start and duration are valid.
742
856
  *
743
857
  * @param {shaka.dash.DashParser.Context} context
744
- * @param {!Array.<string>} baseUris
858
+ * @param {function():!Array.<string>} getBaseUris
745
859
  * @param {shaka.dash.DashParser.PeriodInfo} periodInfo
746
860
  * @return {shaka.extern.Period}
747
861
  * @private
748
862
  */
749
- parsePeriod_(context, baseUris, periodInfo) {
863
+ parsePeriod_(context, getBaseUris, periodInfo) {
750
864
  const Functional = shaka.util.Functional;
751
865
  const XmlUtils = shaka.util.XmlUtils;
752
866
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
753
867
 
754
- context.period = this.createFrame_(periodInfo.node, null, baseUris);
868
+ context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
755
869
  context.periodInfo = periodInfo;
756
870
  context.period.availabilityTimeOffset = context.availabilityTimeOffset;
757
871
 
@@ -822,40 +936,29 @@ shaka.dash.DashParser = class {
822
936
  }
823
937
  }
824
938
 
825
- const audioSets = this.config_.disableAudio ? [] :
826
- this.getSetsOfType_(normalAdaptationSets, ContentType.AUDIO);
827
- const videoSets = this.config_.disableVideo ? [] :
828
- this.getSetsOfType_(normalAdaptationSets, ContentType.VIDEO);
829
- const textSets = this.config_.disableText ? [] :
830
- this.getSetsOfType_(normalAdaptationSets, ContentType.TEXT);
831
- const imageSets = this.config_.disableThumbnails ? [] :
832
- this.getSetsOfType_(normalAdaptationSets, ContentType.IMAGE);
833
-
834
- if (!videoSets.length && !audioSets.length) {
939
+ const audioStreams = this.getStreamsFromSets_(
940
+ this.config_.disableAudio,
941
+ normalAdaptationSets,
942
+ ContentType.AUDIO);
943
+ const videoStreams = this.getStreamsFromSets_(
944
+ this.config_.disableVideo,
945
+ normalAdaptationSets,
946
+ ContentType.VIDEO);
947
+ const textStreams = this.getStreamsFromSets_(
948
+ this.config_.disableText,
949
+ normalAdaptationSets,
950
+ ContentType.TEXT);
951
+ const imageStreams = this.getStreamsFromSets_(
952
+ this.config_.disableThumbnails,
953
+ normalAdaptationSets,
954
+ ContentType.IMAGE);
955
+
956
+ if (videoStreams.length === 0 && audioStreams.length === 0) {
835
957
  throw new shaka.util.Error(
836
958
  shaka.util.Error.Severity.CRITICAL,
837
959
  shaka.util.Error.Category.MANIFEST,
838
- shaka.util.Error.Code.DASH_EMPTY_PERIOD);
839
- }
840
-
841
- const audioStreams = [];
842
- for (const audioSet of audioSets) {
843
- audioStreams.push(...audioSet.streams);
844
- }
845
-
846
- const videoStreams = [];
847
- for (const videoSet of videoSets) {
848
- videoStreams.push(...videoSet.streams);
849
- }
850
-
851
- const textStreams = [];
852
- for (const textSet of textSets) {
853
- textStreams.push(...textSet.streams);
854
- }
855
-
856
- const imageStreams = [];
857
- for (const imageSet of imageSets) {
858
- imageStreams.push(...imageSet.streams);
960
+ shaka.util.Error.Code.DASH_EMPTY_PERIOD,
961
+ );
859
962
  }
860
963
 
861
964
  return {
@@ -868,15 +971,26 @@ shaka.dash.DashParser = class {
868
971
  }
869
972
 
870
973
  /**
974
+ * Gets the streams from the given sets or returns an empty array if disabled
975
+ * or no streams are found.
976
+ * @param {boolean} disabled
871
977
  * @param {!Array.<!shaka.dash.DashParser.AdaptationInfo>} adaptationSets
872
- * @param {string} type
873
- * @return {!Array.<!shaka.dash.DashParser.AdaptationInfo>}
874
- * @private
875
- */
876
- getSetsOfType_(adaptationSets, type) {
877
- return adaptationSets.filter((as) => {
878
- return as.contentType == type;
879
- });
978
+ * @param {string} contentType
979
+ @private
980
+ */
981
+ getStreamsFromSets_(disabled, adaptationSets, contentType) {
982
+ if (disabled || !adaptationSets.length) {
983
+ return [];
984
+ }
985
+
986
+ return adaptationSets.reduce((all, part) => {
987
+ if (part.contentType != contentType) {
988
+ return all;
989
+ }
990
+
991
+ all.push(...part.streams);
992
+ return all;
993
+ }, []);
880
994
  }
881
995
 
882
996
  /**
@@ -964,6 +1078,7 @@ shaka.dash.DashParser = class {
964
1078
  XmlUtils.findChildren(elem, 'EssentialProperty');
965
1079
  // ID of real AdaptationSet if this is a trick mode set:
966
1080
  let trickModeFor = null;
1081
+ let isFastSwitching = false;
967
1082
  let unrecognizedEssentialProperty = false;
968
1083
  for (const prop of essentialProperties) {
969
1084
  const schemeId = prop.getAttribute('schemeIdUri');
@@ -976,6 +1091,8 @@ shaka.dash.DashParser = class {
976
1091
  } else if (schemeId == colourPrimariesScheme ||
977
1092
  schemeId == matrixCoefficientsScheme) {
978
1093
  continue;
1094
+ } else if (schemeId == 'urn:mpeg:dash:ssr:2023') {
1095
+ isFastSwitching = true;
979
1096
  } else {
980
1097
  unrecognizedEssentialProperty = true;
981
1098
  }
@@ -1133,6 +1250,7 @@ shaka.dash.DashParser = class {
1133
1250
  closedCaptions, representation, accessibilityPurpose);
1134
1251
  if (parsedRepresentation) {
1135
1252
  parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
1253
+ parsedRepresentation.fastSwitching = isFastSwitching;
1136
1254
  }
1137
1255
  return parsedRepresentation;
1138
1256
  }).filter((s) => !!s);
@@ -1257,9 +1375,9 @@ shaka.dash.DashParser = class {
1257
1375
  /** @type {shaka.extern.aes128Key|undefined} */
1258
1376
  let aes128Key = undefined;
1259
1377
  if (contentProtection.aes128Info) {
1260
- const baseUris = context.representation.baseUris;
1378
+ const getBaseUris = context.representation.getBaseUris;
1261
1379
  const uris = shaka.util.ManifestParserUtils.resolveUris(
1262
- baseUris, [contentProtection.aes128Info.keyUri]);
1380
+ getBaseUris(), [contentProtection.aes128Info.keyUri]);
1263
1381
  const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
1264
1382
  const request = shaka.net.NetworkingEngine.makeRequest(
1265
1383
  uris, this.config_.retryParameters);
@@ -1313,10 +1431,10 @@ shaka.dash.DashParser = class {
1313
1431
  goog.asserts.assert(isText,
1314
1432
  'Must have Segment* with non-text streams.');
1315
1433
 
1316
- const baseUris = context.representation.baseUris;
1317
1434
  const duration = context.periodInfo.duration || 0;
1318
1435
  streamInfo = {
1319
1436
  generateSegmentIndex: () => {
1437
+ const baseUris = context.representation.getBaseUris();
1320
1438
  return Promise.resolve(shaka.media.SegmentIndex.forSingleSegment(
1321
1439
  periodStart, duration, baseUris));
1322
1440
  },
@@ -1442,10 +1560,12 @@ shaka.dash.DashParser = class {
1442
1560
  spatialAudio,
1443
1561
  closedCaptions,
1444
1562
  hdr,
1563
+ videoLayout: undefined,
1445
1564
  tilesLayout,
1446
1565
  matchedStreams: [],
1447
1566
  accessibilityPurpose,
1448
1567
  external: false,
1568
+ fastSwitching: false,
1449
1569
  };
1450
1570
  }
1451
1571
 
@@ -1518,8 +1638,8 @@ shaka.dash.DashParser = class {
1518
1638
  // NOTE: An updatePeriod_ of -1 means the attribute was missing.
1519
1639
  // An attribute which is present and set to 0 should still result in
1520
1640
  // periodic updates. For more, see:
1521
- // https://github.com/shaka-project/shaka-player/issues/331
1522
- if (this.updatePeriod_ < 0) {
1641
+ // https://github.com/Dash-Industry-Forum/Guidelines-TimingModel/issues/48
1642
+ if (this.updatePeriod_ <= 0) {
1523
1643
  return;
1524
1644
  }
1525
1645
 
@@ -1537,13 +1657,13 @@ shaka.dash.DashParser = class {
1537
1657
  *
1538
1658
  * @param {!Element} elem
1539
1659
  * @param {?shaka.dash.DashParser.InheritanceFrame} parent
1540
- * @param {Array.<string>} baseUris
1660
+ * @param {?function():!Array.<string>} getBaseUris
1541
1661
  * @return {shaka.dash.DashParser.InheritanceFrame}
1542
1662
  * @private
1543
1663
  */
1544
- createFrame_(elem, parent, baseUris) {
1545
- goog.asserts.assert(parent || baseUris,
1546
- 'Must provide either parent or baseUris');
1664
+ createFrame_(elem, parent, getBaseUris) {
1665
+ goog.asserts.assert(parent || getBaseUris,
1666
+ 'Must provide either parent or getBaseUris');
1547
1667
  const ManifestParserUtils = shaka.util.ManifestParserUtils;
1548
1668
  const XmlUtils = shaka.util.XmlUtils;
1549
1669
  parent = parent || /** @type {shaka.dash.DashParser.InheritanceFrame} */ ({
@@ -1556,14 +1676,44 @@ shaka.dash.DashParser = class {
1556
1676
  numChannels: null,
1557
1677
  audioSamplingRate: null,
1558
1678
  availabilityTimeOffset: 0,
1679
+ segmentSequenceCadence: 0,
1559
1680
  });
1560
- baseUris = baseUris || parent.baseUris;
1681
+ getBaseUris = getBaseUris || parent.getBaseUris;
1561
1682
 
1562
1683
  const parseNumber = XmlUtils.parseNonNegativeInt;
1563
1684
  const evalDivision = XmlUtils.evalDivision;
1564
1685
 
1686
+ const id = elem.getAttribute('id');
1565
1687
  const uriObjs = XmlUtils.findChildren(elem, 'BaseURL');
1566
- const uris = uriObjs.map(XmlUtils.getContents);
1688
+ let calculatedBaseUris;
1689
+ let someLocationValid = false;
1690
+ if (this.contentSteeringManager_) {
1691
+ for (const uriObj of uriObjs) {
1692
+ const serviceLocation = uriObj.getAttribute('serviceLocation');
1693
+ const uri = XmlUtils.getContents(uriObj);
1694
+ if (serviceLocation && uri) {
1695
+ this.contentSteeringManager_.addLocation(
1696
+ id, serviceLocation, uri);
1697
+ someLocationValid = true;
1698
+ }
1699
+ }
1700
+ }
1701
+ if (!someLocationValid || !this.contentSteeringManager_) {
1702
+ calculatedBaseUris = uriObjs.map(XmlUtils.getContents);
1703
+ }
1704
+
1705
+ const getFrameUris = () => {
1706
+ if (!uriObjs.length) {
1707
+ return [];
1708
+ }
1709
+ if (this.contentSteeringManager_ && someLocationValid) {
1710
+ return this.contentSteeringManager_.getLocations(id);
1711
+ }
1712
+ if (calculatedBaseUris) {
1713
+ return calculatedBaseUris;
1714
+ }
1715
+ return [];
1716
+ };
1567
1717
 
1568
1718
  let contentType = elem.getAttribute('contentType') || parent.contentType;
1569
1719
  const mimeType = elem.getAttribute('mimeType') || parent.mimeType;
@@ -1606,8 +1756,20 @@ shaka.dash.DashParser = class {
1606
1756
  const availabilityTimeOffset = parent.availabilityTimeOffset + baseUriAto +
1607
1757
  segmentBaseAto + segmentTemplateAto;
1608
1758
 
1759
+ let segmentSequenceCadence = null;
1760
+ const segmentSequenceProperties =
1761
+ XmlUtils.findChild(elem, 'SegmentSequenceProperties');
1762
+ if (segmentSequenceProperties) {
1763
+ const sap = XmlUtils.findChild(segmentSequenceProperties, 'SAP');
1764
+ if (sap) {
1765
+ segmentSequenceCadence = XmlUtils.parseAttr(sap, 'cadence',
1766
+ XmlUtils.parseInt);
1767
+ }
1768
+ }
1769
+
1609
1770
  return {
1610
- baseUris: ManifestParserUtils.resolveUris(baseUris, uris),
1771
+ getBaseUris:
1772
+ () => ManifestParserUtils.resolveUris(getBaseUris(), getFrameUris()),
1611
1773
  segmentBase: segmentBase || parent.segmentBase,
1612
1774
  segmentList:
1613
1775
  XmlUtils.findChild(elem, 'SegmentList') || parent.segmentList,
@@ -1620,11 +1782,13 @@ shaka.dash.DashParser = class {
1620
1782
  frameRate: frameRate,
1621
1783
  pixelAspectRatio: pixelAspectRatio,
1622
1784
  emsgSchemeIdUris: emsgSchemeIdUris,
1623
- id: elem.getAttribute('id'),
1785
+ id: id,
1624
1786
  language: elem.getAttribute('lang'),
1625
1787
  numChannels: numChannels,
1626
1788
  audioSamplingRate: audioSamplingRate,
1627
1789
  availabilityTimeOffset: availabilityTimeOffset,
1790
+ segmentSequenceCadence:
1791
+ segmentSequenceCadence || parent.segmentSequenceCadence,
1628
1792
  };
1629
1793
  }
1630
1794
 
@@ -1794,15 +1958,15 @@ shaka.dash.DashParser = class {
1794
1958
  /**
1795
1959
  * Makes a request to the given URI and calculates the clock offset.
1796
1960
  *
1797
- * @param {!Array.<string>} baseUris
1961
+ * @param {function():!Array.<string>} getBaseUris
1798
1962
  * @param {string} uri
1799
1963
  * @param {string} method
1800
1964
  * @return {!Promise.<number>}
1801
1965
  * @private
1802
1966
  */
1803
- async requestForTiming_(baseUris, uri, method) {
1967
+ async requestForTiming_(getBaseUris, uri, method) {
1804
1968
  const requestUris =
1805
- shaka.util.ManifestParserUtils.resolveUris(baseUris, [uri]);
1969
+ shaka.util.ManifestParserUtils.resolveUris(getBaseUris(), [uri]);
1806
1970
  const request = shaka.net.NetworkingEngine.makeRequest(
1807
1971
  requestUris, this.config_.retryParameters);
1808
1972
  request.method = method;
@@ -1835,12 +1999,12 @@ shaka.dash.DashParser = class {
1835
1999
  /**
1836
2000
  * Parses an array of UTCTiming elements.
1837
2001
  *
1838
- * @param {!Array.<string>} baseUris
2002
+ * @param {function():!Array.<string>} getBaseUris
1839
2003
  * @param {!Array.<!Element>} elems
1840
2004
  * @return {!Promise.<number>}
1841
2005
  * @private
1842
2006
  */
1843
- async parseUtcTiming_(baseUris, elems) {
2007
+ async parseUtcTiming_(getBaseUris, elems) {
1844
2008
  const schemesAndValues = elems.map((elem) => {
1845
2009
  return {
1846
2010
  scheme: elem.getAttribute('schemeIdUri'),
@@ -1869,13 +2033,13 @@ shaka.dash.DashParser = class {
1869
2033
  case 'urn:mpeg:dash:utc:http-head:2014':
1870
2034
  case 'urn:mpeg:dash:utc:http-head:2012':
1871
2035
  // eslint-disable-next-line no-await-in-loop
1872
- return await this.requestForTiming_(baseUris, value, 'HEAD');
2036
+ return await this.requestForTiming_(getBaseUris, value, 'HEAD');
1873
2037
  case 'urn:mpeg:dash:utc:http-xsdate:2014':
1874
2038
  case 'urn:mpeg:dash:utc:http-iso:2014':
1875
2039
  case 'urn:mpeg:dash:utc:http-xsdate:2012':
1876
2040
  case 'urn:mpeg:dash:utc:http-iso:2012':
1877
2041
  // eslint-disable-next-line no-await-in-loop
1878
- return await this.requestForTiming_(baseUris, value, 'GET');
2042
+ return await this.requestForTiming_(getBaseUris, value, 'GET');
1879
2043
  case 'urn:mpeg:dash:utc:direct:2014':
1880
2044
  case 'urn:mpeg:dash:utc:direct:2012': {
1881
2045
  const date = Date.parse(value);
@@ -2038,7 +2202,7 @@ shaka.dash.DashParser.RequestSegmentCallback;
2038
2202
  * segmentBase: Element,
2039
2203
  * segmentList: Element,
2040
2204
  * segmentTemplate: Element,
2041
- * baseUris: !Array.<string>,
2205
+ * getBaseUris: function():!Array.<string>,
2042
2206
  * width: (number|undefined),
2043
2207
  * height: (number|undefined),
2044
2208
  * contentType: string,
@@ -2051,7 +2215,8 @@ shaka.dash.DashParser.RequestSegmentCallback;
2051
2215
  * language: ?string,
2052
2216
  * numChannels: ?number,
2053
2217
  * audioSamplingRate: ?number,
2054
- * availabilityTimeOffset: number
2218
+ * availabilityTimeOffset: number,
2219
+ * segmentSequenceCadence: number
2055
2220
  * }}
2056
2221
  *
2057
2222
  * @description
@@ -2064,8 +2229,8 @@ shaka.dash.DashParser.RequestSegmentCallback;
2064
2229
  * The XML node for SegmentList.
2065
2230
  * @property {Element} segmentTemplate
2066
2231
  * The XML node for SegmentTemplate.
2067
- * @property {!Array.<string>} baseUris
2068
- * An array of absolute base URIs for the frame.
2232
+ * @property {function():!Array.<string>} getBaseUris
2233
+ * Function than returns an array of absolute base URIs for the frame.
2069
2234
  * @property {(number|undefined)} width
2070
2235
  * The inherited width value.
2071
2236
  * @property {(number|undefined)} height
@@ -2092,6 +2257,9 @@ shaka.dash.DashParser.RequestSegmentCallback;
2092
2257
  * Specifies the maximum sampling rate of the content, or null if unknown.
2093
2258
  * @property {number} availabilityTimeOffset
2094
2259
  * Specifies the total availabilityTimeOffset of the segment, or 0 if unknown.
2260
+ * @property {number} segmentSequenceCadence
2261
+ * Specifies the cadence of independent segments in Segment Sequence
2262
+ * Representation.
2095
2263
  */
2096
2264
  shaka.dash.DashParser.InheritanceFrame;
2097
2265
 
@@ -2225,8 +2393,6 @@ shaka.dash.DashParser.GenerateSegmentIndexFunction;
2225
2393
  shaka.dash.DashParser.StreamInfo;
2226
2394
 
2227
2395
 
2228
- shaka.media.ManifestParser.registerParserByExtension(
2229
- 'mpd', () => new shaka.dash.DashParser());
2230
2396
  shaka.media.ManifestParser.registerParserByMime(
2231
2397
  'application/dash+xml', () => new shaka.dash.DashParser());
2232
2398
  shaka.media.ManifestParser.registerParserByMime(