shaka-player 4.3.12 → 4.3.14

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 (140) 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/AUTHORS +5 -0
  5. package/CHANGELOG.md +108 -0
  6. package/CODE_OF_CONDUCT.md +1 -1
  7. package/CONTRIBUTORS +6 -0
  8. package/README.md +2 -0
  9. package/demo/common/asset.js +5 -1
  10. package/demo/common/assets.js +16 -2
  11. package/demo/common/message_ids.js +1 -0
  12. package/demo/custom.js +2 -0
  13. package/demo/locales/en.json +1 -0
  14. package/demo/locales/source.json +4 -0
  15. package/demo/main.js +1 -1
  16. package/dist/demo.compiled.debug.js +56 -54
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +56 -54
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +6 -6
  21. package/dist/locales.js +31 -31
  22. package/dist/receiver.compiled.debug.js +24 -22
  23. package/dist/receiver.compiled.debug.map +3 -3
  24. package/dist/receiver.compiled.js +24 -22
  25. package/dist/receiver.compiled.map +3 -3
  26. package/dist/shaka-player.compiled.d.ts +26 -6
  27. package/dist/shaka-player.compiled.debug.d.ts +26 -6
  28. package/dist/shaka-player.compiled.debug.externs.js +79 -49
  29. package/dist/shaka-player.compiled.debug.js +426 -410
  30. package/dist/shaka-player.compiled.debug.map +4 -4
  31. package/dist/shaka-player.compiled.externs.js +79 -49
  32. package/dist/shaka-player.compiled.js +912 -901
  33. package/dist/shaka-player.compiled.map +4 -4
  34. package/dist/shaka-player.ui.d.ts +38 -7
  35. package/dist/shaka-player.ui.debug.d.ts +38 -7
  36. package/dist/shaka-player.ui.debug.externs.js +80 -49
  37. package/dist/shaka-player.ui.debug.js +460 -442
  38. package/dist/shaka-player.ui.debug.map +4 -4
  39. package/dist/shaka-player.ui.externs.js +80 -49
  40. package/dist/shaka-player.ui.js +1060 -1049
  41. package/dist/shaka-player.ui.map +4 -4
  42. package/docs/tutorials/ad_monetization.md +54 -0
  43. package/docs/tutorials/fairplay.md +2 -2
  44. package/docs/tutorials/index.json +1 -0
  45. package/docs/tutorials/offline.md +1 -1
  46. package/docs/tutorials/plugins.md +4 -0
  47. package/docs/tutorials/text-displayer.md +15 -0
  48. package/docs/tutorials/ui-customization.md +29 -0
  49. package/docs/tutorials/ui.md +4 -1
  50. package/externs/lcevc.js +6 -0
  51. package/externs/shaka/manifest_parser.js +8 -0
  52. package/externs/shaka/player.js +1 -1
  53. package/externs/shaka/text.js +3 -1
  54. package/lib/abr/simple_abr_manager.js +1 -1
  55. package/lib/ads/client_side_ad.js +9 -0
  56. package/lib/ads/client_side_ad_manager.js +7 -2
  57. package/lib/ads/server_side_ad_manager.js +4 -0
  58. package/lib/cast/cast_proxy.js +18 -0
  59. package/lib/cea/cea608_memory.js +1 -1
  60. package/lib/cea/cea_decoder.js +11 -1
  61. package/lib/dash/dash_parser.js +79 -42
  62. package/lib/dash/mpd_utils.js +39 -27
  63. package/lib/hls/hls_parser.js +148 -49
  64. package/lib/hls/hls_utils.js +5 -5
  65. package/lib/media/content_workarounds.js +30 -0
  66. package/lib/media/drm_engine.js +16 -12
  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 +16 -7
  72. package/lib/media/streaming_engine.js +64 -54
  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 +79 -18
  79. package/lib/polyfill/media_capabilities.js +23 -3
  80. package/lib/text/mp4_ttml_parser.js +2 -2
  81. package/lib/text/simple_text_displayer.js +7 -6
  82. package/lib/text/srt_text_parser.js +2 -2
  83. package/lib/text/text_engine.js +3 -2
  84. package/lib/text/ttml_text_parser.js +33 -5
  85. package/lib/text/ui_text_displayer.js +81 -15
  86. package/lib/text/vtt_text_parser.js +13 -0
  87. package/lib/util/buffer_utils.js +26 -4
  88. package/lib/util/cmcd_manager.js +16 -1
  89. package/lib/util/fairplay_utils.js +1 -1
  90. package/lib/util/manifest_parser_utils.js +20 -8
  91. package/lib/util/object_utils.js +1 -1
  92. package/lib/util/periods.js +91 -36
  93. package/lib/util/platform.js +39 -2
  94. package/lib/util/player_configuration.js +5 -3
  95. package/lib/util/stream_utils.js +36 -28
  96. package/lib/util/ts_parser.js +3 -3
  97. package/lib/util/xml_utils.js +5 -0
  98. package/package.json +2 -2
  99. package/test/cea/cea_decoder_unit.js +40 -0
  100. package/test/dash/dash_parser_live_unit.js +5 -8
  101. package/test/dash/dash_parser_manifest_unit.js +5 -0
  102. package/test/dash/dash_parser_segment_base_unit.js +5 -0
  103. package/test/dash/dash_parser_segment_list_unit.js +21 -17
  104. package/test/dash/dash_parser_segment_template_unit.js +5 -0
  105. package/test/hls/hls_live_unit.js +33 -0
  106. package/test/hls/hls_parser_unit.js +52 -60
  107. package/test/media/buffering_observer_unit.js +13 -0
  108. package/test/media/content_workarounds_unit.js +1 -1
  109. package/test/media/drm_engine_unit.js +30 -3
  110. package/test/media/media_source_engine_unit.js +1 -1
  111. package/test/media/transmuxer_integration.js +2 -2
  112. package/test/offline/storage_integration.js +3 -0
  113. package/test/player_integration.js +40 -40
  114. package/test/player_unit.js +31 -0
  115. package/test/polyfill/media_capabilities_unit.js +12 -6
  116. package/test/test/util/dash_parser_util.js +16 -7
  117. package/test/test/util/fake_drm_engine.js +7 -0
  118. package/test/test/util/layout_tests.js +2 -1
  119. package/test/test/util/simple_fakes.js +2 -2
  120. package/test/test/util/test_scheme.js +3 -0
  121. package/test/test/util/waiter.js +5 -0
  122. package/test/text/mp4_ttml_parser_unit.js +4 -4
  123. package/test/text/simple_text_displayer_unit.js +1 -1
  124. package/test/text/srt_text_parser_unit.js +1 -1
  125. package/test/text/text_engine_unit.js +6 -0
  126. package/test/text/ttml_text_parser_unit.js +49 -18
  127. package/test/text/ui_text_displayer_unit.js +9 -0
  128. package/test/text/vtt_text_parser_unit.js +2 -17
  129. package/test/util/buffer_utils_unit.js +1 -1
  130. package/test/util/periods_unit.js +113 -0
  131. package/test/util/ts_parser_unit.js +1 -1
  132. package/third_party/closure-uri/uri.js +1 -1
  133. package/ui/externs/ui.js +24 -0
  134. package/ui/language_utils.js +11 -5
  135. package/ui/play_button.js +20 -0
  136. package/ui/playback_rate_selection.js +4 -0
  137. package/ui/presentation_time.js +5 -5
  138. package/ui/small_play_button.js +2 -2
  139. package/ui/volume_bar.js +10 -6
  140. /package/.github/{config.yml → ISSUE_TEMPLATE/config.yml} +0 -0
