shaka-player 4.2.0 → 4.2.2

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 (88) hide show
  1. package/.github/workflows/build-and-test.yaml +31 -16
  2. package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
  3. package/.github/workflows/deflake.yaml +28 -0
  4. package/.github/workflows/selenium-lab-tests.yaml +168 -54
  5. package/AUTHORS +3 -0
  6. package/CHANGELOG.md +30 -0
  7. package/CONTRIBUTORS +3 -0
  8. package/demo/common/message_ids.js +1 -0
  9. package/demo/config.js +3 -1
  10. package/demo/locales/en.json +1 -0
  11. package/demo/locales/source.json +4 -0
  12. package/dist/controls.css +1 -1
  13. package/dist/controls.css.map +1 -1
  14. package/dist/demo.compiled.debug.js +7 -6
  15. package/dist/demo.compiled.debug.map +3 -3
  16. package/dist/demo.compiled.js +7 -6
  17. package/dist/demo.compiled.map +3 -3
  18. package/dist/deps.js +2 -2
  19. package/dist/locales.js +29 -29
  20. package/dist/receiver.compiled.debug.js +4 -3
  21. package/dist/receiver.compiled.debug.map +3 -3
  22. package/dist/receiver.compiled.js +4 -3
  23. package/dist/receiver.compiled.map +3 -3
  24. package/dist/shaka-player.compiled.d.ts +2 -4
  25. package/dist/shaka-player.compiled.debug.d.ts +2 -4
  26. package/dist/shaka-player.compiled.debug.externs.js +24 -26
  27. package/dist/shaka-player.compiled.debug.js +267 -260
  28. package/dist/shaka-player.compiled.debug.map +4 -4
  29. package/dist/shaka-player.compiled.externs.js +24 -26
  30. package/dist/shaka-player.compiled.js +809 -807
  31. package/dist/shaka-player.compiled.map +4 -4
  32. package/dist/shaka-player.ui.d.ts +2 -4
  33. package/dist/shaka-player.ui.debug.d.ts +2 -4
  34. package/dist/shaka-player.ui.debug.externs.js +24 -26
  35. package/dist/shaka-player.ui.debug.js +244 -236
  36. package/dist/shaka-player.ui.debug.map +4 -4
  37. package/dist/shaka-player.ui.externs.js +24 -26
  38. package/dist/shaka-player.ui.js +973 -971
  39. package/dist/shaka-player.ui.map +4 -4
  40. package/docs/jsdoc-template/publish.js +33 -0
  41. package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
  42. package/docs/jsdoc.conf.json +1 -0
  43. package/externs/shaka/manifest_parser.js +4 -1
  44. package/externs/shaka/player.js +7 -1
  45. package/lib/ads/client_side_ad_manager.js +7 -0
  46. package/lib/cast/cast_sender.js +39 -14
  47. package/lib/cea/cea608_memory.js +4 -0
  48. package/lib/hls/hls_parser.js +54 -82
  49. package/lib/media/drm_engine.js +52 -1
  50. package/lib/media/media_source_engine.js +43 -15
  51. package/lib/media/segment_index.js +4 -1
  52. package/lib/media/streaming_engine.js +79 -14
  53. package/lib/offline/storage.js +1 -0
  54. package/lib/player.js +29 -5
  55. package/lib/text/ttml_text_parser.js +6 -0
  56. package/lib/text/ui_text_displayer.js +8 -0
  57. package/lib/util/error.js +2 -10
  58. package/lib/util/platform.js +24 -0
  59. package/lib/util/player_configuration.js +5 -0
  60. package/lib/util/pssh.js +2 -1
  61. package/lib/util/stream_utils.js +2 -1
  62. package/package.json +1 -1
  63. package/test/cea/cea608_memory_unit.js +88 -0
  64. package/test/dash/dash_parser_content_protection_unit.js +1 -0
  65. package/test/dash/dash_parser_live_unit.js +1 -0
  66. package/test/dash/dash_parser_manifest_unit.js +1 -0
  67. package/test/dash/dash_parser_segment_base_unit.js +1 -0
  68. package/test/dash/dash_parser_segment_list_unit.js +1 -0
  69. package/test/dash/dash_parser_segment_template_unit.js +1 -0
  70. package/test/hls/hls_live_unit.js +169 -247
  71. package/test/hls/hls_parser_unit.js +112 -3
  72. package/test/media/drm_engine_unit.js +90 -0
  73. package/test/media/media_source_engine_integration.js +36 -0
  74. package/test/media/media_source_engine_unit.js +118 -1
  75. package/test/media/segment_index_unit.js +28 -2
  76. package/test/media/streaming_engine_integration.js +9 -7
  77. package/test/media/streaming_engine_unit.js +29 -0
  78. package/test/offline/offline_integration.js +3 -3
  79. package/test/player_integration.js +2 -2
  80. package/test/player_src_equals_integration.js +2 -0
  81. package/test/player_unit.js +39 -1
  82. package/test/test/assets/small.mp4 +0 -0
  83. package/test/test/util/dash_parser_util.js +2 -0
  84. package/test/test/util/ttml_utils.js +0 -2
  85. package/test/text/ttml_text_parser_unit.js +46 -0
  86. package/test/ui/text_displayer_layout_unit.js +17 -2
  87. package/ui/controls.js +0 -7
  88. package/ui/less/ad_controls.less +4 -0
