shaka-player 4.3.13 → 4.3.15

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 (137) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/question.md +7 -0
  3. package/.github/workflows/release-please.yaml +2 -27
  4. package/.github/workflows/shaka-bot-commands/lib.sh +37 -12
  5. package/.github/workflows/shaka-bot-commands/main.sh +4 -2
  6. package/.github/workflows/talk-to-shaka-bot.yaml +1 -0
  7. package/AUTHORS +5 -0
  8. package/CHANGELOG.md +96 -0
  9. package/CODE_OF_CONDUCT.md +1 -1
  10. package/CONTRIBUTORS +6 -0
  11. package/README.md +2 -0
  12. package/demo/common/asset.js +0 -1
  13. package/demo/common/assets.js +0 -1
  14. package/demo/custom.js +2 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +1 -1
  18. package/dist/demo.compiled.debug.map +1 -1
  19. package/dist/demo.compiled.js +1 -1
  20. package/dist/demo.compiled.map +1 -1
  21. package/dist/deps.js +6 -6
  22. package/dist/locales.js +30 -30
  23. package/dist/receiver.compiled.debug.map +1 -1
  24. package/dist/receiver.compiled.map +1 -1
  25. package/dist/shaka-player.compiled.d.ts +25 -5
  26. package/dist/shaka-player.compiled.debug.d.ts +25 -5
  27. package/dist/shaka-player.compiled.debug.externs.js +73 -45
  28. package/dist/shaka-player.compiled.debug.js +426 -406
  29. package/dist/shaka-player.compiled.debug.map +4 -4
  30. package/dist/shaka-player.compiled.externs.js +73 -45
  31. package/dist/shaka-player.compiled.js +912 -903
  32. package/dist/shaka-player.compiled.map +4 -4
  33. package/dist/shaka-player.ui.d.ts +37 -6
  34. package/dist/shaka-player.ui.debug.d.ts +37 -6
  35. package/dist/shaka-player.ui.debug.externs.js +74 -45
  36. package/dist/shaka-player.ui.debug.js +449 -428
  37. package/dist/shaka-player.ui.debug.map +4 -4
  38. package/dist/shaka-player.ui.externs.js +74 -45
  39. package/dist/shaka-player.ui.js +1060 -1051
  40. package/dist/shaka-player.ui.map +4 -4
  41. package/docs/tutorials/ad_monetization.md +54 -0
  42. package/docs/tutorials/fairplay.md +2 -2
  43. package/docs/tutorials/index.json +1 -0
  44. package/docs/tutorials/plugins.md +4 -0
  45. package/docs/tutorials/text-displayer.md +15 -0
  46. package/docs/tutorials/ui-customization.md +29 -0
  47. package/docs/tutorials/ui.md +4 -1
  48. package/externs/lcevc.js +6 -0
  49. package/externs/shaka/manifest_parser.js +8 -0
  50. package/externs/shaka/player.js +1 -1
  51. package/externs/shaka/text.js +3 -1
  52. package/lib/abr/simple_abr_manager.js +1 -1
  53. package/lib/ads/client_side_ad.js +9 -0
  54. package/lib/ads/client_side_ad_manager.js +7 -2
  55. package/lib/ads/server_side_ad_manager.js +4 -0
  56. package/lib/cast/cast_proxy.js +18 -0
  57. package/lib/cea/cea608_memory.js +1 -1
  58. package/lib/cea/cea_decoder.js +11 -1
  59. package/lib/dash/dash_parser.js +75 -41
  60. package/lib/dash/mpd_utils.js +39 -27
  61. package/lib/deprecate/deprecate.js +1 -1
  62. package/lib/hls/hls_parser.js +106 -28
  63. package/lib/hls/hls_utils.js +5 -5
  64. package/lib/media/content_workarounds.js +30 -0
  65. package/lib/media/drm_engine.js +16 -12
  66. package/lib/media/manifest_parser.js +2 -1
  67. package/lib/media/media_source_engine.js +4 -2
  68. package/lib/media/play_rate_controller.js +1 -0
  69. package/lib/media/presentation_timeline.js +11 -0
  70. package/lib/media/quality_observer.js +1 -1
  71. package/lib/media/segment_reference.js +5 -2
  72. package/lib/media/streaming_engine.js +50 -30
  73. package/lib/media/transmuxer.js +1 -1
  74. package/lib/media/video_wrapper.js +4 -3
  75. package/lib/offline/offline_manifest_parser.js +5 -0
  76. package/lib/offline/session_deleter.js +0 -8
  77. package/lib/offline/storage.js +4 -1
  78. package/lib/player.js +80 -19
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/polyfill/mediasource.js +5 -36
  81. package/lib/text/mp4_ttml_parser.js +2 -2
  82. package/lib/text/simple_text_displayer.js +7 -6
  83. package/lib/text/srt_text_parser.js +2 -2
  84. package/lib/text/text_engine.js +3 -2
  85. package/lib/text/ttml_text_parser.js +33 -5
  86. package/lib/text/ui_text_displayer.js +81 -15
  87. package/lib/text/vtt_text_parser.js +17 -3
  88. package/lib/util/buffer_utils.js +26 -4
  89. package/lib/util/cmcd_manager.js +10 -0
  90. package/lib/util/error.js +1 -0
  91. package/lib/util/fairplay_utils.js +1 -1
  92. package/lib/util/manifest_parser_utils.js +14 -8
  93. package/lib/util/object_utils.js +1 -1
  94. package/lib/util/periods.js +91 -36
  95. package/lib/util/platform.js +39 -2
  96. package/lib/util/player_configuration.js +5 -3
  97. package/lib/util/stream_utils.js +36 -28
  98. package/lib/util/xml_utils.js +5 -0
  99. package/package.json +3 -3
  100. package/test/cea/cea_decoder_unit.js +40 -0
  101. package/test/dash/dash_parser_live_unit.js +5 -8
  102. package/test/hls/hls_parser_unit.js +78 -3
  103. package/test/media/buffering_observer_unit.js +13 -0
  104. package/test/media/content_workarounds_unit.js +1 -1
  105. package/test/media/drm_engine_unit.js +30 -3
  106. package/test/media/media_source_engine_unit.js +1 -1
  107. package/test/media/transmuxer_integration.js +2 -2
  108. package/test/offline/storage_integration.js +3 -0
  109. package/test/player_integration.js +49 -41
  110. package/test/player_load_graph_integration.js +13 -7
  111. package/test/player_src_equals_integration.js +2 -2
  112. package/test/player_unit.js +40 -3
  113. package/test/polyfill/media_capabilities_unit.js +12 -6
  114. package/test/test/util/fake_drm_engine.js +7 -0
  115. package/test/test/util/layout_tests.js +2 -1
  116. package/test/test/util/simple_fakes.js +2 -2
  117. package/test/test/util/test_scheme.js +3 -0
  118. package/test/test/util/waiter.js +5 -0
  119. package/test/text/mp4_ttml_parser_unit.js +4 -4
  120. package/test/text/simple_text_displayer_unit.js +1 -1
  121. package/test/text/srt_text_parser_unit.js +1 -1
  122. package/test/text/text_engine_unit.js +6 -0
  123. package/test/text/ttml_text_parser_unit.js +49 -18
  124. package/test/text/ui_text_displayer_unit.js +9 -0
  125. package/test/text/vtt_text_parser_unit.js +31 -0
  126. package/test/util/buffer_utils_unit.js +1 -1
  127. package/test/util/periods_unit.js +113 -0
  128. package/third_party/closure-uri/uri.js +1 -1
  129. package/ui/externs/ui.js +24 -0
  130. package/ui/language_utils.js +11 -5
  131. package/ui/less/containers.less +1 -0
  132. package/ui/play_button.js +20 -0
  133. package/ui/playback_rate_selection.js +4 -0
  134. package/ui/presentation_time.js +5 -5
  135. package/ui/small_play_button.js +2 -2
  136. package/ui/volume_bar.js +10 -6
  137. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -7,6 +7,60 @@ other ad providers in v3.1+.