@@ -160,9 +160,10 @@ shaka.text.TextEngine = class {
160
160
  * @param {BufferSource} buffer
161
161
  * @param {?number} startTime relative to the start of the presentation
162
162
  * @param {?number} endTime relative to the start of the presentation
163
+ * @param {?string=} uri
163
164
  * @return {!Promise}
164
165
  */
165
- async appendBuffer(buffer, startTime, endTime) {
166
+ async appendBuffer(buffer, startTime, endTime, uri) {
166
167
  goog.asserts.assert(
167
168
  this.parser_, 'The parser should already be initialized');
168
169
 
@@ -192,7 +193,7 @@ shaka.text.TextEngine = class {
192
193
 
193
194
  // Parse the buffer and add the new cues.
194
195
  const allCues = this.parser_.parseMedia(
195
- shaka.util.BufferUtils.toUint8(buffer), time);
196
+ shaka.util.BufferUtils.toUint8(buffer), time, uri);
196
197
  const cuesToAppend = allCues.filter((cue) => {
197
198
  return cue.startTime >= this.appendWindowStart_ &&
198
199
  cue.startTime < this.appendWindowEnd_;
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.text.TtmlTextParser');
8
8
 
9
9
  goog.require('goog.asserts');
10
+ goog.require('goog.Uri');
10
11
  goog.require('shaka.log');
11
12
  goog.require('shaka.text.Cue');
12
13
  goog.require('shaka.text.CueRegion');
@@ -42,7 +43,7 @@ shaka.text.TtmlTextParser = class {
42
43
  * @override
43
44
  * @export
44
45
  */
45
- parseMedia(data, time) {
46
+ parseMedia(data, time, uri) {
46
47
  const TtmlTextParser = shaka.text.TtmlTextParser;
47
48
  const XmlUtils = shaka.util.XmlUtils;
48
49
  const ttpNs = TtmlTextParser.parameterNs_;
@@ -139,7 +140,7 @@ shaka.text.TtmlTextParser = class {
139
140
  body, time, rateInfo, metadataElements, styles,
140
141
  regionElements, cueRegions, whitespaceTrim,
141
142
  cellResolutionInfo, /* parentCueElement= */ null,
142
- /* isContent= */ false);
143
+ /* isContent= */ false, uri);
143
144
  if (cue) {
144
145
  // According to the TTML spec, backgrounds default to transparent.
145
146
  // So default the background of the top-level element to transparent.
@@ -167,12 +168,14 @@ shaka.text.TtmlTextParser = class {
167
168
  * @param {?{columns: number, rows: number}} cellResolution
168
169
  * @param {?Element} parentCueElement
169
170
  * @param {boolean} isContent
171
+ * @param {?(string|undefined)} uri
170
172
  * @return {shaka.text.Cue}
171
173
  * @private
172
174
  */
173
175
  static parseCue_(
174
176
  cueNode, timeContext, rateInfo, metadataElements, styles, regionElements,
175
- cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent) {
177
+ cueRegions, whitespaceTrim, cellResolution, parentCueElement, isContent,
178
+ uri) {
176
179
  /** @type {Element} */
177
180
  let cueElement;
178
181
  /** @type {Element} */
@@ -214,7 +217,21 @@ shaka.text.TtmlTextParser = class {
214
217
  }
215
218
  }
216
219
 
217
- if (cueNode.nodeName == 'p' || imageElement) {
220
+ let imageUri = null;
221
+ const backgroundImage = shaka.util.XmlUtils.getAttributeNSList(
222
+ cueElement,
223
+ shaka.text.TtmlTextParser.smpteNsList_,
224
+ 'backgroundImage');
225
+ if (uri && backgroundImage && !backgroundImage.startsWith('#')) {
226
+ const baseUri = new goog.Uri(uri);
227
+ const relativeUri = new goog.Uri(backgroundImage);
228
+ const newUri = baseUri.resolve(relativeUri).toString();
229
+ if (newUri) {
230
+ imageUri = newUri;
231
+ }
232
+ }
233
+
234
+ if (cueNode.nodeName == 'p' || imageElement || imageUri) {
218
235
  isContent = true;
219
236
  }
220
237
 
@@ -247,6 +264,7 @@ shaka.text.TtmlTextParser = class {
247
264
  cellResolution,
248
265
  cueElement,
249
266
  isContent,
267
+ uri,
250
268
  );
251
269
 
252
270
  // This node may or may not generate a nested cue.
@@ -380,6 +398,7 @@ shaka.text.TtmlTextParser = class {
380
398
  cueElement,
381
399
  regionElementForStyle,
382
400
  imageElement,
401
+ imageUri,
383
402
  styles,
384
403
  /** isNested= */ parentIsContent, // "nested in a <div>" doesn't count.
385
404
  /** isLeaf= */ (nestedCues.length == 0));
@@ -464,6 +483,11 @@ shaka.text.TtmlTextParser = class {
464
483
  region.viewportAnchorX = region.viewportAnchorX * 100 /
465
484
  globalWidth;
466
485
  }
486
+ } else if (!extent) {
487
+ region.width = 100 - region.viewportAnchorX;
488
+ region.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
489
+ region.height = 100 - region.viewportAnchorY;
490
+ region.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
467
491
  }
468
492
 
469
493
  region.viewportAnchorUnits = percentage || globalWidth != null ?
@@ -482,13 +506,15 @@ shaka.text.TtmlTextParser = class {
482
506
  * @param {!Element} cueElement
483
507
  * @param {Element} region
484
508
  * @param {Element} imageElement
509
+ * @param {?string} imageUri
485
510
  * @param {!Array.<!Element>} styles
486
511
  * @param {boolean} isNested
487
512
  * @param {boolean} isLeaf
488
513
  * @private
489
514
  */
490
515
  static addStyle_(
491
- cue, cueElement, region, imageElement, styles, isNested, isLeaf) {
516
+ cue, cueElement, region, imageElement, imageUri, styles,
517
+ isNested, isLeaf) {
492
518
  const TtmlTextParser = shaka.text.TtmlTextParser;
493
519
  const Cue = shaka.text.Cue;
494
520
 
@@ -656,6 +682,8 @@ shaka.text.TtmlTextParser = class {
656
682
  backgroundImageData) {
657
683
  cue.backgroundImage = 'data:image/png;base64,' + backgroundImageData;
658
684
  }
685
+ } else if (imageUri) {
686
+ cue.backgroundImage = imageUri;
659
687
  }
660
688
 
661
689
  const textOutline = TtmlTextParser.getStyleAttribute_(
@@ -44,6 +44,9 @@ shaka.text.UITextDisplayer = class {
44
44
  /** @private {HTMLElement} */
45
45
  this.videoContainer_ = videoContainer;
46
46
 
47
+ /** @private {?number} */
48
+ this.aspectRatio_ = null;
49
+
47
50
  /** @type {HTMLElement} */
48
51
  this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
49
52
  this.textContainer_.classList.add('shaka-text-container');
@@ -90,6 +93,23 @@ shaka.text.UITextDisplayer = class {
90
93
  this.updateCaptions_(/* forceUpdate= */ true);
91
94
  });
92
95
 
96
+ // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
97
+ // Whenever the natural width or natural height of the video changes
98
+ // (including, for example, because the selected video track was changed),
99
+ // if the element's readyState attribute is not HAVE_NOTHING, the user
100
+ // agent must queue a media element task given the media element to fire an
101
+ // event named resize at the media element.
102
+ this.eventManager_.listen(this.video_, 'resize', () => {
103
+ const element = /** @type {!HTMLVideoElement} */ (this.video_);
104
+ const width = element.videoWidth;
105
+ const height = element.videoHeight;
106
+ if (width && height) {
107
+ this.aspectRatio_ = width / height;
108
+ } else {
109
+ this.aspectRatio_ = null;
110
+ }
111
+ });
112
+
93
113
  /** @private {ResizeObserver} */
94
114
  this.resizeObserver_ = null;
95
115
  if ('ResizeObserver' in window) {
@@ -134,6 +154,11 @@ shaka.text.UITextDisplayer = class {
134
154
  * @export
135
155
  */
136
156
  destroy() {
157
+ // Return resolved promise if destroy() has been called.
158
+ if (!this.textContainer_) {
159
+ return Promise.resolve();
160
+ }
161
+
137
162
  // Remove the text container element from the UI.
138
163
  this.videoContainer_.removeChild(this.textContainer_);
139
164
  this.textContainer_ = null;
@@ -156,6 +181,8 @@ shaka.text.UITextDisplayer = class {
156
181
  this.resizeObserver_.disconnect();
157
182
  this.resizeObserver_ = null;
158
183
  }
184
+
185
+ return Promise.resolve();
159
186
  }
160
187
 
161
188
 
@@ -400,6 +427,10 @@ shaka.text.UITextDisplayer = class {
400
427
  */
401
428
  getRegionElement_(cue) {
402
429
  const region = cue.region;
430
+ // from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#caption-window-size
431
+ // if aspect ratio is 4/3, use that value, otherwise, use the 16:9 value
432
+ const lineWidthMultiple = this.aspectRatio_ === 4/3 ? 2.5 : 1.9;
433
+ const lineHeightMultiple = 5.33;
403
434
 
404
435
  const regionId = this.generateRegionId_(region);
405
436
  if (this.regionElements_.has(regionId)) {
@@ -417,11 +448,33 @@ shaka.text.UITextDisplayer = class {
417
448
  regionElement.id = 'shaka-text-region---' + regionId;
418
449
  regionElement.classList.add('shaka-text-region');
419
450
 
420
- regionElement.style.height = region.height + heightUnit;
421
- regionElement.style.width = region.width + widthUnit;
422
451
  regionElement.style.position = 'absolute';
423
- regionElement.style.top = region.viewportAnchorY + viewportAnchorUnit;
424
- regionElement.style.left = region.viewportAnchorX + viewportAnchorUnit;
452
+
453
+ const linesUnit = shaka.text.CueRegion.units.LINES;
454
+ if (region.heightUnits === linesUnit && region.widthUnits === linesUnit) {
455
+ regionElement.style.height = region.height * lineHeightMultiple + '%';
456
+ regionElement.style.width = region.width * lineWidthMultiple + '%';
457
+ } else {
458
+ regionElement.style.height = region.height + heightUnit;
459
+ regionElement.style.width = region.width + widthUnit;
460
+ }
461
+
462
+ if (region.viewportAnchorUnits === linesUnit) {
463
+ // from https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-708
464
+ let top = region.viewportAnchorY / 75 * 100;
465
+ const windowWidth = this.aspectRatio_ === 4/3 ? 160 : 210;
466
+ let left = region.viewportAnchorX / windowWidth * 100;
467
+ // adjust top and left values based on the region anchor and window size
468
+ top -= region.regionAnchorY * region.height * lineHeightMultiple / 100;
469
+ left -= region.regionAnchorX * region.width * lineWidthMultiple / 100;
470
+ regionElement.style.top = top + '%';
471
+ regionElement.style.left = left + '%';
472
+ } else {
473
+ regionElement.style.top = region.viewportAnchorY -
474
+ region.regionAnchorY * region.height / 100 + viewportAnchorUnit;
475
+ regionElement.style.left = region.viewportAnchorX -
476
+ region.regionAnchorX * region.width / 100 + viewportAnchorUnit;
477
+ }
425
478
 
426
479
  regionElement.style.display = 'flex';
427
480
  regionElement.style.flexDirection = 'column';
@@ -600,6 +653,10 @@ shaka.text.UITextDisplayer = class {
600
653
  // Only do this for the text itself.
601
654
  elem.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
602
655
  }
656
+ const fontFamily = inherit((c) => c.fontFamily);
657
+ if (fontFamily) {
658
+ elem.style.fontFamily = fontFamily;
659
+ }
603
660
  }
604
661
  if (text) {
605
662
  elem.textContent = text;
@@ -653,37 +710,46 @@ shaka.text.UITextDisplayer = class {
653
710
  // (for vertical growing left) is aligned at the line.
654
711
  // TODO: Implement line alignment with line number.
655
712
  // TODO: Implement lineAlignment of 'CENTER'.
656
- if (cue.line != null) {
713
+ let line = cue.line;
714
+ if (line != null) {
657
715
  let lineInterpretation = cue.lineInterpretation;
658
716
  // HACK: the current implementation of UITextDisplayer only handled
659
- // PERCENTAGE, as cue.line=0 is zero, we can interpret it as percentage
660
- // safely.
661
- if (cue.line == 0 &&
662
- lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
717
+ // PERCENTAGE, so we need convert LINE_NUMBER to PERCENTAGE
718
+ if (lineInterpretation == Cue.lineInterpretation.LINE_NUMBER) {
663
719
  lineInterpretation = Cue.lineInterpretation.PERCENTAGE;
720
+ let maxLines = 16;
721
+ // The maximum number of lines is different if it is a vertical video.
722
+ if (this.aspectRatio_ && this.aspectRatio_ < 1) {
723
+ maxLines = 32;
724
+ }
725
+ if (line < 0) {
726
+ line = 100 + line / maxLines * 100;
727
+ } else {
728
+ line = line / maxLines * 100;
729
+ }
664
730
  }
665
731
  if (lineInterpretation == Cue.lineInterpretation.PERCENTAGE) {
666
732
  style.position = 'absolute';
667
733
  if (cue.writingMode == Cue.writingMode.HORIZONTAL_TOP_TO_BOTTOM) {
668
734
  style.width = '100%';
669
735
  if (cue.lineAlign == Cue.lineAlign.START) {
670
- style.top = cue.line + '%';
736
+ style.top = line + '%';
671
737
  } else if (cue.lineAlign == Cue.lineAlign.END) {
672
- style.bottom = (100 - cue.line) + '%';
738
+ style.bottom = (100 - line) + '%';
673
739
  }
674
740
  } else if (cue.writingMode == Cue.writingMode.VERTICAL_LEFT_TO_RIGHT) {
675
741
  style.height = '100%';
676
742
  if (cue.lineAlign == Cue.lineAlign.START) {
677
- style.left = cue.line + '%';
743
+ style.left = line + '%';
678
744
  } else if (cue.lineAlign == Cue.lineAlign.END) {
679
- style.right = (100 - cue.line) + '%';
745
+ style.right = (100 - line) + '%';
680
746
  }
681
747
  } else {
682
748
  style.height = '100%';
683
749
  if (cue.lineAlign == Cue.lineAlign.START) {
684
- style.right = cue.line + '%';
750
+ style.right = line + '%';
685
751
  } else if (cue.lineAlign == Cue.lineAlign.END) {
686
- style.left = (100 - cue.line) + '%';
752
+ style.left = (100 - line) + '%';
687
753
  }
688
754
  }
689
755
  }
@@ -433,6 +433,11 @@ shaka.text.VttTextParser = class {
433
433
  */
434
434
  static parseCueStyles(payload, rootCue, styles) {
435
435
  const VttTextParser = shaka.text.VttTextParser;
436
+ // Optimization for unstyled payloads.
437
+ if (!payload.includes('<')) {
438
+ rootCue.payload = VttTextParser.htmlUnescape_(payload);
439
+ return;
440
+ }
436
441
  if (styles.size === 0) {
437
442
  VttTextParser.addDefaultTextColor_(styles);
438
443
  }
@@ -688,6 +693,14 @@ shaka.text.VttTextParser = class {
688
693
  static generateCueFromElement_(element, rootCue, cues, styles) {
689
694
  const VttTextParser = shaka.text.VttTextParser;
690
695
  const nestedCue = rootCue.clone();
696
+ // We don't want propagate some properties.
697
+ nestedCue.nestedCues = [];
698
+ nestedCue.payload = '';
699
+ // We don't want propagate some position settings
700
+ nestedCue.line = null;
701
+ nestedCue.region = new shaka.text.CueRegion();
702
+ nestedCue.position = null;
703
+ nestedCue.size = 0;
691
704
  if (element.nodeType === Node.ELEMENT_NODE && element.nodeName) {
692
705
  const bold = shaka.text.Cue.fontWeight.BOLD;
693
706
  const italic = shaka.text.Cue.fontStyle.ITALIC;
@@ -110,6 +110,23 @@ shaka.util.BufferUtils = class {
110
110
  return shaka.util.BufferUtils.view_(data, offset, length, Uint8Array);
111
111
  }
112
112
 
113
+ /**
114
+ * Creates a new Uint16Array view on the same buffer. This clamps the values
115
+ * to be within the same view (i.e. you can't use this to move past the end
116
+ * of the view, even if the underlying buffer is larger). However, you can
117
+ * pass a negative offset to access the data before the view.
118
+ *
119
+ * @param {BufferSource} data
120
+ * @param {number=} offset The offset from the beginning of this data's view
121
+ * to start the new view at.
122
+ * @param {number=} length The byte length of the new view.
123
+ * @return {!Uint16Array}
124
+ * @export
125
+ */
126
+ static toUint16(data, offset = 0, length = Infinity) {
127
+ return shaka.util.BufferUtils.view_(data, offset, length, Uint16Array);
128
+ }
129
+
113
130
  /**
114
131
  * Creates a DataView over the given buffer.
115
132
  *
@@ -135,15 +152,20 @@ shaka.util.BufferUtils = class {
135
152
  */
136
153
  static view_(data, offset, length, Type) {
137
154
  const buffer = shaka.util.BufferUtils.unsafeGetArrayBuffer_(data);
155
+ let bytesPerElement = 1;
156
+ if ('BYTES_PER_ELEMENT' in Type) {
157
+ bytesPerElement = Type.BYTES_PER_ELEMENT;
158
+ }
138
159
  // Absolute end of the |data| view within |buffer|.
139
160
  /** @suppress {strictMissingProperties} */
140
- const dataEnd = (data.byteOffset || 0) + data.byteLength;
161
+ const dataEnd = ((data.byteOffset || 0) + data.byteLength) /
162
+ bytesPerElement;
141
163
  // Absolute start of the result within |buffer|.
142
164
  /** @suppress {strictMissingProperties} */
143
- const rawStart = (data.byteOffset || 0) + offset;
144
- const start = Math.max(0, Math.min(rawStart, dataEnd));
165
+ const rawStart = ((data.byteOffset || 0) + offset) / bytesPerElement;
166
+ const start = Math.floor(Math.max(0, Math.min(rawStart, dataEnd)));
145
167
  // Absolute end of the result within |buffer|.
146
- const end = Math.min(start + Math.max(length, 0), dataEnd);
168
+ const end = Math.floor(Math.min(start + Math.max(length, 0), dataEnd));
147
169
  return new Type(buffer, start, end - start);
148
170
  }
149
171
  };
@@ -331,12 +331,27 @@ shaka.util.CmcdManager = class {
331
331
  * @private
332
332
  */
333
333
  getObjectTypeFromMimeType_(mimeType) {
334
- switch (mimeType) {
334
+ switch (mimeType.toLowerCase()) {
335
+ case 'audio/mp4':
336
+ case 'audio/webm':
337
+ case 'audio/ogg':
338
+ case 'audio/mpeg':
339
+ case 'audio/aac':
340
+ case 'audio/flac':
341
+ case 'audio/wav':
342
+ return shaka.util.CmcdManager.ObjectType.AUDIO;
343
+
335
344
  case 'video/webm':
336
345
  case 'video/mp4':
346
+ case 'video/mpeg':
347
+ case 'video/mp2t':
337
348
  return shaka.util.CmcdManager.ObjectType.MUXED;
338
349
 
339
350
  case 'application/x-mpegurl':
351
+ case 'application/vnd.apple.mpegurl':
352
+ case 'application/dash+xml':
353
+ case 'video/vnd.mpeg.dash.mpd':
354
+ case 'application/vnd.ms-sstr+xml':
340
355
  return shaka.util.CmcdManager.ObjectType.MANIFEST;
341
356
 
342
357
  default:
@@ -192,7 +192,7 @@ shaka.util.FairPlayUtils = class {
192
192
  const stringToArray = (string) => {
193
193
  // 2 bytes for each char
194
194
  const buffer = new ArrayBuffer(string.length * 2);
195
- const array = new Uint16Array(buffer);
195
+ const array = shaka.util.BufferUtils.toUint16(buffer);
196
196
  for (let i = 0, strLen = string.length; i < strLen; i++) {
197
197
  array[i] = string.charCodeAt(i);
198
198
  }
@@ -8,7 +8,6 @@ goog.provide('shaka.util.ManifestParserUtils');
8
8
 
9
9
  goog.require('goog.Uri');
10
10
  goog.require('shaka.util.Error');
11
- goog.require('shaka.util.Functional');
12
11
 
13
12
 
14
13
  /**
@@ -24,18 +23,29 @@ shaka.util.ManifestParserUtils = class {
24
23
  * @return {!Array.<string>}
25
24
  */
26
25
  static resolveUris(baseUris, relativeUris) {
27
- const Functional = shaka.util.Functional;
28
26
  if (relativeUris.length == 0) {
29
27
  return baseUris;
30
28
  }
31
29
 
30
+ if (baseUris.length == 1 && relativeUris.length == 1) {
31
+ const baseUri = new goog.Uri(baseUris[0]);
32
+ const relativeUri = new goog.Uri(relativeUris[0]);
33
+ return [baseUri.resolve(relativeUri).toString()];
34
+ }
35
+
32
36
  const relativeAsGoog = relativeUris.map((uri) => new goog.Uri(uri));
33
- // Resolve each URI relative to each base URI, creating an Array of Arrays.
34
- // Then flatten the Arrays into a single Array.
35
- return baseUris.map((uri) => new goog.Uri(uri))
36
- .map((base) => relativeAsGoog.map((i) => base.resolve(i)))
37
- .reduce(Functional.collapseArrays, [])
38
- .map((uri) => uri.toString());
37
+
38
+ // For each base URI, this code resolves it with every relative URI.
39
+ // The result is a single array containing all the resolved URIs.
40
+ const resolvedUris = [];
41
+ for (const baseStr of baseUris) {
42
+ const base = new goog.Uri(baseStr);
43
+ for (const relative of relativeAsGoog) {
44
+ resolvedUris.push(base.resolve(relative).toString());
45
+ }
46
+ }
47
+
48
+ return resolvedUris;
39
49
  }
40
50
 
41
51
 
@@ -166,6 +176,8 @@ shaka.util.ManifestParserUtils.VIDEO_CODEC_REGEXPS_ = [
166
176
  /^avc/,
167
177
  /^hev/,
168
178
  /^hvc/,
179
+ /^vvc/,
180
+ /^vvi/,
169
181
  /^vp0?[89]/,
170
182
  /^av01/,
171
183
  /^dvh/,
@@ -19,7 +19,7 @@ shaka.util.ObjectUtils = class {
19
19
  * @return {T}
20
20
  */
21
21
  static cloneObject(arg) {
22
- const seenObjects = new Set();
22
+ const seenObjects = new WeakSet();
23
23
  // This recursively clones the value |val|, using the captured variable
24
24
  // |seenObjects| to track the objects we have already cloned.
25
25
  /** @suppress {strictMissingProperties} */
@@ -90,6 +90,37 @@ shaka.util.PeriodCombiner = class {
90
90
  return this.imageStreams_;
91
91
  }
92
92
 
93
+ /**
94
+ * Returns an object that contains arrays of streams by type
95
+ * @param {!Array.<shaka.util.PeriodCombiner.Period>} periods
96
+ * @return {{
97
+ * audioStreamsPerPeriod: !Array.<!Array.<shaka.extern.Stream>>,
98
+ * videoStreamsPerPeriod: !Array.<!Array.<shaka.extern.Stream>>,
99
+ * textStreamsPerPeriod: !Array.<!Array.<shaka.extern.Stream>>,
100
+ * imageStreamsPerPeriod: !Array.<!Array.<shaka.extern.Stream>>
101
+ * }}
102
+ * @private
103
+ */
104
+ getStreamsPerPeriod_(periods) {
105
+ const audioStreamsPerPeriod = [];
106
+ const videoStreamsPerPeriod = [];
107
+ const textStreamsPerPeriod = [];
108
+ const imageStreamsPerPeriod = [];
109
+
110
+ for (const period of periods) {
111
+ audioStreamsPerPeriod.push(period.audioStreams);
112
+ videoStreamsPerPeriod.push(period.videoStreams);
113
+ textStreamsPerPeriod.push(period.textStreams);
114
+ imageStreamsPerPeriod.push(period.imageStreams);
115
+ }
116
+ return {
117
+ audioStreamsPerPeriod,
118
+ videoStreamsPerPeriod,
119
+ textStreamsPerPeriod,
120
+ imageStreamsPerPeriod,
121
+ };
122
+ }
123
+
93
124
  /**
94
125
  * @param {!Array.<shaka.util.PeriodCombiner.Period>} periods
95
126
  * @param {boolean} isDynamic
@@ -136,14 +167,12 @@ shaka.util.PeriodCombiner = class {
136
167
  return;
137
168
  }
138
169
 
139
- const audioStreamsPerPeriod = periods.map(
140
- (period) => period.audioStreams);
141
- const videoStreamsPerPeriod = periods.map(
142
- (period) => period.videoStreams);
143
- const textStreamsPerPeriod = periods.map(
144
- (period) => period.textStreams);
145
- const imageStreamsPerPeriod = periods.map(
146
- (period) => period.imageStreams);
170
+ const {
171
+ audioStreamsPerPeriod,
172
+ videoStreamsPerPeriod,
173
+ textStreamsPerPeriod,
174
+ imageStreamsPerPeriod,
175
+ } = this.getStreamsPerPeriod_(periods);
147
176
 
148
177
  // It's okay to have a period with no text or images, but our algorithm
149
178
  // fails on any period without matching streams. So we add dummy streams
@@ -159,33 +188,32 @@ shaka.util.PeriodCombiner = class {
159
188
  ContentType.IMAGE));
160
189
  }
161
190
 
162
- await shaka.util.PeriodCombiner.combine_(
163
- this.audioStreams_,
164
- audioStreamsPerPeriod,
165
- firstNewPeriodIndex,
166
- shaka.util.PeriodCombiner.cloneStream_,
167
- shaka.util.PeriodCombiner.concatenateStreams_);
168
-
169
- await shaka.util.PeriodCombiner.combine_(
170
- this.videoStreams_,
171
- videoStreamsPerPeriod,
172
- firstNewPeriodIndex,
173
- shaka.util.PeriodCombiner.cloneStream_,
174
- shaka.util.PeriodCombiner.concatenateStreams_);
175
-
176
- await shaka.util.PeriodCombiner.combine_(
177
- this.textStreams_,
178
- textStreamsPerPeriod,
179
- firstNewPeriodIndex,
180
- shaka.util.PeriodCombiner.cloneStream_,
181
- shaka.util.PeriodCombiner.concatenateStreams_);
182
-
183
- await shaka.util.PeriodCombiner.combine_(
184
- this.imageStreams_,
185
- imageStreamsPerPeriod,
186
- firstNewPeriodIndex,
187
- shaka.util.PeriodCombiner.cloneStream_,
188
- shaka.util.PeriodCombiner.concatenateStreams_);
191
+ await Promise.all([
192
+ shaka.util.PeriodCombiner.combine_(
193
+ this.audioStreams_,
194
+ audioStreamsPerPeriod,
195
+ firstNewPeriodIndex,
196
+ shaka.util.PeriodCombiner.cloneStream_,
197
+ shaka.util.PeriodCombiner.concatenateStreams_),
198
+ shaka.util.PeriodCombiner.combine_(
199
+ this.videoStreams_,
200
+ videoStreamsPerPeriod,
201
+ firstNewPeriodIndex,
202
+ shaka.util.PeriodCombiner.cloneStream_,
203
+ shaka.util.PeriodCombiner.concatenateStreams_),
204
+ shaka.util.PeriodCombiner.combine_(
205
+ this.textStreams_,
206
+ textStreamsPerPeriod,
207
+ firstNewPeriodIndex,
208
+ shaka.util.PeriodCombiner.cloneStream_,
209
+ shaka.util.PeriodCombiner.concatenateStreams_),
210
+ shaka.util.PeriodCombiner.combine_(
211
+ this.imageStreams_,
212
+ imageStreamsPerPeriod,
213
+ firstNewPeriodIndex,
214
+ shaka.util.PeriodCombiner.cloneStream_,
215
+ shaka.util.PeriodCombiner.concatenateStreams_),
216
+ ]);
189
217
  }
190
218
 
191
219
  // Create variants for all audio/video combinations.
@@ -1102,6 +1130,11 @@ shaka.util.PeriodCombiner = class {
1102
1130
  return true;
1103
1131
  }
1104
1132
 
1133
+ // Forced subtitles should be treated as unique streams
1134
+ if (outputStream.forced !== candidate.forced) {
1135
+ return false;
1136
+ }
1137
+
1105
1138
  const languageRelatedness = LanguageUtils.relatedness(
1106
1139
  outputStream.language, candidate.language);
1107
1140
 
@@ -1180,7 +1213,18 @@ shaka.util.PeriodCombiner = class {
1180
1213
  return false;
1181
1214
  }
1182
1215
 
1183
- // If language-based differences haven't decided this, look at roles. If
1216
+ // If language-based differences haven't decided this, look at labels.
1217
+ // If available options differ, look does any matches with output stream.
1218
+ if (best.label !== candidate.label) {
1219
+ if (outputStream.label === best.label) {
1220
+ return false;
1221
+ }
1222
+ if (outputStream.label === candidate.label) {
1223
+ return true;
1224
+ }
1225
+ }
1226
+
1227
+ // If label-based differences haven't decided this, look at roles. If
1184
1228
  // the candidate has more roles in common with the output, upgrade to the
1185
1229
  // candidate.
1186
1230
  if (outputStream.roles.length) {
@@ -1383,6 +1427,17 @@ shaka.util.PeriodCombiner = class {
1383
1427
  return false;
1384
1428
  }
1385
1429
 
1430
+ // If language-based differences haven't decided this, look at labels.
1431
+ // If available options differ, look does any matches with output stream.
1432
+ if (best.label !== candidate.label) {
1433
+ if (outputStream.label === best.label) {
1434
+ return false;
1435
+ }
1436
+ if (outputStream.label === candidate.label) {
1437
+ return true;
1438
+ }
1439
+ }
1440
+
1386
1441
  // If the candidate has more roles in common with the output, upgrade to the
1387
1442
  // candidate.
1388
1443
  if (outputStream.roles.length) {