@@ -522,9 +522,13 @@ shaka.media.MediaSourceEngine = class {
522
522
  * @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
523
523
  * captions
524
524
  * @param {boolean=} seeked True if we just seeked
525
+ * @param {boolean=} adaptation True if we just automatically switched active
526
+ * variant(s).
525
527
  * @return {!Promise}
526
528
  */
527
- async appendBuffer(contentType, data, reference, hasClosedCaptions, seeked) {
529
+ async appendBuffer(
530
+ contentType, data, reference, hasClosedCaptions, seeked = false,
531
+ adaptation = false) {
528
532
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
529
533
 
530
534
  if (contentType == ContentType.TEXT) {
@@ -625,6 +629,14 @@ shaka.media.MediaSourceEngine = class {
625
629
 
626
630
  await this.enqueueOperation_(
627
631
  contentType, () => this.append_(contentType, data));
632
+ // If the input buffer passed to SourceBuffer#appendBuffer() does not
633
+ // contain a complete media segment, the call will exit while the
634
+ // SourceBuffer's append state is
635
+ // still PARSING_MEDIA_SEGMENT. Reset the parser state by calling
636
+ // abort() to safely reset timestampOffset to 'originalOffset'.
637
+ // https://www.w3.org/TR/media-source-2/#sourcebuffer-segment-parser-loop
638
+ await this.enqueueOperation_(
639
+ contentType, () => this.abort_(contentType));
628
640
 
629
641
  // Reset the offset and append window.
630
642
  sourceBuffer.timestampOffset = originalOffset;
@@ -638,10 +650,17 @@ shaka.media.MediaSourceEngine = class {
638
650
  const textOffset = (reference.startTime || 0) - (mediaStartTime || 0);
639
651
  this.textSequenceModeOffset_.resolve(textOffset);
640
652
 
641
- // Finally, clear the buffer.
653
+ // Clear the buffer.
642
654
  await this.enqueueOperation_(
643
655
  contentType,
644
656
  () => this.remove_(contentType, 0, this.mediaSource_.duration));
657
+
658
+ // Finally, flush the buffer in case of choppy video start on HLS fMP4.
659
+ if (contentType == ContentType.VIDEO) {
660
+ await this.enqueueOperation_(
661
+ contentType,
662
+ () => this.flush_(contentType));
663
+ }
645
664
  }
646
665
 
647
666
  // Now switch to sequence mode and fall through to our normal operations.
@@ -650,10 +669,15 @@ shaka.media.MediaSourceEngine = class {
650
669
 
651
670
  if (reference && this.sequenceMode_ && contentType != ContentType.TEXT) {
652
671
  // In sequence mode, for non-text streams, if we just cleared the buffer
653
- // and are performing an unbuffered seek, we need to set a new
654
- // timestampOffset on the sourceBuffer.
655
- if (seeked) {
672
+ // and are either performing an unbuffered seek or handling an automatic
673
+ // adaptation, we need to set a new timestampOffset on the sourceBuffer.
674
+ if (seeked || adaptation) {
656
675
  const timestampOffset = reference.startTime;
676
+ // The logic to call abort() before setting the timestampOffset is
677
+ // extended during unbuffered seeks or automatic adaptations; it is
678
+ // possible for the append state to be PARSING_MEDIA_SEGMENT from the
679
+ // previous SourceBuffer#appendBuffer() call.
680
+ this.enqueueOperation_(contentType, () => this.abort_(contentType));
657
681
  this.enqueueOperation_(
658
682
  contentType,
659
683
  () => this.setTimestampOffset_(contentType, timestampOffset));
@@ -849,21 +873,25 @@ shaka.media.MediaSourceEngine = class {
849
873
  }
850
874
 
851
875
  /**
852
- * We only support increasing duration at this time. Decreasing duration
853
- * causes the MSE removal algorithm to run, which results in an 'updateend'
854
- * event. Supporting this scenario would be complicated, and is not currently
855
- * needed.
856
- *
857
876
  * @param {number} duration
858
877
  * @return {!Promise}
859
878
  */
860
879
  async setDuration(duration) {
861
- goog.asserts.assert(
862
- isNaN(this.mediaSource_.duration) ||
863
- this.mediaSource_.duration <= duration,
864
- 'duration cannot decrease: ' + this.mediaSource_.duration + ' -> ' +
865
- duration);
866
880
  await this.enqueueBlockingOperation_(() => {
881
+ // Reducing the duration causes the MSE removal algorithm to run, which
882
+ // triggers an 'updateend' event to fire. To handle this scenario, we
883
+ // have to insert a dummy operation into the beginning of each queue,
884
+ // which the 'updateend' handler will remove.
885
+ if (duration < this.mediaSource_.duration) {
886
+ for (const contentType in this.sourceBuffers_) {
887
+ const dummyOperation = {
888
+ start: () => {},
889
+ p: new shaka.util.PublicPromise(),
890
+ };
891
+ this.queues_[contentType].unshift(dummyOperation);
892
+ }
893
+ }
894
+
867
895
  this.mediaSource_.duration = duration;
868
896
  });
869
897
  }
@@ -364,7 +364,10 @@ shaka.media.SegmentIndex = class {
364
364
  lastReference.partialReferences,
365
365
  lastReference.tilesLayout,
366
366
  lastReference.tileDuration,
367
- lastReference.syncTime);
367
+ lastReference.syncTime,
368
+ lastReference.status,
369
+ lastReference.hlsAes128Key,
370
+ );
368
371
  }
369
372
 
370
373
 
@@ -320,8 +320,14 @@ shaka.media.StreamingEngine = class {
320
320
  * @param {number=} safeMargin
321
321
  * @param {boolean=} force
322
322
  * If true, reload the variant even if it did not change.
323
+ * @param {boolean=} adaptation
324
+ * If true, update the media state to indicate MediaSourceEngine should
325
+ * reset the timestamp offset to ensure the new track segments are correctly
326
+ * placed on the timeline.
323
327
  */
324
- switchVariant(variant, clearBuffer = false, safeMargin = 0, force = false) {
328
+ switchVariant(
329
+ variant, clearBuffer = false, safeMargin = 0, force = false,
330
+ adaptation = false) {
325
331
  this.currentVariant_ = variant;
326
332
 
327
333
  if (!this.startupComplete_) {
@@ -332,12 +338,14 @@ shaka.media.StreamingEngine = class {
332
338
  if (variant.video) {
333
339
  this.switchInternal_(
334
340
  variant.video, /* clearBuffer= */ clearBuffer,
335
- /* safeMargin= */ safeMargin, /* force= */ force);
341
+ /* safeMargin= */ safeMargin, /* force= */ force,
342
+ /* adaptation= */ adaptation);
336
343
  }
337
344
  if (variant.audio) {
338
345
  this.switchInternal_(
339
346
  variant.audio, /* clearBuffer= */ clearBuffer,
340
- /* safeMargin= */ safeMargin, /* force= */ force);
347
+ /* safeMargin= */ safeMargin, /* force= */ force,
348
+ /* adaptation= */ adaptation);
341
349
  }
342
350
  }
343
351
 
@@ -383,9 +391,13 @@ shaka.media.StreamingEngine = class {
383
391
  * @param {number} safeMargin
384
392
  * @param {boolean} force
385
393
  * If true, reload the text stream even if it did not change.
394
+ * @param {boolean=} adaptation
395
+ * If true, update the media state to indicate MediaSourceEngine should
396
+ * reset the timestamp offset to ensure the new track segments are correctly
397
+ * placed on the timeline.
386
398
  * @private
387
399
  */
388
- switchInternal_(stream, clearBuffer, safeMargin, force) {
400
+ switchInternal_(stream, clearBuffer, safeMargin, force, adaptation) {
389
401
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
390
402
  const type = /** @type {!ContentType} */(stream.type);
391
403
  const mediaState = this.mediaStates_.get(type);
@@ -440,6 +452,7 @@ shaka.media.StreamingEngine = class {
440
452
 
441
453
  mediaState.stream = stream;
442
454
  mediaState.segmentIterator = null;
455
+ mediaState.adaptation = !!adaptation;
443
456
 
444
457
  const streamTag = shaka.media.StreamingEngine.logPrefix_(mediaState);
445
458
  shaka.log.debug('switch: switching to Stream ' + streamTag);
@@ -769,12 +782,11 @@ shaka.media.StreamingEngine = class {
769
782
  const mediaSourceEngine = this.playerInterface_.mediaSourceEngine;
770
783
  const forceTransmuxTS = this.config_.forceTransmuxTS;
771
784
 
772
-
773
785
  await mediaSourceEngine.init(streamsByType, forceTransmuxTS,
774
786
  this.manifest_.sequenceMode);
775
787
  this.destroyer_.ensureNotDestroyed();
776
788
 
777
- this.setDuration_();
789
+ this.updateDuration();
778
790
 
779
791
  for (const type of streamsByType.keys()) {
780
792
  const stream = streamsByType.get(type);
@@ -824,9 +836,8 @@ shaka.media.StreamingEngine = class {
824
836
 
825
837
  /**
826
838
  * Sets the MediaSource's duration.
827
- * @private
828
839
  */
829
- setDuration_() {
840
+ updateDuration() {
830
841
  const duration = this.manifest_.presentationTimeline.getDuration();
831
842
  if (duration < Infinity) {
832
843
  this.playerInterface_.mediaSourceEngine.setDuration(duration);
@@ -1001,7 +1012,11 @@ shaka.media.StreamingEngine = class {
1001
1012
  const timeUntilEnd =
1002
1013
  this.manifest_.presentationTimeline.getDuration() - timeNeeded;
1003
1014
  const oneMicrosecond = 1e-6;
1004
- if (timeUntilEnd < oneMicrosecond) {
1015
+
1016
+ const bufferEnd =
1017
+ this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
1018
+
1019
+ if (timeUntilEnd < oneMicrosecond && !!bufferEnd) {
1005
1020
  // We shouldn't rebuffer if the playhead is close to the end of the
1006
1021
  // presentation.
1007
1022
  shaka.log.debug(logPrefix, 'buffered to end of presentation');
@@ -1031,8 +1046,6 @@ shaka.media.StreamingEngine = class {
1031
1046
  return this.config_.updateIntervalSeconds / 2;
1032
1047
  }
1033
1048
 
1034
- const bufferEnd =
1035
- this.playerInterface_.mediaSourceEngine.bufferEnd(mediaState.type);
1036
1049
  const reference = this.getSegmentReferenceNeeded_(
1037
1050
  mediaState, presentationTime, bufferEnd);
1038
1051
  if (!reference) {
@@ -1238,7 +1251,16 @@ shaka.media.StreamingEngine = class {
1238
1251
  if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4 &&
1239
1252
  !reference.hlsAes128Key) {
1240
1253
  let remaining = new Uint8Array(0);
1254
+ let processingResult = false;
1255
+ let callbackCalled = false;
1241
1256
  const streamDataCallback = async (data) => {
1257
+ if (processingResult) {
1258
+ // If the fallback result processing was triggered, don't also
1259
+ // append the buffer here. In theory this should never happen,
1260
+ // but it does on some older TVs.
1261
+ return;
1262
+ }
1263
+ callbackCalled = true;
1242
1264
  this.destroyer_.ensureNotDestroyed();
1243
1265
  if (this.fatalError_) {
1244
1266
  return;
@@ -1269,7 +1291,33 @@ shaka.media.StreamingEngine = class {
1269
1291
  }
1270
1292
  };
1271
1293
 
1272
- await this.fetch_(mediaState, reference, streamDataCallback);
1294
+ const result =
1295
+ await this.fetch_(mediaState, reference, streamDataCallback);
1296
+ if (!callbackCalled) {
1297
+ // In some environments, we might be forced to use network plugins
1298
+ // that don't support streamDataCallback. In those cases, as a
1299
+ // fallback, append the buffer here.
1300
+ processingResult = true;
1301
+ this.destroyer_.ensureNotDestroyed();
1302
+ if (this.fatalError_) {
1303
+ return;
1304
+ }
1305
+
1306
+ // If the text stream gets switched between fetch_() and append_(),
1307
+ // the new text parser is initialized, but the new init segment is
1308
+ // not fetched yet. That would cause an error in
1309
+ // TextParser.parseMedia().
1310
+ // See http://b/168253400
1311
+ if (mediaState.waitingToClearBuffer) {
1312
+ shaka.log.info(logPrefix, 'waitingToClearBuffer, skip append');
1313
+ mediaState.performingUpdate = false;
1314
+ this.scheduleUpdate_(mediaState, 0);
1315
+ return;
1316
+ }
1317
+
1318
+ await this.append_(
1319
+ mediaState, presentationTime, stream, reference, result);
1320
+ }
1273
1321
  } else {
1274
1322
  if (this.config_.lowLatencyMode && !isReadableStreamSupported) {
1275
1323
  shaka.log.warning('Low latency streaming mode is enabled, but ' +
@@ -1590,6 +1638,8 @@ shaka.media.StreamingEngine = class {
1590
1638
  shaka.log.v1(logPrefix, 'appending init segment');
1591
1639
  const hasClosedCaptions = mediaState.stream.closedCaptions &&
1592
1640
  mediaState.stream.closedCaptions.size > 0;
1641
+ await this.playerInterface_.beforeAppendSegment(
1642
+ mediaState.type, initSegment);
1593
1643
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
1594
1644
  mediaState.type, initSegment, /* reference= */ null,
1595
1645
  hasClosedCaptions);
@@ -1640,14 +1690,21 @@ shaka.media.StreamingEngine = class {
1640
1690
  shaka.log.v1(logPrefix, 'appending media segment at',
1641
1691
  (reference.syncTime == null ? 'unknown' : reference.syncTime));
1642
1692
 
1693
+ // 'seeked' or 'adaptation' triggered logic applies only to this
1694
+ // appendBuffer() call.
1643
1695
  const seeked = mediaState.seeked;
1644
1696
  mediaState.seeked = false;
1697
+ const adaptation = mediaState.adaptation;
1698
+ mediaState.adaptation = false;
1699
+
1700
+ await this.playerInterface_.beforeAppendSegment(mediaState.type, segment);
1645
1701
  await this.playerInterface_.mediaSourceEngine.appendBuffer(
1646
1702
  mediaState.type,
1647
1703
  segment,
1648
1704
  reference,
1649
1705
  hasClosedCaptions,
1650
- seeked);
1706
+ seeked,
1707
+ adaptation);
1651
1708
  this.destroyer_.ensureNotDestroyed();
1652
1709
  shaka.log.v2(logPrefix, 'appended media segment');
1653
1710
  }
@@ -2024,7 +2081,9 @@ shaka.media.StreamingEngine = class {
2024
2081
  * onEvent: function(!Event),
2025
2082
  * onManifestUpdate: function(),
2026
2083
  * onSegmentAppended: function(),
2027
- * onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference)
2084
+ * onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference),
2085
+ * beforeAppendSegment: function(
2086
+ * shaka.util.ManifestParserUtils.ContentType,!BufferSource):Promise
2028
2087
  * }}
2029
2088
  *
2030
2089
  * @property {function():number} getPresentationTime
@@ -2052,6 +2111,9 @@ shaka.media.StreamingEngine = class {
2052
2111
  * @property
2053
2112
  * {function(!number, !shaka.media.InitSegmentReference)} onInitSegmentAppended
2054
2113
  * Called when an init segment is appended to a MediaSource.
2114
+ * @property {!function(shaka.util.ManifestParserUtils.ContentType,
2115
+ * !BufferSource):Promise} beforeAppendSegment
2116
+ * A function called just before appending to the source buffer.
2055
2117
  */
2056
2118
  shaka.media.StreamingEngine.PlayerInterface;
2057
2119
 
@@ -2075,6 +2137,7 @@ shaka.media.StreamingEngine.PlayerInterface;
2075
2137
  * clearBufferSafeMargin: number,
2076
2138
  * clearingBuffer: boolean,
2077
2139
  * seeked: boolean,
2140
+ * adaptation: boolean,
2078
2141
  * recovering: boolean,
2079
2142
  * hasError: boolean,
2080
2143
  * operation: shaka.net.NetworkingEngine.PendingRequest
@@ -2121,6 +2184,8 @@ shaka.media.StreamingEngine.PlayerInterface;
2121
2184
  * True indicates that the buffer is being cleared.
2122
2185
  * @property {boolean} seeked
2123
2186
  * True indicates that the presentation just seeked.
2187
+ * @property {boolean} adaptation
2188
+ * True indicates that the presentation just automatically switched variants.
2124
2189
  * @property {boolean} recovering
2125
2190
  * True indicates that the last segment was not appended because it could not
2126
2191
  * fit in the buffer.
@@ -1169,6 +1169,7 @@ shaka.offline.Storage = class {
1169
1169
  isLowLatencyMode: () => false,
1170
1170
  isAutoLowLatencyMode: () => false,
1171
1171
  enableLowLatencyMode: () => {},
1172
+ updateDuration: () => {},
1172
1173
  };
1173
1174
 
1174
1175
  parser.configure(config.manifest);
package/lib/player.js CHANGED
@@ -1770,6 +1770,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1770
1770
  enableLowLatencyMode: () => {
1771
1771
  this.configure('streaming.lowLatencyMode', true);
1772
1772
  },
1773
+ updateDuration: () => {
1774
+ if (this.streamingEngine_) {
1775
+ this.streamingEngine_.updateDuration();
1776
+ }
1777
+ },
1773
1778
  };
1774
1779
 
1775
1780
  const startTime = Date.now() / 1000;
@@ -2952,6 +2957,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2952
2957
  this.qualityObserver_.addMediaQualityChange(mediaQuality, position);
2953
2958
  }
2954
2959
  },
2960
+ beforeAppendSegment: (contentType, segment) => {
2961
+ return this.drmEngine_.parseInbandPssh(contentType, segment);
2962
+ },
2955
2963
  };
2956
2964
 
2957
2965
  return new shaka.media.StreamingEngine(this.manifest_, playerInterface);
@@ -3838,8 +3846,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3838
3846
  return;
3839
3847
  }
3840
3848
 
3841
- this.switchVariant_(variant, /* fromAdaptation= */ false, clearBuffer,
3842
- safeMargin);
3849
+ this.switchVariant_(
3850
+ variant, /* fromAdaptation= */ false, clearBuffer, safeMargin);
3843
3851
 
3844
3852
  // Workaround for
3845
3853
  // https://github.com/shaka-project/shaka-player/issues/1299
@@ -4459,6 +4467,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4459
4467
  this.stats_.setBandwidthEstimate(estimate);
4460
4468
  }
4461
4469
 
4470
+ if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
4471
+ this.stats_.setResolution(
4472
+ /* width= */ element.videoWidth || NaN,
4473
+ /* height= */ element.videoHeight || NaN);
4474
+ }
4475
+
4462
4476
  return this.stats_.getBlob();
4463
4477
  }
4464
4478
 
@@ -5370,7 +5384,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5370
5384
  const currentVariant = this.streamingEngine_.getCurrentVariant();
5371
5385
  if (variant == currentVariant) {
5372
5386
  shaka.log.debug('Variant already selected.');
5373
- // If you want to clear the buffer, we force to reselect the same variant
5387
+ // If you want to clear the buffer, we force to reselect the same variant.
5388
+ // We don't need to reset the timestampOffset since it's the same variant,
5389
+ // so 'adaptation' isn't passed here.
5374
5390
  if (clearBuffer) {
5375
5391
  this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin,
5376
5392
  /* force= */ true);
@@ -5380,7 +5396,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5380
5396
 
5381
5397
  // Add entries to the history.
5382
5398
  this.addVariantToSwitchHistory_(variant, fromAdaptation);
5383
- this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
5399
+ this.streamingEngine_.switchVariant(
5400
+ variant, clearBuffer, safeMargin, /* force= */ undefined,
5401
+ /* adaptation= */ fromAdaptation);
5384
5402
  let oldTrack = null;
5385
5403
  if (currentVariant) {
5386
5404
  oldTrack = shaka.util.StreamUtils.variantToTrack(currentVariant);
@@ -5625,6 +5643,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5625
5643
  return false;
5626
5644
  }
5627
5645
 
5646
+ if (!navigator.onLine) {
5647
+ // Don't restrict variants if we're completely offline, or else we end up
5648
+ // rapidly restricting all of them.
5649
+ return false;
5650
+ }
5651
+
5628
5652
  let maxDisabledTime = this.config_.streaming.maxDisabledTime;
5629
5653
  if (maxDisabledTime == 0) {
5630
5654
  if (error.code == shaka.util.Error.Code.SEGMENT_MISSING) {
@@ -6558,7 +6582,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6558
6582
  * @define {string} A version number taken from git at compile time.
6559
6583
  * @export
6560
6584
  */
6561
- shaka.Player.version = 'v4.2.0-uncompiled';
6585
+ shaka.Player.version = 'v4.2.2-uncompiled';
6562
6586
 
6563
6587
  // Initialize the deprecation system using the version string we just set
6564
6588
  // on the player.
@@ -164,6 +164,12 @@ shaka.text.TtmlTextParser = class {
164
164
  cellResolutionInfo, /* parentCueElement= */ null,
165
165
  /* isContent= */ false);
166
166
  if (cue) {
167
+ // According to the TTML spec, backgrounds default to transparent.
168
+ // So default the background of the top-level element to transparent.
169
+ // Nested elements may override that background color already.
170
+ if (!cue.backgroundColor) {
171
+ cue.backgroundColor = 'transparent';
172
+ }
167
173
  cues.push(cue);
168
174
  }
169
175
  }
@@ -493,6 +493,14 @@ shaka.text.UITextDisplayer = class {
493
493
  style.backgroundRepeat = 'no-repeat';
494
494
  style.backgroundSize = 'contain';
495
495
  style.backgroundPosition = 'center';
496
+
497
+ // Quoting https://www.w3.org/TR/ttml-imsc1.2/:
498
+ // "The width and height (in pixels) of the image resource referenced by
499
+ // smpte:backgroundImage SHALL be equal to the width and height expressed
500
+ // by the tts:extent attribute of the region in which the div element is
501
+ // presented".
502
+ style.width = '100%';
503
+ style.height = '100%';
496
504
  } else {
497
505
  // If we have both text and nested cues, then style everything; otherwise
498
506
  // place the text in its own <span> so the background doesn't fill the
package/lib/util/error.js CHANGED
@@ -656,16 +656,8 @@ shaka.util.Error.Code = {
656
656
  */
657
657
  'CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM': 4033,
658
658
 
659
- /**
660
- * We do not support AES-128 encryption with HLS yet.
661
- */
662
- 'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
663
-
664
- /**
665
- * An internal error code that should never be seen by applications, thrown
666
- * to force the HLS parser to skip an unsupported stream.
667
- */
668
- 'HLS_INTERNAL_SKIP_STREAM': 4035,
659
+ // RETIRED: 'HLS_AES_128_ENCRYPTION_NOT_SUPPORTED': 4034,
660
+ // RETIRED: 'HLS_INTERNAL_SKIP_STREAM': 4035,
669
661
 
670
662
  /** The Manifest contained no Variants. */
671
663
  'NO_VARIANTS': 4036,
@@ -173,6 +173,30 @@ shaka.util.Platform = class {
173
173
  shaka.util.Platform.userAgentContains_('Android');
174
174
  }
175
175
 
176
+ /**
177
+ * Returns a major version number for Chrome, or Chromium-based browsers.
178
+ *
179
+ * For example:
180
+ * - Chrome 106.0.5249.61 returns 106.
181
+ * - Edge 106.0.1370.34 returns 106 (since this is based on Chromium).
182
+ * - Safari returns null (since this is independent of Chromium).
183
+ *
184
+ * @return {?number} A major version number or null if not Chromium-based.
185
+ */
186
+ static chromeVersion() {
187
+ if (!shaka.util.Platform.userAgentContains_('Chrome')) {
188
+ return null;
189
+ }
190
+
191
+ // Looking for something like "Chrome/106.0.0.0".
192
+ const match = navigator.userAgent.match(/Chrome\/(\d+)/);
193
+ if (match) {
194
+ return parseInt(match[1], /* base= */ 10);
195
+ }
196
+
197
+ return null;
198
+ }
199
+
176
200
  /**
177
201
  * Check if the current platform is Google Chrome.
178
202
  *
@@ -75,6 +75,10 @@ shaka.util.PlayerConfiguration = class {
75
75
  updateExpirationTime: 1,
76
76
  preferredKeySystems: [],
77
77
  keySystemsMapping: {},
78
+ // The Xbox One browser does not detect DRM key changes signalled by a
79
+ // change in the PSSH in media segments. We need to parse PSSH from media
80
+ // segments to detect key changes.
81
+ parseInbandPsshEnabled: shaka.util.Platform.isXboxOne(),
78
82
  };
79
83
 
80
84
  const manifest = {
@@ -314,6 +318,7 @@ shaka.util.PlayerConfiguration = class {
314
318
  */
315
319
  static mergeConfigObjects(destination, updates, template) {
316
320
  const overrides = {
321
+ '.drm.keySystemsMapping': '',
317
322
  '.drm.servers': '',
318
323
  '.drm.clearKeys': '',
319
324
  '.drm.advanced': {
package/lib/util/pssh.js CHANGED
@@ -42,11 +42,12 @@ shaka.util.Pssh = class {
42
42
 
43
43
  new shaka.util.Mp4Parser()
44
44
  .box('moov', shaka.util.Mp4Parser.children)
45
+ .box('moof', shaka.util.Mp4Parser.children)
45
46
  .fullBox('pssh', (box) => this.parsePsshBox_(box))
46
47
  .parse(psshBox);
47
48
 
48
49
  if (this.data.length == 0) {
49
- shaka.log.warning('No pssh box found!');
50
+ shaka.log.v2('No pssh box found!');
50
51
  }
51
52
  }
52
53
 
@@ -474,7 +474,8 @@ shaka.util.StreamUtils = class {
474
474
  if (shaka.util.Platform.isXboxOne() && video &&
475
475
  ((video.width && video.width > 1920) ||
476
476
  (video.height && video.height > 1080)) &&
477
- video.codecs.includes('avc1.')) {
477
+ (video.codecs.includes('avc1.') ||
478
+ video.codecs.includes('avc3.'))) {
478
479
  shaka.log.debug('Dropping variant - not compatible with platform',
479
480
  shaka.util.StreamUtils.getVariantSummaryString_(variant));
480
481
  return false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.2.0",
4
+ "version": "4.2.2",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -295,5 +295,93 @@ describe('Cea608Memory', () => {
295
295
  const caption = memory.forceEmit(startTime, endTime);
296
296
  expect(caption).toEqual(expectedCaption);
297
297
  });
298
+
299
+ it('does not move rows if source row index is negative', () => {
300
+ const startTime = 1;
301
+ const endTime = 2;
302
+ const text = 'test';
303
+
304
+ // Add the text to the buffer, each character on separate rows.
305
+ // At this point, the memory looks like:
306
+ // [1]: t
307
+ // [2]: e
308
+ // [3]: s
309
+ // [4]: t
310
+ for (const c of text) {
311
+ memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
312
+ c.charCodeAt(0));
313
+ memory.setRow(memory.getRow() + 1); // increment row
314
+ }
315
+
316
+ const srcRowIdx = -1;
317
+ const dstRowIdx = 2;
318
+ const rowsToMove = 3;
319
+ memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
320
+
321
+ // Expected text is 't\ne\ns\nt'
322
+ const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
323
+ topLevelCue.nestedCues = [
324
+ CeaUtils.createDefaultCue(startTime, endTime, 't'),
325
+ CeaUtils.createLineBreakCue(startTime, endTime),
326
+ CeaUtils.createDefaultCue(startTime, endTime, 'e'),
327
+ CeaUtils.createLineBreakCue(startTime, endTime),
328
+ CeaUtils.createDefaultCue(startTime, endTime, 's'),
329
+ CeaUtils.createLineBreakCue(startTime, endTime),
330
+ CeaUtils.createDefaultCue(startTime, endTime, 't'),
331
+ ];
332
+
333
+ const expectedCaption = {
334
+ stream,
335
+ cue: topLevelCue,
336
+ };
337
+
338
+ // Force out the new memory.
339
+ const caption = memory.forceEmit(startTime, endTime);
340
+ expect(caption).toEqual(expectedCaption);
341
+ });
342
+
343
+ it('does not move rows if destination row index is negative', () => {
344
+ const startTime = 1;
345
+ const endTime = 2;
346
+ const text = 'test';
347
+
348
+ // Add the text to the buffer, each character on separate rows.
349
+ // At this point, the memory looks like:
350
+ // [1]: t
351
+ // [2]: e
352
+ // [3]: s
353
+ // [4]: t
354
+ for (const c of text) {
355
+ memory.addChar(CharSet.BASIC_NORTH_AMERICAN,
356
+ c.charCodeAt(0));
357
+ memory.setRow(memory.getRow() + 1); // increment row
358
+ }
359
+
360
+ const srcRowIdx = 1;
361
+ const dstRowIdx = -2;
362
+ const rowsToMove = 3;
363
+ memory.moveRows(dstRowIdx, srcRowIdx, rowsToMove);
364
+
365
+ // Expected text is 't\ne\ns\nt'
366
+ const topLevelCue = new shaka.text.Cue(startTime, endTime, '');
367
+ topLevelCue.nestedCues = [
368
+ CeaUtils.createDefaultCue(startTime, endTime, 't'),
369
+ CeaUtils.createLineBreakCue(startTime, endTime),
370
+ CeaUtils.createDefaultCue(startTime, endTime, 'e'),
371
+ CeaUtils.createLineBreakCue(startTime, endTime),
372
+ CeaUtils.createDefaultCue(startTime, endTime, 's'),
373
+ CeaUtils.createLineBreakCue(startTime, endTime),
374
+ CeaUtils.createDefaultCue(startTime, endTime, 't'),
375
+ ];
376
+
377
+ const expectedCaption = {
378
+ stream,
379
+ cue: topLevelCue,
380
+ };
381
+
382
+ // Force out the new memory.
383
+ const caption = memory.forceEmit(startTime, endTime);
384
+ expect(caption).toEqual(expectedCaption);
385
+ });
298
386
  });
299
387
  });
@@ -43,6 +43,7 @@ describe('DashParser ContentProtection', () => {
43
43
  isLowLatencyMode: () => false,
44
44
  isAutoLowLatencyMode: () => false,
45
45
  enableLowLatencyMode: () => {},
46
+ updateDuration: () => {},
46
47
  };
47
48
 
48
49
  const actual = await dashParser.start(