7
7
  Please note that the current API is likely to undergo significant
8
8
  changes as our support extends.
9
9
 
10
+ #### AWS Elemental MediaTailor Integration
11
+
12
+ Shaka Player provides an integration with the [AWS Elemental MediaTailor][].
13
+ We support Client Side, Server Side and overlays ad insertion.
14
+
15
+ [AWS Elemental MediaTailor]: https://aws.amazon.com/mediatailor/
16
+
17
+ All ad insertion experiences are available through the
18
+ {@linksource shaka.extern.IAdManager} object on the Player.
19
+
20
+ If you're not using Shaka's UI library, you will
21
+ also need to create a `<div>` over your video element to serve as an ad
22
+ container.
23
+
24
+ Start by initializing the server side logic.
25
+ With Shaka UI:
26
+
27
+ ```js
28
+ const video = document.getElementById('video');
29
+ const ui = video['ui'];
30
+ const controls = video.ui.getControls();
31
+ // If you're using a non-UI build, this is the div you'll need to create
32
+ // for your layout.
33
+ const container = controls.getServerSideAdContainer();
34
+ const player = controls.getPlayer();
35
+ const netEngine = player.getNetworkingEngine();
36
+ const adManager = player.getAdManager();
37
+ adManager.initMediaTailor(container, netEngine, video);
38
+ ```
39
+
40
+ Requesting a Client Side stream:
41
+
42
+ ```js
43
+ const mediaTailorUrl = 'https://d305rncpy6ne2q.cloudfront.net/v1/session/94063eadf7d8c56e9e2edd84fdf897826a70d0df/SFP-MediaTailor-VOD-HLS-DASH/out/v1/b94f3611978f419985a18335bac9d9cb/ddb73bf548a44551a0059c346226445a/eaa5485198bf497284559efb8172425e/index.mpd';
44
+ const mediaTailorAdsParams = {
45
+ adsParams: {
46
+ assetid: 'test2',
47
+ podduration: '15',
48
+ },
49
+ };
50
+ const uri = await adManager.requestMediaTailorStream(mediaTailorUrl, mediaTailorAdsParams);
51
+ player.load(uri);
52
+ ```
53
+
54
+ Requesting a Server Side stream:
55
+
56
+ ```js
57
+ const mediaTailorUrl = 'https://ad391cc0d55b44c6a86d232548adc225.mediatailor.us-east-1.amazonaws.com/v1/session/d02fedbbc5a68596164208dd24e9b48aa60dadc7/singssai/master.m3u8';
58
+ const uri = await adManager.requestMediaTailorStream(mediaTailorUrl);
59
+ player.load(uri);
60
+ ```
61
+
62
+ Note: overlays ad insertions is the same as server side.
63
+
10
64
  #### IMA SDK Integration
