shaka-player 4.0.2 → 4.0.5

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 (162) hide show
  1. package/.github/workflows/build-and-test.yaml +67 -15
  2. package/.github/workflows/compute-incremental-coverage.py +251 -0
  3. package/.github/workflows/report-incremental-coverage.yaml +39 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +44 -1
  5. package/AUTHORS +2 -0
  6. package/CHANGELOG.md +43 -0
  7. package/CONTRIBUTORS +4 -0
  8. package/README.md +20 -6
  9. package/demo/common/assets.js +1 -1
  10. package/demo/index.html +1 -3
  11. package/demo/main.js +3 -3
  12. package/dist/controls.css +1 -1
  13. package/dist/demo.compiled.debug.js +3 -3
  14. package/dist/demo.compiled.debug.map +2 -2
  15. package/dist/demo.compiled.js +3 -3
  16. package/dist/demo.compiled.map +2 -2
  17. package/dist/deps.js +3 -3
  18. package/dist/locales.js +29 -29
  19. package/dist/receiver.compiled.debug.js +1 -1
  20. package/dist/receiver.compiled.debug.map +1 -1
  21. package/dist/receiver.compiled.js +1 -1
  22. package/dist/receiver.compiled.map +1 -1
  23. package/dist/shaka-player.compiled.d.ts +12 -1
  24. package/dist/shaka-player.compiled.debug.d.ts +12 -1
  25. package/dist/shaka-player.compiled.debug.externs.js +52 -40
  26. package/dist/shaka-player.compiled.debug.js +269 -256
  27. package/dist/shaka-player.compiled.debug.map +4 -4
  28. package/dist/shaka-player.compiled.externs.js +52 -40
  29. package/dist/shaka-player.compiled.js +739 -730
  30. package/dist/shaka-player.compiled.map +4 -4
  31. package/dist/shaka-player.ui.d.ts +12 -1
  32. package/dist/shaka-player.ui.debug.d.ts +12 -1
  33. package/dist/shaka-player.ui.debug.externs.js +52 -40
  34. package/dist/shaka-player.ui.debug.js +296 -282
  35. package/dist/shaka-player.ui.debug.map +4 -4
  36. package/dist/shaka-player.ui.externs.js +52 -40
  37. package/dist/shaka-player.ui.js +885 -878
  38. package/dist/shaka-player.ui.map +4 -4
  39. package/docs/tutorials/ui.md +5 -5
  40. package/docs/tutorials/upgrade.md +1 -0
  41. package/docs/tutorials/welcome.md +6 -3
  42. package/externs/shaka/player.js +4 -2
  43. package/karma.conf.js +3 -11
  44. package/lib/ads/client_side_ad_manager.js +1 -1
  45. package/lib/dash/dash_parser.js +5 -0
  46. package/lib/hls/hls_parser.js +183 -70
  47. package/lib/media/drm_engine.js +1 -1
  48. package/lib/media/media_source_engine.js +57 -24
  49. package/lib/media/presentation_timeline.js +24 -1
  50. package/lib/media/segment_index.js +26 -1
  51. package/lib/media/segment_reference.js +10 -3
  52. package/lib/media/streaming_engine.js +4 -4
  53. package/lib/media/time_ranges_utils.js +46 -0
  54. package/lib/player.js +1 -6
  55. package/lib/polyfill/media_capabilities.js +9 -0
  56. package/lib/polyfill/videoplaybackquality.js +3 -1
  57. package/lib/text/cue.js +0 -5
  58. package/lib/text/simple_text_displayer.js +21 -9
  59. package/lib/text/ui_text_displayer.js +83 -19
  60. package/lib/util/buffer_utils.js +3 -0
  61. package/lib/util/object_utils.js +1 -0
  62. package/lib/util/platform.js +19 -1
  63. package/lib/util/stream_utils.js +24 -3
  64. package/lib/util/string_utils.js +102 -13
  65. package/maintained-branches.md +15 -0
  66. package/package.json +3 -5
  67. package/test/hls/hls_parser_unit.js +102 -8
  68. package/test/media/drm_engine_integration.js +24 -8
  69. package/test/media/media_source_engine_integration.js +24 -10
  70. package/test/media/media_source_engine_unit.js +53 -38
  71. package/test/media/streaming_engine_unit.js +28 -27
  72. package/test/test/util/fake_media_source_engine.js +10 -9
  73. package/test/text/simple_text_displayer_unit.js +27 -0
  74. package/test/text/ui_text_displayer_unit.js +65 -0
  75. package/test/util/string_utils_unit.js +46 -8
  76. package/ui/text_selection.js +2 -2
  77. package/app-engine/README.md +0 -9
  78. package/app-engine/demo-version-index/README.md +0 -14
  79. package/app-engine/demo-version-index/app.yaml +0 -28
  80. package/app-engine/demo-version-index/generate.py +0 -164
  81. package/app-engine/demo-version-index/main.py +0 -11
  82. package/app-engine/demo-version-index/requirements.txt +0 -5
  83. package/app-engine/demo-version-index/templates/index.html +0 -114
  84. package/app-engine/shaka-player-demo/README.md +0 -18
  85. package/app-engine/shaka-player-demo/app.yaml +0 -61
  86. package/app-engine/shaka-player-demo/main.py +0 -95
  87. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  88. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  89. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  90. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  91. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  92. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  93. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  94. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  95. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  96. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  97. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  98. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  99. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  100. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  101. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  102. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  103. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  104. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  105. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  106. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  107. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  108. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  109. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  110. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  111. package/app-engine/shaka-player-demo/posters/sources.txt +0 -46
  112. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  113. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  114. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  115. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  116. package/app-engine/shaka-player-demo/posters.py +0 -110
  117. package/app-engine/shaka-player-demo/requirements.txt +0 -5
  118. package/app-engine/shaka-player-demo/time.txt +0 -1
  119. package/build/README.md +0 -127
  120. package/build/__init__.py +0 -0
  121. package/build/all.py +0 -150
  122. package/build/apps.py +0 -186
  123. package/build/build.py +0 -392
  124. package/build/check.py +0 -354
  125. package/build/checkversion.py +0 -84
  126. package/build/compiler.py +0 -513
  127. package/build/conformance.textproto +0 -335
  128. package/build/docker/Dockerfile +0 -20
  129. package/build/docker/docker-compose.yml +0 -10
  130. package/build/docs.py +0 -54
  131. package/build/eslint-plugin-shaka-rules/arg-comment-spacing.js +0 -72
  132. package/build/eslint-plugin-shaka-rules/index.js +0 -24
  133. package/build/eslint-plugin-shaka-rules/package.json +0 -7
  134. package/build/eslint-plugin-shaka-rules/private.js +0 -109
  135. package/build/gendeps.py +0 -71
  136. package/build/generateExterns.js +0 -867
  137. package/build/generateLocalizations.py +0 -262
  138. package/build/generateTsDefs.py +0 -137
  139. package/build/install-linux-prereqs.sh +0 -136
  140. package/build/license-header +0 -5
  141. package/build/misspellings.txt +0 -39
  142. package/build/pixelsChanged.js +0 -35
  143. package/build/shaka-lab.yaml +0 -161
  144. package/build/shakaBuildHelpers.py +0 -356
  145. package/build/stats.py +0 -816
  146. package/build/subprocessWindowsPatch.py +0 -103
  147. package/build/test.py +0 -541
  148. package/build/types/ads +0 -8
  149. package/build/types/cast +0 -6
  150. package/build/types/cea +0 -13
  151. package/build/types/complete +0 -9
  152. package/build/types/core +0 -108
  153. package/build/types/dash +0 -8
  154. package/build/types/hls +0 -9
  155. package/build/types/manifests +0 -5
  156. package/build/types/networking +0 -6
  157. package/build/types/offline +0 -22
  158. package/build/types/polyfill +0 -18
  159. package/build/types/text +0 -11
  160. package/build/types/ui +0 -39
  161. package/build/updateScreenshots.py +0 -85
  162. package/build/wrapper.template.js +0 -52
