shaka-player 4.5.0 → 4.6.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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/AUTHORS +2 -0
  3. package/CHANGELOG.md +109 -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 +34 -30
  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 +789 -738
  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 +1147 -1113
  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 +857 -791
  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 +1359 -1314
  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 +251 -84
  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 +92 -29
  70. package/lib/hls/hls_classes.js +28 -43
  71. package/lib/hls/hls_parser.js +770 -652
  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 +72 -44
  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 +998 -1503
  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 +417 -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
  *
@@ -236,7 +285,8 @@ shaka.dash.DashParser = class {
236
285
 
237
286
  // For redirections add the response uri to the first entry in the
238
287
  // Manifest Uris array.
239
- if (response.uri && !this.manifestUris_.includes(response.uri)) {
288
+ if (response.uri && response.uri != response.originalUri &&
289
+ !this.manifestUris_.includes(response.uri)) {
240
290
  this.manifestUris_.unshift(response.uri);
241
291
  }
242
292
 
@@ -320,10 +370,68 @@ shaka.dash.DashParser = class {
320
370
  manifestBaseUris = absoluteLocations;
321
371
  }
322
372
 
373
+ let contentSteeringPromise = Promise.resolve();
374
+
375
+ const contentSteering = XmlUtils.findChild(mpd, 'ContentSteering');
376
+ if (contentSteering && this.playerInterface_) {
377
+ const defaultPathwayId =
378
+ contentSteering.getAttribute('defaultServiceLocation');
379
+ if (!this.contentSteeringManager_) {
380
+ this.contentSteeringManager_ =
381
+ new shaka.util.ContentSteeringManager(this.playerInterface_);
382
+ this.contentSteeringManager_.configure(this.config_);
383
+ this.contentSteeringManager_.setManifestType(
384
+ shaka.media.ManifestParser.DASH);
385
+ this.contentSteeringManager_.setBaseUris(manifestBaseUris);
386
+ this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
387
+ const uri = XmlUtils.getContents(contentSteering);
388
+ if (uri) {
389
+ const queryBeforeStart =
390
+ XmlUtils.parseAttr(contentSteering, 'queryBeforeStart',
391
+ XmlUtils.parseBoolean, /* defaultValue= */ false);
392
+ if (queryBeforeStart) {
393
+ contentSteeringPromise =
394
+ this.contentSteeringManager_.requestInfo(uri);
395
+ } else {
396
+ this.contentSteeringManager_.requestInfo(uri);
397
+ }
398
+ }
399
+ } else {
400
+ this.contentSteeringManager_.setBaseUris(manifestBaseUris);
401
+ this.contentSteeringManager_.setDefaultPathwayId(defaultPathwayId);
402
+ }
403
+ }
404
+
323
405
  const uriObjs = XmlUtils.findChildren(mpd, 'BaseURL');
324
- const uris = uriObjs.map(XmlUtils.getContents);
325
- const baseUris = shaka.util.ManifestParserUtils.resolveUris(
326
- manifestBaseUris, uris);
406
+ let calculatedBaseUris;
407
+ let someLocationValid = false;
408
+ if (this.contentSteeringManager_) {
409
+ this.contentSteeringManager_.clearPreviousLocations();
410
+ for (const uriObj of uriObjs) {
411
+ const serviceLocation = uriObj.getAttribute('serviceLocation');
412
+ const uri = XmlUtils.getContents(uriObj);
413
+ if (serviceLocation && uri) {
414
+ this.contentSteeringManager_.addLocation(
415
+ 'BaseURL', serviceLocation, uri);
416
+ someLocationValid = true;
417
+ }
418
+ }
419
+ }
420
+ if (!someLocationValid || !this.contentSteeringManager_) {
421
+ const uris = uriObjs.map(XmlUtils.getContents);
422
+ calculatedBaseUris = shaka.util.ManifestParserUtils.resolveUris(
423
+ manifestBaseUris, uris);
424
+ }
425
+
426
+ const getBaseUris = () => {
427
+ if (this.contentSteeringManager_ && someLocationValid) {
428
+ return this.contentSteeringManager_.getLocations('BaseURL');
429
+ }
430
+ if (calculatedBaseUris) {
431
+ return calculatedBaseUris;
432
+ }
433
+ return [];
434
+ };
327
435
 
328
436
  let availabilityTimeOffset = 0;
329
437
  if (uriObjs && uriObjs.length) {
@@ -439,7 +547,7 @@ shaka.dash.DashParser = class {
439
547
  profiles: profiles.split(','),
440
548
  };
441
549
 
442
- const periodsAndDuration = this.parsePeriods_(context, baseUris, mpd);
550
+ const periodsAndDuration = this.parsePeriods_(context, getBaseUris, mpd);
443
551
  const duration = periodsAndDuration.duration;
444
552
  const periods = periodsAndDuration.periods;
445
553
 
@@ -483,18 +591,14 @@ shaka.dash.DashParser = class {
483
591
 
484
592
  await this.periodCombiner_.combinePeriods(periods, context.dynamic);
485
593
 
594
+ await contentSteeringPromise;
595
+
486
596
  // Set minBufferTime to 0 for low-latency DASH live stream to achieve the
487
597
  // best latency
488
598
  if (this.lowLatencyMode_) {
489
599
  minBufferTime = 0;
490
600
  }
491
601
 
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
602
  // These steps are not done on manifest update.
499
603
  if (!this.manifest_) {
500
604
  this.manifest_ = {
@@ -515,7 +619,7 @@ shaka.dash.DashParser = class {
515
619
  if (presentationTimeline.usingPresentationStartTime()) {
516
620
  const XmlUtils = shaka.util.XmlUtils;
517
621
  const timingElements = XmlUtils.findChildren(mpd, 'UTCTiming');
518
- const offset = await this.parseUtcTiming_(baseUris, timingElements);
622
+ const offset = await this.parseUtcTiming_(getBaseUris, timingElements);
519
623
  // Detect calls to stop().
520
624
  if (!this.playerInterface_) {
521
625
  return;
@@ -575,8 +679,19 @@ shaka.dash.DashParser = class {
575
679
  const maxPlaybackRate = playbackRateNode ?
576
680
  parseFloat(playbackRateNode.getAttribute('max')) :
577
681
  null;
682
+ const minLatency = latencyNode && latencyNode.getAttribute('min') ?
683
+ parseInt(latencyNode.getAttribute('min'), 10) / 1000 :
684
+ null;
685
+ const minPlaybackRate = playbackRateNode ?
686
+ parseFloat(playbackRateNode.getAttribute('min')) :
687
+ null;
578
688
 
579
- return {maxLatency, maxPlaybackRate};
689
+ return {
690
+ maxLatency,
691
+ maxPlaybackRate,
692
+ minLatency,
693
+ minPlaybackRate,
694
+ };
580
695
  }
581
696
 
582
697
  return null;
@@ -588,7 +703,7 @@ shaka.dash.DashParser = class {
588
703
  * children.
589
704
  *
590
705
  * @param {shaka.dash.DashParser.Context} context
591
- * @param {!Array.<string>} baseUris
706
+ * @param {function():!Array.<string>} getBaseUris
592
707
  * @param {!Element} mpd
593
708
  * @return {{
594
709
  * periods: !Array.<shaka.extern.Period>,
@@ -597,7 +712,7 @@ shaka.dash.DashParser = class {
597
712
  * }}
598
713
  * @private
599
714
  */
600
- parsePeriods_(context, baseUris, mpd) {
715
+ parsePeriods_(context, getBaseUris, mpd) {
601
716
  const XmlUtils = shaka.util.XmlUtils;
602
717
  const presentationDuration = XmlUtils.parseAttr(
603
718
  mpd, 'mediaPresentationDuration', XmlUtils.parseDuration);
@@ -687,7 +802,7 @@ shaka.dash.DashParser = class {
687
802
  node: elem,
688
803
  isLastPeriod: periodDuration == null || !next,
689
804
  };
690
- const period = this.parsePeriod_(context, baseUris, info);
805
+ const period = this.parsePeriod_(context, getBaseUris, info);
691
806
  periods.push(period);
692
807
 
693
808
  if (context.period.id && periodDuration) {
@@ -741,17 +856,17 @@ shaka.dash.DashParser = class {
741
856
  * was done before this was called so start and duration are valid.
742
857
  *
743
858
  * @param {shaka.dash.DashParser.Context} context
744
- * @param {!Array.<string>} baseUris
859
+ * @param {function():!Array.<string>} getBaseUris
745
860
  * @param {shaka.dash.DashParser.PeriodInfo} periodInfo
746
861
  * @return {shaka.extern.Period}
747
862
  * @private
748
863
  */
749
- parsePeriod_(context, baseUris, periodInfo) {
864
+ parsePeriod_(context, getBaseUris, periodInfo) {
750
865
  const Functional = shaka.util.Functional;
751
866
  const XmlUtils = shaka.util.XmlUtils;
752
867
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
753
868
 
754
- context.period = this.createFrame_(periodInfo.node, null, baseUris);
869
+ context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
755
870
  context.periodInfo = periodInfo;
756
871
  context.period.availabilityTimeOffset = context.availabilityTimeOffset;
757
872
 
@@ -822,40 +937,29 @@ shaka.dash.DashParser = class {
822
937
  }
823
938
  }
824
939
 
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) {
940
+ const audioStreams = this.getStreamsFromSets_(
941
+ this.config_.disableAudio,
942
+ normalAdaptationSets,
943
+ ContentType.AUDIO);
944
+ const videoStreams = this.getStreamsFromSets_(
945
+ this.config_.disableVideo,
946
+ normalAdaptationSets,
947
+ ContentType.VIDEO);
948
+ const textStreams = this.getStreamsFromSets_(
949
+ this.config_.disableText,
950
+ normalAdaptationSets,
951
+ ContentType.TEXT);
952
+ const imageStreams = this.getStreamsFromSets_(
953
+ this.config_.disableThumbnails,
954
+ normalAdaptationSets,
955
+ ContentType.IMAGE);
956
+
957
+ if (videoStreams.length === 0 && audioStreams.length === 0) {
835
958
  throw new shaka.util.Error(
836
959
  shaka.util.Error.Severity.CRITICAL,
837
960
  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);
961
+ shaka.util.Error.Code.DASH_EMPTY_PERIOD,
962
+ );
859
963
  }
860
964
 
861
965
  return {
@@ -868,15 +972,26 @@ shaka.dash.DashParser = class {
868
972
  }
869
973
 
870
974
  /**
975
+ * Gets the streams from the given sets or returns an empty array if disabled
976
+ * or no streams are found.
977
+ * @param {boolean} disabled
871
978
  * @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
- });
979
+ * @param {string} contentType
980
+ @private
981
+ */
982
+ getStreamsFromSets_(disabled, adaptationSets, contentType) {
983
+ if (disabled || !adaptationSets.length) {
984
+ return [];
985
+ }
986
+
987
+ return adaptationSets.reduce((all, part) => {
988
+ if (part.contentType != contentType) {
989
+ return all;
990
+ }
991
+
992
+ all.push(...part.streams);
993
+ return all;
994
+ }, []);
880
995
  }
881
996
 
882
997
  /**
@@ -964,6 +1079,7 @@ shaka.dash.DashParser = class {
964
1079
  XmlUtils.findChildren(elem, 'EssentialProperty');
965
1080
  // ID of real AdaptationSet if this is a trick mode set:
966
1081
  let trickModeFor = null;
1082
+ let isFastSwitching = false;
967
1083
  let unrecognizedEssentialProperty = false;
968
1084
  for (const prop of essentialProperties) {
969
1085
  const schemeId = prop.getAttribute('schemeIdUri');
@@ -976,6 +1092,8 @@ shaka.dash.DashParser = class {
976
1092
  } else if (schemeId == colourPrimariesScheme ||
977
1093
  schemeId == matrixCoefficientsScheme) {
978
1094
  continue;
1095
+ } else if (schemeId == 'urn:mpeg:dash:ssr:2023') {
1096
+ isFastSwitching = true;
979
1097
  } else {
980
1098
  unrecognizedEssentialProperty = true;
981
1099
  }
@@ -1133,6 +1251,7 @@ shaka.dash.DashParser = class {
1133
1251
  closedCaptions, representation, accessibilityPurpose);
1134
1252
  if (parsedRepresentation) {
1135
1253
  parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
1254
+ parsedRepresentation.fastSwitching = isFastSwitching;
1136
1255
  }
1137
1256
  return parsedRepresentation;
1138
1257
  }).filter((s) => !!s);
@@ -1257,9 +1376,9 @@ shaka.dash.DashParser = class {
1257
1376
  /** @type {shaka.extern.aes128Key|undefined} */
1258
1377
  let aes128Key = undefined;
1259
1378
  if (contentProtection.aes128Info) {
1260
- const baseUris = context.representation.baseUris;
1379
+ const getBaseUris = context.representation.getBaseUris;
1261
1380
  const uris = shaka.util.ManifestParserUtils.resolveUris(
1262
- baseUris, [contentProtection.aes128Info.keyUri]);
1381
+ getBaseUris(), [contentProtection.aes128Info.keyUri]);
1263
1382
  const requestType = shaka.net.NetworkingEngine.RequestType.KEY;
1264
1383
  const request = shaka.net.NetworkingEngine.makeRequest(
1265
1384
  uris, this.config_.retryParameters);
@@ -1313,10 +1432,10 @@ shaka.dash.DashParser = class {
1313
1432
  goog.asserts.assert(isText,
1314
1433
  'Must have Segment* with non-text streams.');
1315
1434
 
1316
- const baseUris = context.representation.baseUris;
1317
1435
  const duration = context.periodInfo.duration || 0;
1318
1436
  streamInfo = {
1319
1437
  generateSegmentIndex: () => {
1438
+ const baseUris = context.representation.getBaseUris();
1320
1439
  return Promise.resolve(shaka.media.SegmentIndex.forSingleSegment(
1321
1440
  periodStart, duration, baseUris));
1322
1441
  },
@@ -1442,10 +1561,12 @@ shaka.dash.DashParser = class {
1442
1561
  spatialAudio,
1443
1562
  closedCaptions,
1444
1563
  hdr,
1564
+ videoLayout: undefined,
1445
1565
  tilesLayout,
1446
1566
  matchedStreams: [],
1447
1567
  accessibilityPurpose,
1448
1568
  external: false,
1569
+ fastSwitching: false,
1449
1570
  };
1450
1571
  }
1451
1572
 
@@ -1518,8 +1639,8 @@ shaka.dash.DashParser = class {
1518
1639
  // NOTE: An updatePeriod_ of -1 means the attribute was missing.
1519
1640
  // An attribute which is present and set to 0 should still result in
1520
1641
  // periodic updates. For more, see:
1521
- // https://github.com/shaka-project/shaka-player/issues/331
1522
- if (this.updatePeriod_ < 0) {
1642
+ // https://github.com/Dash-Industry-Forum/Guidelines-TimingModel/issues/48
1643
+ if (this.updatePeriod_ <= 0) {
1523
1644
  return;
1524
1645
  }
1525
1646
 
@@ -1537,13 +1658,13 @@ shaka.dash.DashParser = class {
1537
1658
  *
1538
1659
  * @param {!Element} elem
1539
1660
  * @param {?shaka.dash.DashParser.InheritanceFrame} parent
1540
- * @param {Array.<string>} baseUris
1661
+ * @param {?function():!Array.<string>} getBaseUris
1541
1662
  * @return {shaka.dash.DashParser.InheritanceFrame}
1542
1663
  * @private
1543
1664
  */
1544
- createFrame_(elem, parent, baseUris) {
1545
- goog.asserts.assert(parent || baseUris,
1546
- 'Must provide either parent or baseUris');
1665
+ createFrame_(elem, parent, getBaseUris) {
1666
+ goog.asserts.assert(parent || getBaseUris,
1667
+ 'Must provide either parent or getBaseUris');
1547
1668
  const ManifestParserUtils = shaka.util.ManifestParserUtils;
1548
1669
  const XmlUtils = shaka.util.XmlUtils;
1549
1670
  parent = parent || /** @type {shaka.dash.DashParser.InheritanceFrame} */ ({
@@ -1556,14 +1677,44 @@ shaka.dash.DashParser = class {
1556
1677
  numChannels: null,
1557
1678
  audioSamplingRate: null,
1558
1679
  availabilityTimeOffset: 0,
1680
+ segmentSequenceCadence: 0,
1559
1681
  });
1560
- baseUris = baseUris || parent.baseUris;
1682
+ getBaseUris = getBaseUris || parent.getBaseUris;
1561
1683
 
1562
1684
  const parseNumber = XmlUtils.parseNonNegativeInt;
1563
1685
  const evalDivision = XmlUtils.evalDivision;
1564
1686
 
1687
+ const id = elem.getAttribute('id');
1565
1688
  const uriObjs = XmlUtils.findChildren(elem, 'BaseURL');
1566
- const uris = uriObjs.map(XmlUtils.getContents);
1689
+ let calculatedBaseUris;
1690
+ let someLocationValid = false;
1691
+ if (this.contentSteeringManager_) {
1692
+ for (const uriObj of uriObjs) {
1693
+ const serviceLocation = uriObj.getAttribute('serviceLocation');
1694
+ const uri = XmlUtils.getContents(uriObj);
1695
+ if (serviceLocation && uri) {
1696
+ this.contentSteeringManager_.addLocation(
1697
+ id, serviceLocation, uri);
1698
+ someLocationValid = true;
1699
+ }
1700
+ }
1701
+ }
1702
+ if (!someLocationValid || !this.contentSteeringManager_) {
1703
+ calculatedBaseUris = uriObjs.map(XmlUtils.getContents);
1704
+ }
1705
+
1706
+ const getFrameUris = () => {
1707
+ if (!uriObjs.length) {
1708
+ return [];
1709
+ }
1710
+ if (this.contentSteeringManager_ && someLocationValid) {
1711
+ return this.contentSteeringManager_.getLocations(id);
1712
+ }
1713
+ if (calculatedBaseUris) {
1714
+ return calculatedBaseUris;
1715
+ }
1716
+ return [];
1717
+ };
1567
1718
 
1568
1719
  let contentType = elem.getAttribute('contentType') || parent.contentType;
1569
1720
  const mimeType = elem.getAttribute('mimeType') || parent.mimeType;
@@ -1606,8 +1757,20 @@ shaka.dash.DashParser = class {
1606
1757
  const availabilityTimeOffset = parent.availabilityTimeOffset + baseUriAto +
1607
1758
  segmentBaseAto + segmentTemplateAto;
1608
1759
 
1760
+ let segmentSequenceCadence = null;
1761
+ const segmentSequenceProperties =
1762
+ XmlUtils.findChild(elem, 'SegmentSequenceProperties');
1763
+ if (segmentSequenceProperties) {
1764
+ const sap = XmlUtils.findChild(segmentSequenceProperties, 'SAP');
1765
+ if (sap) {
1766
+ segmentSequenceCadence = XmlUtils.parseAttr(sap, 'cadence',
1767
+ XmlUtils.parseInt);
1768
+ }
1769
+ }
1770
+
1609
1771
  return {
1610
- baseUris: ManifestParserUtils.resolveUris(baseUris, uris),
1772
+ getBaseUris:
1773
+ () => ManifestParserUtils.resolveUris(getBaseUris(), getFrameUris()),
1611
1774
  segmentBase: segmentBase || parent.segmentBase,
1612
1775
  segmentList:
1613
1776
  XmlUtils.findChild(elem, 'SegmentList') || parent.segmentList,
@@ -1620,11 +1783,13 @@ shaka.dash.DashParser = class {
1620
1783
  frameRate: frameRate,
1621
1784
  pixelAspectRatio: pixelAspectRatio,
1622
1785
  emsgSchemeIdUris: emsgSchemeIdUris,
1623
- id: elem.getAttribute('id'),
1786
+ id: id,
1624
1787
  language: elem.getAttribute('lang'),
1625
1788
  numChannels: numChannels,
1626
1789
  audioSamplingRate: audioSamplingRate,
1627
1790
  availabilityTimeOffset: availabilityTimeOffset,
1791
+ segmentSequenceCadence:
1792
+ segmentSequenceCadence || parent.segmentSequenceCadence,
1628
1793
  };
1629
1794
  }
1630
1795
 
@@ -1794,15 +1959,15 @@ shaka.dash.DashParser = class {
1794
1959
  /**
1795
1960
  * Makes a request to the given URI and calculates the clock offset.
1796
1961
  *
1797
- * @param {!Array.<string>} baseUris
1962
+ * @param {function():!Array.<string>} getBaseUris
1798
1963
  * @param {string} uri
1799
1964
  * @param {string} method
1800
1965
  * @return {!Promise.<number>}
1801
1966
  * @private
1802
1967
  */
1803
- async requestForTiming_(baseUris, uri, method) {
1968
+ async requestForTiming_(getBaseUris, uri, method) {
1804
1969
  const requestUris =
1805
- shaka.util.ManifestParserUtils.resolveUris(baseUris, [uri]);
1970
+ shaka.util.ManifestParserUtils.resolveUris(getBaseUris(), [uri]);
1806
1971
  const request = shaka.net.NetworkingEngine.makeRequest(
1807
1972
  requestUris, this.config_.retryParameters);
1808
1973
  request.method = method;
@@ -1835,12 +2000,12 @@ shaka.dash.DashParser = class {
1835
2000
  /**
1836
2001
  * Parses an array of UTCTiming elements.
1837
2002
  *
1838
- * @param {!Array.<string>} baseUris
2003
+ * @param {function():!Array.<string>} getBaseUris
1839
2004
  * @param {!Array.<!Element>} elems
1840
2005
  * @return {!Promise.<number>}
1841
2006
  * @private
1842
2007
  */
1843
- async parseUtcTiming_(baseUris, elems) {
2008
+ async parseUtcTiming_(getBaseUris, elems) {
1844
2009
  const schemesAndValues = elems.map((elem) => {
1845
2010
  return {
1846
2011
  scheme: elem.getAttribute('schemeIdUri'),
@@ -1869,13 +2034,13 @@ shaka.dash.DashParser = class {
1869
2034
  case 'urn:mpeg:dash:utc:http-head:2014':
1870
2035
  case 'urn:mpeg:dash:utc:http-head:2012':
1871
2036
  // eslint-disable-next-line no-await-in-loop
1872
- return await this.requestForTiming_(baseUris, value, 'HEAD');
2037
+ return await this.requestForTiming_(getBaseUris, value, 'HEAD');
1873
2038
  case 'urn:mpeg:dash:utc:http-xsdate:2014':
1874
2039
  case 'urn:mpeg:dash:utc:http-iso:2014':
1875
2040
  case 'urn:mpeg:dash:utc:http-xsdate:2012':
1876
2041
  case 'urn:mpeg:dash:utc:http-iso:2012':
1877
2042
  // eslint-disable-next-line no-await-in-loop
1878
- return await this.requestForTiming_(baseUris, value, 'GET');
2043
+ return await this.requestForTiming_(getBaseUris, value, 'GET');
1879
2044
  case 'urn:mpeg:dash:utc:direct:2014':
1880
2045
  case 'urn:mpeg:dash:utc:direct:2012': {
1881
2046
  const date = Date.parse(value);
@@ -2038,7 +2203,7 @@ shaka.dash.DashParser.RequestSegmentCallback;
2038
2203
  * segmentBase: Element,
2039
2204
  * segmentList: Element,
2040
2205
  * segmentTemplate: Element,
2041
- * baseUris: !Array.<string>,
2206
+ * getBaseUris: function():!Array.<string>,
2042
2207
  * width: (number|undefined),
2043
2208
  * height: (number|undefined),
2044
2209
  * contentType: string,
@@ -2051,7 +2216,8 @@ shaka.dash.DashParser.RequestSegmentCallback;
2051
2216
  * language: ?string,
2052
2217
  * numChannels: ?number,
2053
2218
  * audioSamplingRate: ?number,
2054
- * availabilityTimeOffset: number
2219
+ * availabilityTimeOffset: number,
2220
+ * segmentSequenceCadence: number
2055
2221
  * }}
2056
2222
  *
2057
2223
  * @description
@@ -2064,8 +2230,8 @@ shaka.dash.DashParser.RequestSegmentCallback;
2064
2230
  * The XML node for SegmentList.
2065
2231
  * @property {Element} segmentTemplate
2066
2232
  * The XML node for SegmentTemplate.
2067
- * @property {!Array.<string>} baseUris
2068
- * An array of absolute base URIs for the frame.
2233
+ * @property {function():!Array.<string>} getBaseUris
2234
+ * Function than returns an array of absolute base URIs for the frame.
2069
2235
  * @property {(number|undefined)} width
2070
2236
  * The inherited width value.
2071
2237
  * @property {(number|undefined)} height
@@ -2092,6 +2258,9 @@ shaka.dash.DashParser.RequestSegmentCallback;
2092
2258
  * Specifies the maximum sampling rate of the content, or null if unknown.
2093
2259
  * @property {number} availabilityTimeOffset
2094
2260
  * Specifies the total availabilityTimeOffset of the segment, or 0 if unknown.
2261
+ * @property {number} segmentSequenceCadence
2262
+ * Specifies the cadence of independent segments in Segment Sequence
2263
+ * Representation.
2095
2264
  */
2096
2265
  shaka.dash.DashParser.InheritanceFrame;
2097
2266
 
@@ -2225,8 +2394,6 @@ shaka.dash.DashParser.GenerateSegmentIndexFunction;
2225
2394
  shaka.dash.DashParser.StreamInfo;
2226
2395
 
2227
2396
 
2228
- shaka.media.ManifestParser.registerParserByExtension(
2229
- 'mpd', () => new shaka.dash.DashParser());
2230
2397
  shaka.media.ManifestParser.registerParserByMime(
2231
2398
  'application/dash+xml', () => new shaka.dash.DashParser());
2232
2399
  shaka.media.ManifestParser.registerParserByMime(