11
65
 
12
66
  Shaka Player provides an integration with the [Interactive Media Ads][] SDKs.
@@ -98,9 +98,9 @@ player.getNetworkingEngine().registerRequestFilter((type, request) => {
98
98
  const originalPayload = new Uint8Array(request.body);
99
99
  const base64Payload =
100
100
  shaka.util.Uint8ArrayUtils.toStandardBase64(originalPayload);
101
- const params = 'spc=' + base64Payload;
101
+ const params = 'spc=' + encodeURIComponent(base64Payload);
102
102
  request.headers['Content-Type'] = 'application/x-www-form-urlencoded';
103
- request.body = shaka.util.StringUtils.toUTF8(encodeURIComponent(params));
103
+ request.body = shaka.util.StringUtils.toUTF8(params);
104
104
  });
105
105
 
106
106
  player.getNetworkingEngine().registerResponseFilter((type, response) => {
@@ -9,6 +9,7 @@
9
9
  { "license-wrapping": { "title": "License Wrapping" } },
10
10
  { "ui": { "title": "UI Library" } },
11
11
  { "ui-customization": { "title": "Configuring the UI" } },
12
+ { "text-displayer": { "title": "Configuring text displayer" } },
12
13
  { "errors": { "title": "Error Handling" } },
13
14
  { "a11y": { "title": "Creating accessible buttons" } },
14
15
  { "ad_monetization": { "title": "Serving ads with our IMA SDK integration" } },
@@ -38,6 +38,10 @@ __Subtitle/caption parsers__
38
38
  {@linksource shaka.text.Mp4VttParser}
39
39
  - TTML: {@linksource shaka.text.TtmlTextParser} and
40
40
  {@linksource shaka.text.Mp4TtmlParser}
41
+ - SubRip (SRT): {@linksource shaka.text.SrtTextParser}
42
+ - LyRiCs (LRC): {@linksource shaka.text.LrcTextParser}
43
+ - SubStation Alpha (SSA, ASS): {@linksource shaka.text.SsaTextParser}
44
+ - SubViewer (SBV): {@linksource shaka.text.SbvTextParser}
41
45
 
42
46
  __Subtitle/caption displayers__
43
47
  - Configured at runtime on a Player instance
@@ -0,0 +1,15 @@
1
+ # Configuring text displayer`
2
+
3
+ Shaka Player supports two types of Text Displayer.
4
+ 1) Using the browser's native API, Shaka Player creates a textTrack along
5
+ with the AUDIO or VIDEO Element.
6
+ 2) Rendering the subtitles in a container in the DOM.
7
+ 3) Providing a custom text displayer factory would allow a developer to make
8
+ their own custom text displayer that fits neither category.
9
+
10
+ By default, if not configured otherwise, the player will use type 1.
11
+ To configure type 2, you have to call `setVideoContainer` function before
12
+ making `load` call in the player.
13
+
14
+ Note: The UI calls setVideoContainer for you if the video's controls are
15
+ disabled, so this isn't always necessary when using the UI.
@@ -265,3 +265,32 @@ PR contributions to [the gallery repo][] are welcome.
265
265
  [@lucksy]: https://github.com/lucksy