@@ -10,6 +10,7 @@ goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
11
  goog.require('shaka.media.ContentWorkarounds');
12
12
  goog.require('shaka.media.IClosedCaptionParser');
13
+ goog.require('shaka.media.SegmentReference');
13
14
  goog.require('shaka.media.TimeRangesUtils');
14
15
  goog.require('shaka.media.Transmuxer');
15
16
  goog.require('shaka.text.TextEngine');
@@ -466,13 +467,13 @@ shaka.media.MediaSourceEngine = class {
466
467
  */
467
468
  getBufferedInfo() {
468
469
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
469
- const TimeRangeUtils = shaka.media.TimeRangesUtils;
470
+ const TimeRangesUtils = shaka.media.TimeRangesUtils;
470
471
 
471
472
  const info = {
472
- total: TimeRangeUtils.getBufferedInfo(this.video_.buffered),
473
- audio: TimeRangeUtils.getBufferedInfo(
473
+ total: TimeRangesUtils.getBufferedInfo(this.video_.buffered),
474
+ audio: TimeRangesUtils.getBufferedInfo(
474
475
  this.getBuffered_(ContentType.AUDIO)),
475
- video: TimeRangeUtils.getBufferedInfo(
476
+ video: TimeRangesUtils.getBufferedInfo(
476
477
  this.getBuffered_(ContentType.VIDEO)),
477
478
  text: [],
478
479
  };
@@ -516,15 +517,14 @@ shaka.media.MediaSourceEngine = class {
516
517
  *
517
518
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
518
519
  * @param {!BufferSource} data
519
- * @param {?number} startTime relative to the start of the presentation
520
- * @param {?number} endTime relative to the start of the presentation
520
+ * @param {?shaka.media.SegmentReference} reference The segment reference
521
+ * we are appending, or null for init segments
521
522
  * @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
522
- * captions
523
+ * captions
523
524
  * @param {boolean=} seeked True if we just seeked
524
525
  * @return {!Promise}
525
526
  */
526
- async appendBuffer(
527
- contentType, data, startTime, endTime, hasClosedCaptions, seeked) {
527
+ async appendBuffer(contentType, data, reference, hasClosedCaptions, seeked) {
528
528
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
529
529
 
530
530
  if (contentType == ContentType.TEXT) {
@@ -533,7 +533,10 @@ shaka.media.MediaSourceEngine = class {
533
533
  const offset = await this.textSequenceModeOffset_;
534
534
  this.textEngine_.setTimestampOffset(offset);
535
535
  }
536
- await this.textEngine_.appendBuffer(data, startTime, endTime);
536
+ await this.textEngine_.appendBuffer(
537
+ data,
538
+ reference ? reference.startTime : null,
539
+ reference ? reference.endTime : null);
537
540
  return;
538
541
  }
539
542
 
@@ -549,7 +552,10 @@ shaka.media.MediaSourceEngine = class {
549
552
  if (transmuxedData.metadata) {
550
553
  const timestampOffset =
551
554
  this.sourceBuffers_[contentType].timestampOffset;
552
- this.onMetadata_(transmuxedData.metadata, timestampOffset, endTime);
555
+ this.onMetadata_(
556
+ transmuxedData.metadata,
557
+ timestampOffset,
558
+ reference ? reference.endTime : null);
553
559
  }
554
560
  // This doesn't work for native TS support (ex. Edge/Chromecast),
555
561
  // since no transmuxing is needed for native TS.
@@ -559,7 +565,10 @@ shaka.media.MediaSourceEngine = class {
559
565
  const closedCaptions = this.textEngine_
560
566
  .convertMuxjsCaptionsToShakaCaptions(transmuxedData.captions);
561
567
  this.textEngine_.storeAndAppendClosedCaptions(
562
- closedCaptions, startTime, endTime, videoOffset);
568
+ closedCaptions,
569
+ reference ? reference.startTime : null,
570
+ reference ? reference.endTime : null,
571
+ videoOffset);
563
572
  }
564
573
 
565
574
  data = transmuxedData.data;
@@ -569,7 +578,7 @@ shaka.media.MediaSourceEngine = class {
569
578
  }
570
579
  // If it is the init segment for closed captions, initialize the closed
571
580
  // caption parser.
572
- if (startTime == null && endTime == null) {
581
+ if (!reference) {
573
582
  this.captionParser_.init(data);
574
583
  } else {
575
584
  const closedCaptions = this.captionParser_.parseFrom(data);
@@ -577,18 +586,21 @@ shaka.media.MediaSourceEngine = class {
577
586
  const videoOffset =
578
587
  this.sourceBuffers_[ContentType.VIDEO].timestampOffset;
579
588
  this.textEngine_.storeAndAppendClosedCaptions(
580
- closedCaptions, startTime, endTime, videoOffset);
589
+ closedCaptions,
590
+ reference.startTime,
591
+ reference.endTime,
592
+ videoOffset);
581
593
  }
582
594
  }
583
595
  }
584
596
 
585
- data = this.workAroundBrokenPlatforms_(data, startTime, contentType);
597
+ data = this.workAroundBrokenPlatforms_(
598
+ data, reference ? reference.startTime : null, contentType);
586
599
 
587
600
  const sourceBuffer = this.sourceBuffers_[contentType];
588
601
  const SEQUENCE = shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
589
602
 
590
- if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE &&
591
- startTime != null) {
603
+ if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE && reference) {
592
604
  // This is the first media segment to be appended to a SourceBuffer in
593
605
  // sequence mode. We set the mode late so that we can trick MediaSource
594
606
  // into extracting a timestamp for us to align text segments in sequence
@@ -623,7 +635,7 @@ shaka.media.MediaSourceEngine = class {
623
635
  // segments.
624
636
  const mediaStartTime = shaka.media.TimeRangesUtils.bufferStart(
625
637
  this.getBuffered_(contentType));
626
- const textOffset = (startTime || 0) - (mediaStartTime || 0);
638
+ const textOffset = (reference.startTime || 0) - (mediaStartTime || 0);
627
639
  this.textSequenceModeOffset_.resolve(textOffset);
628
640
 
629
641
  // Finally, clear the buffer.
@@ -636,22 +648,43 @@ shaka.media.MediaSourceEngine = class {
636
648
  sourceBuffer.mode = SEQUENCE;
637
649
  }
638
650
 
639
- if (startTime != null && this.sequenceMode_ &&
640
- contentType != ContentType.TEXT) {
651
+ if (reference && this.sequenceMode_ && contentType != ContentType.TEXT) {
641
652
  // In sequence mode, for non-text streams, if we just cleared the buffer
642
653
  // and are performing an unbuffered seek, we need to set a new
643
654
  // timestampOffset on the sourceBuffer.
644
655
  if (seeked) {
645
- const timestampOffset = /** @type {number} */ (startTime);
656
+ const timestampOffset = reference.startTime;
646
657
  this.enqueueOperation_(
647
658
  contentType,
648
659
  () => this.setTimestampOffset_(contentType, timestampOffset));
649
660
  }
650
661
  }
651
662
 
652
- await this.enqueueOperation_(
653
- contentType,
654
- () => this.append_(contentType, data));
663
+ let bufferedBefore = null;
664
+
665
+ await this.enqueueOperation_(contentType, () => {
666
+ if (goog.DEBUG && reference) {
667
+ bufferedBefore = this.getBuffered_(contentType);
668
+ }
669
+ this.append_(contentType, data);
670
+ });
671
+
672
+ if (goog.DEBUG && reference) {
673
+ const bufferedAfter = this.getBuffered_(contentType);
674
+ const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
675
+ bufferedBefore, bufferedAfter);
676
+ if (newBuffered) {
677
+ const segmentDuration = reference.endTime - reference.startTime;
678
+ if (Math.abs(newBuffered.start - reference.startTime) >
679
+ segmentDuration / 2) {
680
+ shaka.log.error('Possible encoding problem detected!',
681
+ 'Unexpected buffered range for reference', reference,
682
+ 'from URIs', reference.getUris(),
683
+ 'should be', {start: reference.startTime, end: reference.endTime},
684
+ 'but got', newBuffered);
685
+ }
686
+ }
687
+ }
655
688
  }
656
689
 
657
690
  /**
@@ -93,6 +93,9 @@ shaka.media.PresentationTimeline = class {
93
93
  * @private {number}
94
94
  */
95
95
  this.availabilityTimeOffset_ = 0;
96
+
97
+ /** @private {boolean} */
98
+ this.startTimeLocked_ = false;
96
99
  }
97
100
 
98
101
 
@@ -229,7 +232,8 @@ shaka.media.PresentationTimeline = class {
229
232
  this.maxSegmentEndTime_ =
230
233
  Math.max(this.maxSegmentEndTime_, lastReferenceEndTime);
231
234
 
232
- if (this.presentationStartTime_ != null && this.autoCorrectDrift_) {
235
+ if (this.presentationStartTime_ != null && this.autoCorrectDrift_ &&
236
+ !this.startTimeLocked_) {
233
237
  // Since we have explicit segment end times, calculate a presentation
234
238
  // start based on them. This start time accounts for drift.
235
239
  // Date.now() is in milliseconds, from which we compute "now" in seconds.
@@ -243,6 +247,25 @@ shaka.media.PresentationTimeline = class {
243
247
  }
244
248
 
245
249
 
250
+ /**
251
+ * Lock the presentation timeline's start time. After this is called, no
252
+ * further adjustments to presentationStartTime_ will be permitted.
253
+ *
254
+ * This should be called after all Periods have been parsed, and all calls to
255
+ * notifySegments() from the initial manifest parse have been made.
256
+ *
257
+ * Without this, we can get assertion failures in SegmentIndex for certain
258
+ * DAI content. If DAI adds ad segments to the manifest faster than
259
+ * real-time, adjustments to presentationStartTime_ can cause availability
260
+ * windows to jump around on updates.
261
+ *
262
+ * @export
263
+ */
264
+ lockStartTime() {
265
+ this.startTimeLocked_ = true;
266
+ }
267
+
268
+
246
269
  /**
247
270
  * Gives PresentationTimeline a Stream's minimum segment start time.
248
271
  *
@@ -94,6 +94,25 @@ shaka.media.SegmentIndex = class {
94
94
  }
95
95
 
96
96
 
97
+ /**
98
+ * Return the earliest reference, or null if empty.
99
+ * @return {shaka.media.SegmentReference}
100
+ */
101
+ earliestReference() {
102
+ return this.references[0] || null;
103
+ }
104
+
105
+
106
+ /**
107
+ * Drop the first N references.
108
+ * Used in early HLS synchronization, and does not count as eviction.
109
+ * @param {number} n
110
+ */
111
+ dropFirstReferences(n) {
112
+ this.references.splice(0, n);
113
+ }
114
+
115
+
97
116
  /**
98
117
  * Finds the position of the segment for the given time, in seconds, relative
99
118
  * to the start of the presentation. Returns the position of the segment
@@ -166,7 +185,13 @@ shaka.media.SegmentIndex = class {
166
185
  for (const ref of this.references) {
167
186
  ref.startTime += offset;
168
187
  ref.endTime += offset;
169
- ref.timestampOffset += offset;
188
+ ref.trueEndTime += offset;
189
+
190
+ for (const partial of ref.partialReferences) {
191
+ partial.startTime += offset;
192
+ partial.endTime += offset;
193
+ partial.trueEndTime += offset;
194
+ }
170
195
  }
171
196
  }
172
197
  }
@@ -160,8 +160,9 @@ shaka.media.SegmentReference = class {
160
160
  * If not provided, the duration should be automatically calculated based on
161
161
  * the duration of the reference.
162
162
  * @param {?number=} syncTime
163
- * A time value, expressed in the same scale as the start and end time, which
164
- * is used to synchronize between streams.
163
+ * A time value, expressed in seconds since 1970, which is used to
164
+ * synchronize between streams. Both produced and consumed by the HLS
165
+ * parser. Other components should not need this value.
165
166
  * @param {shaka.media.SegmentReference.Status=} status
166
167
  * The segment status is used to indicate that a segment does not exist or is
167
168
  * not available.
@@ -221,7 +222,13 @@ shaka.media.SegmentReference = class {
221
222
  /** @type {?number} */
222
223
  this.tileDuration = tileDuration;
223
224
 
224
- /** @type {?number} */
225
+ /**
226
+ * A time value, expressed in seconds since 1970, which is used to
227
+ * synchronize between streams. Both produced and consumed by the HLS
228
+ * parser. Other components should not need this value.
229
+ *
230
+ * @type {?number}
231
+ */
225
232
  this.syncTime = syncTime;
226
233
 
227
234
  /** @type {shaka.media.SegmentReference.Status} */
@@ -1548,8 +1548,8 @@ shaka.media.StreamingEngine = class {
1548
1548
  const hasClosedCaptions = mediaState.stream.closedCaptions &&
1549
1549
  mediaState.stream.closedCaptions.size > 0;
1550
1550
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
1551
- mediaState.type, initSegment, /* startTime= */ null,
1552
- /* endTime= */ null, hasClosedCaptions);
1551
+ mediaState.type, initSegment, /* reference= */ null,
1552
+ hasClosedCaptions);
1553
1553
  } catch (error) {
1554
1554
  mediaState.lastInitSegmentReference = null;
1555
1555
  throw error;
@@ -1602,8 +1602,7 @@ shaka.media.StreamingEngine = class {
1602
1602
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
1603
1603
  mediaState.type,
1604
1604
  segment,
1605
- reference.syncTime == null ? reference.startTime : reference.syncTime,
1606
- reference.endTime,
1605
+ reference,
1607
1606
  hasClosedCaptions,
1608
1607
  seeked);
1609
1608
  this.destroyer_.ensureNotDestroyed();
@@ -1784,6 +1783,7 @@ shaka.media.StreamingEngine = class {
1784
1783
  *
1785
1784
  * @return {!Promise.<BufferSource>}
1786
1785
  * @private
1786
+ * @suppress {strictMissingProperties}
1787
1787
  */
1788
1788
  async fetch_(mediaState, reference, streamDataCallback) {
1789
1789
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
@@ -155,4 +155,50 @@ shaka.media.TimeRangesUtils = class {
155
155
  }
156
156
  return ret;
157
157
  }
158
+
159
+ /**
160
+ * This operation can be potentially EXPENSIVE and should only be done in
161
+ * debug builds for debugging purposes.
162
+ *
163
+ * @param {TimeRanges} oldRanges
164
+ * @param {TimeRanges} newRanges
165
+ * @return {?shaka.extern.BufferedRange} The last added range,
166
+ * chronologically by presentation time.
167
+ */
168
+ static computeAddedRange(oldRanges, newRanges) {
169
+ const TimeRangesUtils = shaka.media.TimeRangesUtils;
170
+
171
+ if (!oldRanges || !oldRanges.length) {
172
+ return null;
173
+ }
174
+ if (!newRanges || !newRanges.length) {
175
+ return TimeRangesUtils.getBufferedInfo(newRanges).pop();
176
+ }
177
+
178
+ const newRangesReversed =
179
+ TimeRangesUtils.getBufferedInfo(newRanges).reverse();
180
+ const oldRangesReversed =
181
+ TimeRangesUtils.getBufferedInfo(oldRanges).reverse();
182
+ for (const newRange of newRangesReversed) {
183
+ let foundOverlap = false;
184
+
185
+ for (const oldRange of oldRangesReversed) {
186
+ if (oldRange.end >= newRange.start && oldRange.end <= newRange.end) {
187
+ foundOverlap = true;
188
+
189
+ // If the new range goes beyond the corresponding old one, the
190
+ // difference is newly-added.
191
+ if (newRange.end > oldRange.end) {
192
+ return {start: oldRange.end, end: newRange.end};
193
+ }
194
+ }
195
+ }
196
+
197
+ if (!foundOverlap) {
198
+ return newRange;
199
+ }
200
+ }
201
+
202
+ return null;
203
+ }
158
204
  };
package/lib/player.js CHANGED
@@ -833,14 +833,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
833
833
  if (!window.Promise) {
834
834
  shaka.log.alwaysWarn('A Promise implementation or polyfill is required');
835
835
  }
836
- if (!window.TextDecoder || !window.TextEncoder) {
837
- shaka.log.alwaysWarn(
838
- 'A TextDecoder/TextEncoder implementation or polyfill is required');
839
- }
840
836
 
841
837
  // Basic features needed for the library to be usable.
842
838
  const basicSupport = !!window.Promise && !!window.Uint8Array &&
843
- !!window.TextDecoder && !!window.TextEncoder &&
844
839
  // eslint-disable-next-line no-restricted-syntax
845
840
  !!Array.prototype.forEach;
846
841
  if (!basicSupport) {
@@ -6450,7 +6445,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6450
6445
  * @define {string} A version number taken from git at compile time.
6451
6446
  * @export
6452
6447
  */
6453
- shaka.Player.version = 'v4.0.2-uncompiled';
6448
+ shaka.Player.version = 'v4.0.5-uncompiled';
6454
6449
 
6455
6450
  // Initialize the deprecation system using the version string we just set
6456
6451
  // on the player.
@@ -37,9 +37,18 @@ shaka.polyfill.MediaCapabilities = class {
37
37
  // See: https://github.com/shaka-project/shaka-player/issues/3582
38
38
  // TODO: re-evaluate MediaCapabilities in the future versions of PS5
39
39
  // Browsers.
40
+ // Since MediaCapabilities implementation does not exist in PS4 browsers, we
41
+ // should always install polyfill.
42
+ // Since MediaCapabilities implementation is buggy in Tizen browsers, we
43
+ // should always install polyfill for Tizen browsers.
44
+ // Since MediaCapabilities implementation is buggy in WebOS browsers, we
45
+ // should always install polyfill for WebOS browsers.
40
46
  let canUseNativeMCap = true;
41
47
  if (shaka.util.Platform.isApple() ||
42
48
  shaka.util.Platform.isPS5() ||
49
+ shaka.util.Platform.isPS4() ||
50
+ shaka.util.Platform.isWebOS() ||
51
+ shaka.util.Platform.isTizen() ||
43
52
  shaka.util.Platform.isChromecast()) {
44
53
  canUseNativeMCap = false;
45
54
  }
@@ -7,6 +7,7 @@
7
7
  goog.provide('shaka.polyfill.VideoPlaybackQuality');
8
8
 
9
9
  goog.require('shaka.polyfill');
10
+ goog.require('shaka.util.Platform');
10
11
 
11
12
 
12
13
  /**
@@ -33,7 +34,8 @@ shaka.polyfill.VideoPlaybackQuality = class {
33
34
  return;
34
35
  }
35
36
 
36
- if ('webkitDroppedFrameCount' in proto) {
37
+ if ('webkitDroppedFrameCount' in proto ||
38
+ shaka.util.Platform.isWebOS3()) {
37
39
  proto.getVideoPlaybackQuality =
38
40
  shaka.polyfill.VideoPlaybackQuality.webkit_;
39
41
  }
package/lib/text/cue.js CHANGED
@@ -41,14 +41,9 @@ shaka.text.Cue = class {
41
41
  */
42
42
  this.endTime = endTime;
43
43
 
44
- // The explicit type here is to work around an apparent compiler bug where
45
- // the compiler seems to get confused about the type. Since we are
46
- // overriding the interface type, it shouldn't be necessary to do this.
47
- // Compiler version 20200517.
48
44
  /**
49
45
  * @override
50
46
  * @exportInterface
51
- * @type {string}
52
47
  */
53
48
  this.payload = payload;
54
49
 
@@ -121,16 +121,28 @@ shaka.text.SimpleTextDisplayer = class {
121
121
  // We don't want to modify the array or objects passed in, since we don't
122
122
  // technically own them. So we build a new array and replace certain items
123
123
  // in it if they need to be flattened.
124
- const flattenedCues = cues.map((cue) => {
125
- if (cue.nestedCues.length) {
126
- const flatCue = cue.clone();
127
- flatCue.nestedCues = [];
128
- flatCue.payload = flattenPayload(cue);
129
- return flatCue;
130
- } else {
131
- return cue;
124
+ // We also don't want to flatten the text payloads starting at a container
125
+ // element; otherwise, for containers encapsulating multiple caption lines,
126
+ // the lines would merge into a single cue. This is undesirable when a
127
+ // subset of the captions are outside of the append time window. To fix
128
+ // this, we only call flattenPayload() starting at elements marked as
129
+ // isContainer = false.
130
+ const getCuesToFlatten = (cues, result) => {
131
+ for (const cue of cues) {
132
+ if (cue.isContainer) {
133
+ // Recurse to find the actual text payload cues.
134
+ getCuesToFlatten(cue.nestedCues, result);
135
+ } else {
136
+ // Flatten the payload.
137
+ const flatCue = cue.clone();
138
+ flatCue.nestedCues = [];
139
+ flatCue.payload = flattenPayload(cue);
140
+ result.push(flatCue);
141
+ }
132
142
  }
133
- });
143
+ return result;
144
+ };
145
+ const flattenedCues = getCuesToFlatten(cues, []);
134
146
 
135
147
  // Convert cues.
136
148
  const textTrackCues = [];
@@ -77,6 +77,7 @@ shaka.text.UITextDisplayer = class {
77
77
  * the cue (e.g. the HTML element to put nested cues inside).
78
78
  * @private {Map.<!shaka.extern.Cue, !{
79
79
  * cueElement: !HTMLElement,
80
+ * regionElement: HTMLElement,
80
81
  * wrapper: !HTMLElement
81
82
  * }>}
82
83
  */
@@ -97,6 +98,9 @@ shaka.text.UITextDisplayer = class {
97
98
  });
98
99
  this.resizeObserver_.observe(this.textContainer_);
99
100
  }
101
+
102
+ /** @private {Map.<string, !HTMLElement>} */
103
+ this.regionElements_ = new Map();
100
104
  }
101
105
 
102
106
 
@@ -234,6 +238,11 @@ shaka.text.UITextDisplayer = class {
234
238
  // even ones which are going to be planted again.
235
239
  toUproot.push(cueRegistry.cueElement);
236
240
 
241
+ // Also uproot all displayed region elements.
242
+ if (cueRegistry.regionElement) {
243
+ toUproot.push(cueRegistry.regionElement);
244
+ }
245
+
237
246
  // If the cue should not be displayed, remove it entirely.
238
247
  if (!shouldBeDisplayed) {
239
248
  // Since something has to be removed, we will need to update the DOM.
@@ -268,8 +277,12 @@ shaka.text.UITextDisplayer = class {
268
277
  goog.asserts.assert(topCue == cue, 'Parent cues should be kept in order');
269
278
  }
270
279
  if (updateDOM) {
271
- for (const cueElement of toUproot) {
272
- container.removeChild(cueElement);
280
+ for (const element of toUproot) {
281
+ // NOTE: Because we uproot shared region elements, too, we might hit an
282
+ // element here that has no parent because we've already processed it.
283
+ if (element.parentElement) {
284
+ element.parentElement.removeChild(element);
285
+ }
273
286
  }
274
287
  toPlant.sort((a, b) => {
275
288
  if (a.startTime != b.startTime) {
@@ -281,7 +294,12 @@ shaka.text.UITextDisplayer = class {
281
294
  for (const cue of toPlant) {
282
295
  const cueRegistry = this.currentCuesMap_.get(cue);
283
296
  goog.asserts.assert(cueRegistry, 'cueRegistry should exist.');
284
- container.appendChild(cueRegistry.cueElement);
297
+ if (cueRegistry.regionElement) {
298
+ container.appendChild(cueRegistry.regionElement);
299
+ cueRegistry.regionElement.appendChild(cueRegistry.cueElement);
300
+ } else {
301
+ container.appendChild(cueRegistry.cueElement);
302
+ }
285
303
  }
286
304
  }
287
305
  }
@@ -298,11 +316,15 @@ shaka.text.UITextDisplayer = class {
298
316
 
299
317
  const currentTime = this.video_.currentTime;
300
318
  if (!this.isTextVisible_ || forceUpdate) {
301
- if (this.currentCuesMap_.size > 0) {
302
- // Clear away any existing cues.
303
- shaka.util.Dom.removeAllChildren(this.textContainer_);
304
- this.currentCuesMap_.clear();
319
+ // Remove child elements from all regions.
320
+ for (const regionElement of this.regionElements_.values()) {
321
+ shaka.util.Dom.removeAllChildren(regionElement);
305
322
  }
323
+ // Remove all top-level elements in the text container.
324
+ shaka.util.Dom.removeAllChildren(this.textContainer_);
325
+ // Clear the element maps.
326
+ this.currentCuesMap_.clear();
327
+ this.regionElements_.clear();
306
328
  }
307
329
  if (this.isTextVisible_) {
308
330
  // Log currently attached cue elements for verification, later.
@@ -332,6 +354,54 @@ shaka.text.UITextDisplayer = class {
332
354
  }
333
355
  }
334
356
 
357
+ /**
358
+ * Get or create a region element corresponding to the cue region. These are
359
+ * cached by ID.
360
+ *
361
+ * @param {!shaka.extern.Cue} cue
362
+ * @return {!HTMLElement}
363
+ * @private
364
+ */
365
+ getRegionElement_(cue) {
366
+ const region = cue.region;
367
+
368
+ if (this.regionElements_.has(region.id)) {
369
+ return this.regionElements_.get(region.id);
370
+ }
371
+
372
+ const regionElement = shaka.util.Dom.createHTMLElement('span');
373
+
374
+ const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
375
+ const heightUnit = region.heightUnits == percentageUnit ? '%' : 'px';
376
+ const widthUnit = region.widthUnits == percentageUnit ? '%' : 'px';
377
+ const viewportAnchorUnit =
378
+ region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
379
+
380
+ regionElement.id = 'shaka-text-region---' + region.id;
381
+ regionElement.classList.add('shaka-text-region');
382
+
383
+ regionElement.style.height = region.height + heightUnit;
384
+ regionElement.style.width = region.width + widthUnit;
385
+ regionElement.style.position = 'absolute';
386
+ regionElement.style.top = region.viewportAnchorY + viewportAnchorUnit;
387
+ regionElement.style.left = region.viewportAnchorX + viewportAnchorUnit;
388
+
389
+ regionElement.style.display = 'flex';
390
+ regionElement.style.flexDirection = 'column';
391
+ regionElement.style.alignItems = 'center';
392
+
393
+ if (cue.displayAlign == shaka.text.Cue.displayAlign.BEFORE) {
394
+ regionElement.style.justifyContent = 'flex-start';
395
+ } else if (cue.displayAlign == shaka.text.Cue.displayAlign.CENTER) {
396
+ regionElement.style.justifyContent = 'center';
397
+ } else {
398
+ regionElement.style.justifyContent = 'flex-end';
399
+ }
400
+
401
+ this.regionElements_.set(region.id, regionElement);
402
+ return regionElement;
403
+ }
404
+
335
405
  /**
336
406
  * Creates the object for a cue.
337
407
  *
@@ -354,6 +424,11 @@ shaka.text.UITextDisplayer = class {
354
424
  this.setCaptionStyles_(cueElement, cue, parents, needWrapper);
355
425
  }
356
426
 
427
+ let regionElement = null;
428
+ if (cue.region && cue.region.id) {
429
+ regionElement = this.getRegionElement_(cue);
430
+ }
431
+
357
432
  let wrapper = cueElement;
358
433
  if (needWrapper) {
359
434
  // Create a wrapper element which will serve to contain all children into
@@ -365,7 +440,7 @@ shaka.text.UITextDisplayer = class {
365
440
  cueElement.appendChild(wrapper);
366
441
  }
367
442
 
368
- this.currentCuesMap_.set(cue, {cueElement, wrapper});
443
+ this.currentCuesMap_.set(cue, {cueElement, wrapper, regionElement});
369
444
  }
370
445
 
371
446
  /**
@@ -525,17 +600,6 @@ shaka.text.UITextDisplayer = class {
525
600
  }
526
601
  }
527
602
  }
528
- } else if (cue.region && cue.region.id) {
529
- const percentageUnit = shaka.text.CueRegion.units.PERCENTAGE;
530
- const heightUnit = cue.region.heightUnits == percentageUnit ? '%' : 'px';
531
- const widthUnit = cue.region.widthUnits == percentageUnit ? '%' : 'px';
532
- const viewportAnchorUnit =
533
- cue.region.viewportAnchorUnits == percentageUnit ? '%' : 'px';
534
- style.height = cue.region.height + heightUnit;
535
- style.width = cue.region.width + widthUnit;
536
- style.position = 'absolute';
537
- style.top = cue.region.viewportAnchorY + viewportAnchorUnit;
538
- style.left = cue.region.viewportAnchorX + viewportAnchorUnit;
539
603
  }
540
604
 
541
605
  style.lineHeight = cue.lineHeight;
@@ -20,6 +20,7 @@ shaka.util.BufferUtils = class {
20
20
  * @param {?BufferSource} arr2
21
21
  * @return {boolean}
22
22
  * @export
23
+ * @suppress {strictMissingProperties}
23
24
  */
24
25
  static equal(arr1, arr2) {
25
26
  const BufferUtils = shaka.util.BufferUtils;
@@ -135,8 +136,10 @@ shaka.util.BufferUtils = class {
135
136
  static view_(data, offset, length, Type) {
136
137
  const buffer = shaka.util.BufferUtils.unsafeGetArrayBuffer_(data);
137
138
  // Absolute end of the |data| view within |buffer|.
139
+ /** @suppress {strictMissingProperties} */
138
140
  const dataEnd = (data.byteOffset || 0) + data.byteLength;
139
141
  // Absolute start of the result within |buffer|.
142
+ /** @suppress {strictMissingProperties} */
140
143
  const rawStart = (data.byteOffset || 0) + offset;
141
144
  const start = Math.max(0, Math.min(rawStart, dataEnd));
142
145
  // Absolute end of the result within |buffer|.