266
266
  [pre-packaged Shaka UI themes]: https://lucksy.github.io/shaka-player-themes/
267
267
  [the gallery repo]: https://github.com/lucksy/shaka-player-themes
268
+
269
+ #### Add custom localization
270
+
271
+ Load specific locale data at runtime (adjust the URL and language as needed):
272
+ ```js
273
+ const locale = 'el';
274
+ const controls = ui.getControls();
275
+ const localization = controls.getLocalization();
276
+ const response = await fetch('ui/locales/' + locale + '.json'); // <----- JSON translation URL here
277
+ const translations = await response.json();
278
+ const translation_map = new Map(Object.entries(translations));
279
+ localization.insert(locale, translation_map);
280
+ ```
281
+
282
+ Lazy-load any requested locale data at runtime (adjust the URL as needed):
283
+ ```js
284
+ const controls = ui.getControls();
285
+ const localization = controls.getLocalization();
286
+
287
+ localization.addEventListener('unknown-locales', async (e) => {
288
+ for (const locale of e.locales) {
289
+ const response = await fetch('ui/locales/' + locale + '.json'); // <----- JSON translation URL here
290
+ const translations = await response.json();
291
+ const translation_map = new Map(Object.entries(translations));
292
+ localization.insert(locale, translation_map);
293
+ }
294
+ });
295
+ ```
296
+
@@ -203,11 +203,14 @@ constructor.
203
203
  ```js
204
204
  // "local" because it is for local playback only, as opposed to the player proxy
205
205
  // object, which will route your calls to the ChromeCast receiver as necessary.
206
- const localPlayer = new shaka.Player(videoElement);
206
+ const localPlayer = new shaka.Player();
207
207
  // "Overlay" because the UI will add DOM elements inside the container,
208
208
  // to visually overlay the video element
209
209
  const ui = new shaka.ui.Overlay(localPlayer, videoContainerElement,
210
210
  videoElement);
211
+ // Now that the player has been configured to be part of a UI, attach it to the
212
+ // video.
213
+ await localPlayer.attach(videoElement);
211
214
 
212
215
  // As with DOM-based setup, get access to the UI controls and player from the
213
216
  // UI.
package/externs/lcevc.js CHANGED
@@ -1,3 +1,9 @@
1
+ /*! @license
2
+ * Shaka Player
3
+ * Copyright 2023 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
1
7
  /**
2
8
  * @fileoverview Externs for LcevcDil
3
9
  * compiler.
@@ -94,6 +94,14 @@ shaka.extern.ManifestParser = class {
94
94
  * @exportDoc
95
95
  */
96
96
  onExpirationUpdated(sessionId, expiration) {}
97
+
98
+ /**
99
+ * Tell the parser that the initial variant has been chosen.
100
+ *
101
+ * @param {shaka.extern.Variant} variant
102
+ * @exportDoc
103
+ */
104
+ onInitialVariantChosen(variant) {}
97
105
  };
98
106
 
99
107
 
@@ -561,7 +561,7 @@ shaka.extern.TimelineRegionInfo;
561
561
  * @property {?number} channelsCount
562
562
  * The number of audio channels, or null if unknown.
563
563
  * @property {?string} pixelAspectRatio
564
- * The pixel aspect ratio value; e.g "1:1".
564
+ * The pixel aspect ratio value; e.g. "1:1".
565
565
  * @property {?number} width
566
566
  * The video width in pixels.
567
567
  * @exportDoc
@@ -417,11 +417,13 @@ shaka.extern.TextParser = class {
417
417
  * @param {shaka.extern.TextParser.TimeContext} timeContext
418
418
  * The time information that should be used to adjust the times values
419
419
  * for each cue.
420
+ * @param {?(string|undefined)} uri
421
+ * The media uri.
420
422
  * @return {!Array.<!shaka.extern.Cue>}
421
423
  *
422
424
  * @exportDoc
423
425
  */
424
- parseMedia(data, timeContext) {}
426
+ parseMedia(data, timeContext, uri) {}
425
427
 
426
428
  /**
427
429
  * Notifies the stream if the manifest is in sequence mode or not.
@@ -63,7 +63,7 @@ shaka.abr.SimpleAbrManager = class {
63
63
  this.bandwidthEstimator_.configure(this.config_.advanced);
64
64
  }
65
65
  const chosenVariant = this.chooseVariant();
66
- if (chosenVariant) {
66
+ if (chosenVariant && navigator.onLine) {
67
67
  this.switch_(chosenVariant);
68
68
  }
69
69
  }
@@ -207,6 +207,15 @@ shaka.ads.ClientSideAd = class {
207
207
  }
208
208
  }
209
209
 
210
+ /**
211
+ * It's required for a muted ad to start when autoplaying.
212
+ *
213
+ * @param {number} videoVolume
214
+ */
215
+ setInitialMuted(videoVolume) {
216
+ this.volume_ = videoVolume;
217
+ this.manager_.setVolume(0);
218
+ }
210
219
 
211
220
  /**
212
221
  * @override
@@ -13,6 +13,10 @@ goog.provide('shaka.ads.ClientSideAdManager');
13
13
 
14
14
  goog.require('goog.asserts');
15
15
  goog.require('shaka.ads.ClientSideAd');
16
+ goog.require('shaka.log');
17
+ goog.require('shaka.util.Dom');
18
+ goog.require('shaka.util.EventManager');
19
+ goog.require('shaka.util.FakeEvent');
16
20
  goog.require('shaka.util.IReleasable');
17
21
 
18
22
  /**
@@ -452,9 +456,10 @@ shaka.ads.ClientSideAdManager = class {
452
456
  if (this.ad_.isLinear()) {
453
457
  this.adContainer_.setAttribute('ad-active', 'true');
454
458
  this.video_.pause();
455
- this.ad_.setVolume(this.video_.volume);
456
459
  if (this.video_.muted) {
457
- this.ad_.setMuted(true);
460
+ this.ad_.setInitialMuted(this.video_.volume);
461
+ } else {
462
+ this.ad_.setVolume(this.video_.volume);
458
463
  }
459
464
  }
460
465
  }
@@ -14,7 +14,11 @@ goog.provide('shaka.ads.ServerSideAdManager');
14
14
  goog.require('goog.asserts');
15
15
  goog.require('shaka.ads.ServerSideAd');
16
16
  goog.require('shaka.log');
17
+ goog.require('shaka.util.EventManager');
18
+ goog.require('shaka.util.Error');
19
+ goog.require('shaka.util.FakeEvent');
17
20
  goog.require('shaka.util.IReleasable');
21
+ goog.require('shaka.util.PublicPromise');
18
22
 
19
23
 
20
24
  /**
@@ -496,6 +496,16 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
496
496
  startTime = this.sender_.get('video', 'currentTime');
497
497
  }
498
498
 
499
+ let activeTextTrack;
500
+ const textTracks = this.sender_.get('player', 'getTextTracks')();
501
+
502
+ if (textTracks && textTracks.length) {
503
+ activeTextTrack = textTracks.find((t) => t.active);
504
+ }
505
+
506
+ const isTextTrackVisible =
507
+ this.sender_.get('player', 'isTextTrackVisible')();
508
+
499
509
  // Now load the manifest, if present.
500
510
  if (assetUri) {
501
511
  // Don't autoplay the content until we finish setting up initial state.
@@ -527,6 +537,14 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
527
537
  /** @type {Object} */(this.localPlayer_)[setter](value);
528
538
  }
529
539
 
540
+ this.localPlayer_.setTextTrackVisibility(isTextTrackVisible);
541
+ if (activeTextTrack) {
542
+ this.localPlayer_.selectTextLanguage(
543
+ activeTextTrack.language,
544
+ activeTextTrack.roles,
545
+ activeTextTrack.forced);
546
+ }
547
+
530
548
  // Restore the original autoplay setting.
531
549
  this.localVideo_.autoplay = autoplay;
532
550
  if (assetUri) {
@@ -80,7 +80,7 @@ shaka.cea.Cea608Memory = class {
80
80
  * @return {?shaka.cea.ICaptionDecoder.ClosedCaption}
81
81
  */
82
82
  forceEmit(startTime, endTime) {
83
- const stream = `CC${(this.fieldNum_<< 1) | this.channelNum_ +1}`;
83
+ const stream = `CC${((this.fieldNum_<< 1) | this.channelNum_) + 1}`;
84
84
  const topLevelCue = new shaka.text.Cue(
85
85
  startTime, endTime, /* payload= */ '');
86
86
  return shaka.cea.CeaUtils.getParsedCaption(
@@ -117,6 +117,9 @@ shaka.cea.CeaDecoder = class {
117
117
  const reader = new shaka.util.DataViewReader(
118
118
  userDataSeiMessage, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
119
119
 
120
+ if (reader.getLength() < shaka.cea.CeaDecoder.MIN_LENGTH) {
121
+ return;
122
+ }
120
123
  if (reader.readUint8() !== shaka.cea.CeaDecoder.USA_COUNTRY_CODE) {
121
124
  return;
122
125
  }
@@ -260,7 +263,7 @@ shaka.cea.CeaDecoder = class {
260
263
  // Get the correct stream for this caption packet (CC1, ..., CC4)
261
264
  const selectedChannel = fieldNum ?
262
265
  this.currentField2Channel_ : this.currentField1Channel_;
263
- const selectedMode = `CC${(fieldNum << 1) | selectedChannel + 1}`;
266
+ const selectedMode = `CC${((fieldNum << 1) | selectedChannel) + 1}`;
264
267
  const selectedStream = this.cea608ModeToStream_.get(selectedMode);
265
268
 
266
269
  // Check for bad frames (bad pairs). This can be two 0xff, two 0x00, or any
@@ -402,3 +405,10 @@ shaka.cea.CeaDecoder.NTSC_CC_FIELD_2 = 1;
402
405
  * @private @const {number}
403
406
  */
404
407
  shaka.cea.CeaDecoder.USA_COUNTRY_CODE = 0xb5;
408
+
409
+ /**
410
+ * Caption packet min length
411
+ * Country Code + ATSC_PROVIDER_CODE + ATSC_1_USER_IDENTIFIER + USER_DATA_TYPE
412
+ * @private @const {number}
413
+ */
414
+ shaka.cea.CeaDecoder.MIN_LENGTH = 8;
@@ -213,6 +213,33 @@ shaka.dash.DashParser = class {
213
213
  // No-op
214
214
  }
215
215
 
216
+ /**
217
+ * @override
218
+ * @exportInterface
219
+ */
220
+ onInitialVariantChosen(variant) {
221
+ // For live it is necessary that the first time we update the manifest with
222
+ // a shorter time than indicated to take into account that the last segment
223
+ // added could be halfway, for example
224
+ if (this.manifest_ && this.manifest_.presentationTimeline.isLive()) {
225
+ const stream = variant.video || variant.audio;
226
+ if (stream && stream.segmentIndex) {
227
+ const availabilityEnd =
228
+ this.manifest_.presentationTimeline.getSegmentAvailabilityEnd();
229
+ const position = stream.segmentIndex.find(availabilityEnd);
230
+ if (position == null) {
231
+ return;
232
+ }
233
+ const reference = stream.segmentIndex.get(position);
234
+ if (!reference) {
235
+ return;
236
+ }
237
+ this.updatePeriod_ = reference.endTime - availabilityEnd;
238
+ this.setUpdateTimer_(/* offset= */ 0);
239
+ }
240
+ }
241
+ }
242
+
216
243
  /**
217
244
  * Makes a network request for the manifest and parses the resulting data.
218
245
  *
@@ -242,7 +269,8 @@ shaka.dash.DashParser = class {
242
269
 
243
270
  // For redirections add the response uri to the first entry in the
244
271
  // Manifest Uris array.
245
- if (response.uri && !this.manifestUris_.includes(response.uri)) {
272
+ if (response.uri && response.uri != response.originalUri &&
273
+ !this.manifestUris_.includes(response.uri)) {
246
274
  this.manifestUris_.unshift(response.uri);
247
275
  }
248
276
 
@@ -489,6 +517,12 @@ shaka.dash.DashParser = class {
489
517
 
490
518
  await this.periodCombiner_.combinePeriods(periods, context.dynamic);
491
519
 
520
+ // Set minBufferTime to 0 for low-latency DASH live stream to achieve the
521
+ // best latency
522
+ if (this.lowLatencyMode_) {
523
+ minBufferTime = 0;
524
+ }
525
+
492
526
  // These steps are not done on manifest update.
493
527
  if (!this.manifest_) {
494
528
  this.manifest_ = {
@@ -780,40 +814,29 @@ shaka.dash.DashParser = class {
780
814
  }
781
815
  }
782
816
 
783
- const audioSets = this.config_.disableAudio ? [] :
784
- this.getSetsOfType_(normalAdaptationSets, ContentType.AUDIO);
785
- const videoSets = this.config_.disableVideo ? [] :
786
- this.getSetsOfType_(normalAdaptationSets, ContentType.VIDEO);
787
- const textSets = this.config_.disableText ? [] :
788
- this.getSetsOfType_(normalAdaptationSets, ContentType.TEXT);
789
- const imageSets = this.config_.disableThumbnails ? [] :
790
- this.getSetsOfType_(normalAdaptationSets, ContentType.IMAGE);
791
-
792
- if (!videoSets.length && !audioSets.length) {
817
+ const audioStreams = this.getStreamsFromSets_(
818
+ this.config_.disableAudio,
819
+ normalAdaptationSets,
820
+ ContentType.AUDIO);
821
+ const videoStreams = this.getStreamsFromSets_(
822
+ this.config_.disableVideo,
823
+ normalAdaptationSets,
824
+ ContentType.VIDEO);
825
+ const textStreams = this.getStreamsFromSets_(
826
+ this.config_.disableText,
827
+ normalAdaptationSets,
828
+ ContentType.TEXT);
829
+ const imageStreams = this.getStreamsFromSets_(
830
+ this.config_.disableThumbnails,
831
+ normalAdaptationSets,
832
+ ContentType.IMAGE);
833
+
834
+ if (videoStreams.length === 0 && audioStreams.length === 0) {
793
835
  throw new shaka.util.Error(
794
836
  shaka.util.Error.Severity.CRITICAL,
795
837
  shaka.util.Error.Category.MANIFEST,
796
- shaka.util.Error.Code.DASH_EMPTY_PERIOD);
797
- }
798
-
799
- const audioStreams = [];
800
- for (const audioSet of audioSets) {
801
- audioStreams.push(...audioSet.streams);
802
- }
803
-
804
- const videoStreams = [];
805
- for (const videoSet of videoSets) {
806
- videoStreams.push(...videoSet.streams);
807
- }
808
-
809
- const textStreams = [];
810
- for (const textSet of textSets) {
811
- textStreams.push(...textSet.streams);
812
- }
813
-
814
- const imageStreams = [];
815
- for (const imageSet of imageSets) {
816
- imageStreams.push(...imageSet.streams);
838
+ shaka.util.Error.Code.DASH_EMPTY_PERIOD,
839
+ );
817
840
  }
818
841
 
819
842
  return {
@@ -826,15 +849,26 @@ shaka.dash.DashParser = class {
826
849
  }
827
850
 
828
851
  /**
852
+ * Gets the streams from the given sets or returns an empty array if disabled
853
+ * or no streams are found.
854
+ * @param {boolean} disabled
829
855
  * @param {!Array.<!shaka.dash.DashParser.AdaptationInfo>} adaptationSets
830
- * @param {string} type
831
- * @return {!Array.<!shaka.dash.DashParser.AdaptationInfo>}
832
- * @private
833
- */
834
- getSetsOfType_(adaptationSets, type) {
835
- return adaptationSets.filter((as) => {
836
- return as.contentType == type;
837
- });
856
+ * @param {string} contentType
857
+ @private
858
+ */
859
+ getStreamsFromSets_(disabled, adaptationSets, contentType) {
860
+ if (disabled || !adaptationSets.length) {
861
+ return [];
862
+ }
863
+
864
+ return adaptationSets.reduce((all, part) => {
865
+ if (part.contentType != contentType) {
866
+ return all;
867
+ }
868
+
869
+ all.push(...part.streams);
870
+ return all;
871
+ }, []);
838
872
  }
839
873
 
840
874
  /**
@@ -1407,7 +1441,7 @@ shaka.dash.DashParser = class {
1407
1441
  // NOTE: An updatePeriod_ of -1 means the attribute was missing.
1408
1442
  // An attribute which is present and set to 0 should still result in
1409
1443
  // periodic updates. For more, see:
1410
- // https://github.com/shaka-project/shaka-player/issues/331
1444
+ // https://github.com/Dash-Industry-Forum/Guidelines-TimingModel/issues/48
1411
1445
  if (this.updatePeriod_ < 0) {
1412
1446
  return;
1413
1447
  }
@@ -310,30 +310,46 @@ shaka.dash.MpdUtils = class {
310
310
  }
311
311
 
312
312
  /**
313
- * Searches the inheritance for a Segment* with the given attribute.
314
- *
313
+ * Parses common attributes for Representation, AdaptationSet, and Period.
315
314
  * @param {shaka.dash.DashParser.Context} context
316
315
  * @param {function(?shaka.dash.DashParser.InheritanceFrame):Element} callback
317
- * Gets the Element that contains the attribute to inherit.
318
- * @param {string} attribute
319
- * @return {?string}
320
- */
321
- static inheritAttribute(context, callback, attribute) {
316
+ * @return {!Array.<!Element>}
317
+ */
318
+ static getNodes(context, callback) {
322
319
  const Functional = shaka.util.Functional;
323
320
  goog.asserts.assert(
324
321
  callback(context.representation),
325
- 'There must be at least one element of the given type');
322
+ 'There must be at least one element of the given type',
323
+ );
326
324
 
327
- /** @type {!Array.<!Element>} */
328
- const nodes = [
325
+ return [
329
326
  callback(context.representation),
330
327
  callback(context.adaptationSet),
331
328
  callback(context.period),
332
329
  ].filter(Functional.isNotNull);
330
+ }
333
331
 
334
- return nodes
335
- .map((s) => { return s.getAttribute(attribute); })
336
- .reduce((all, part) => { return all || part; });
332
+ /**
333
+ * Searches the inheritance for a Segment* with the given attribute.
334
+ *
335
+ * @param {shaka.dash.DashParser.Context} context
336
+ * @param {function(?shaka.dash.DashParser.InheritanceFrame):Element} callback
337
+ * Gets the Element that contains the attribute to inherit.
338
+ * @param {string} attribute
339
+ * @return {?string}
340
+ */
341
+ static inheritAttribute(context, callback, attribute) {
342
+ const MpdUtils = shaka.dash.MpdUtils;
343
+ const nodes = MpdUtils.getNodes(context, callback);
344
+
345
+ let result = null;
346
+ for (const node of nodes) {
347
+ result = node.getAttribute(attribute);
348
+ if (result) {
349
+ break;
350
+ }
351
+ }
352
+ return result;
337
353
  }
338
354
 
339
355
  /**
@@ -346,22 +362,18 @@ shaka.dash.MpdUtils = class {
346
362
  * @return {Element}
347
363
  */
348
364
  static inheritChild(context, callback, child) {
349
- const Functional = shaka.util.Functional;
350
- goog.asserts.assert(
351
- callback(context.representation),
352
- 'There must be at least one element of the given type');
353
-
354
- /** @type {!Array.<!Element>} */
355
- const nodes = [
356
- callback(context.representation),
357
- callback(context.adaptationSet),
358
- callback(context.period),
359
- ].filter(Functional.isNotNull);
365
+ const MpdUtils = shaka.dash.MpdUtils;
366
+ const nodes = MpdUtils.getNodes(context, callback);
360
367
 
361
368
  const XmlUtils = shaka.util.XmlUtils;
362
- return nodes
363
- .map((s) => { return XmlUtils.findChild(s, child); })
364
- .reduce((all, part) => { return all || part; });
369
+ let result = null;
370
+ for (const node of nodes) {
371
+ result = XmlUtils.findChild(node, child);
372
+ if (result) {
373
+ break;
374
+ }
375
+ }
376
+ return result;
365
377
  }
366
378
 
367
379
  /**
@@ -107,7 +107,7 @@ shaka.Deprecate = class {
107
107
  libraryVersion,
108
108
  '. Additional information:',
109
109
  description,
110
- ].join('');
110
+ ].join(' ');
111
111
 
112
112
  shaka.log.alwaysError(errorMessage);
113
113
  goog.asserts.assert(false, errorMessage);