shaka-player 4.3.4 → 4.3.6

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 (111) hide show
  1. package/.github/workflows/build-and-test.yaml +4 -4
  2. package/.github/workflows/demo-version-index.yaml +6 -0
  3. package/.github/workflows/selenium-lab-tests.yaml +78 -17
  4. package/.github/workflows/update-screenshots.yaml +142 -0
  5. package/AUTHORS +2 -1
  6. package/CHANGELOG.md +56 -0
  7. package/CONTRIBUTORS +4 -1
  8. package/demo/common/asset.js +15 -0
  9. package/demo/common/assets.js +4 -2
  10. package/demo/common/message_ids.js +1 -0
  11. package/demo/custom.js +19 -0
  12. package/demo/locales/en.json +1 -0
  13. package/demo/locales/source.json +4 -0
  14. package/demo/main.js +14 -0
  15. package/dist/controls.css +2 -2
  16. package/dist/controls.css.map +1 -1
  17. package/dist/demo.compiled.debug.js +184 -311
  18. package/dist/demo.compiled.debug.map +4 -4
  19. package/dist/demo.compiled.js +184 -311
  20. package/dist/demo.compiled.map +4 -4
  21. package/dist/deps.js +2 -2
  22. package/dist/locales.js +29 -29
  23. package/dist/receiver.compiled.debug.js +51 -80
  24. package/dist/receiver.compiled.debug.map +4 -4
  25. package/dist/receiver.compiled.js +51 -80
  26. package/dist/receiver.compiled.map +4 -4
  27. package/dist/shaka-player.compiled.d.ts +19 -3
  28. package/dist/shaka-player.compiled.debug.d.ts +19 -3
  29. package/dist/shaka-player.compiled.debug.externs.js +20 -14
  30. package/dist/shaka-player.compiled.debug.js +291 -283
  31. package/dist/shaka-player.compiled.debug.map +4 -4
  32. package/dist/shaka-player.compiled.externs.js +20 -14
  33. package/dist/shaka-player.compiled.js +830 -825
  34. package/dist/shaka-player.compiled.map +3 -3
  35. package/dist/shaka-player.ui.d.ts +19 -3
  36. package/dist/shaka-player.ui.debug.d.ts +19 -3
  37. package/dist/shaka-player.ui.debug.externs.js +8 -2
  38. package/dist/shaka-player.ui.debug.js +301 -292
  39. package/dist/shaka-player.ui.debug.map +3 -3
  40. package/dist/shaka-player.ui.externs.js +8 -2
  41. package/dist/shaka-player.ui.js +927 -922
  42. package/dist/shaka-player.ui.map +3 -3
  43. package/docs/tutorials/a11y.md +0 -4
  44. package/docs/tutorials/ad_monetization.md +0 -4
  45. package/docs/tutorials/basic-usage.md +0 -5
  46. package/docs/tutorials/config.md +0 -5
  47. package/docs/tutorials/debugging.md +0 -5
  48. package/docs/tutorials/drm-config.md +0 -5
  49. package/docs/tutorials/errors.md +94 -0
  50. package/docs/tutorials/fairplay.md +4 -4
  51. package/docs/tutorials/index.json +2 -0
  52. package/docs/tutorials/license-server-auth.md +0 -5
  53. package/docs/tutorials/license-wrapping.md +0 -5
  54. package/docs/tutorials/network-and-buffering-config.md +0 -5
  55. package/docs/tutorials/ui-customization.md +0 -5
  56. package/docs/tutorials/ui.md +0 -7
  57. package/docs/tutorials/welcome.md +0 -5
  58. package/docs/tutorials/widevine-service-certs.md +45 -0
  59. package/externs/ima.js +9 -0
  60. package/externs/network_information.js +7 -2
  61. package/externs/shaka/abr_manager.js +6 -0
  62. package/externs/shaka/net.js +1 -0
  63. package/externs/shaka/player.js +13 -7
  64. package/lib/abr/simple_abr_manager.js +27 -3
  65. package/lib/ads/client_side_ad_manager.js +11 -5
  66. package/lib/dash/dash_parser.js +49 -48
  67. package/lib/debug/log.js +9 -13
  68. package/lib/hls/hls_parser.js +23 -6
  69. package/lib/media/drm_engine.js +4 -2
  70. package/lib/media/media_source_engine.js +27 -9
  71. package/lib/media/presentation_timeline.js +9 -2
  72. package/lib/media/segment_index.js +2 -0
  73. package/lib/media/segment_reference.js +3 -2
  74. package/lib/media/streaming_engine.js +31 -7
  75. package/lib/media/transmuxer.js +13 -7
  76. package/lib/media/video_wrapper.js +3 -3
  77. package/lib/net/http_fetch_plugin.js +79 -57
  78. package/lib/net/networking_engine.js +1 -0
  79. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  80. package/lib/player.js +24 -3
  81. package/lib/polyfill/media_capabilities.js +15 -8
  82. package/lib/polyfill/mediasource.js +24 -2
  83. package/lib/text/mp4_vtt_parser.js +4 -0
  84. package/lib/text/text_engine.js +1 -0
  85. package/lib/text/ui_text_displayer.js +1 -0
  86. package/lib/text/vtt_text_parser.js +4 -5
  87. package/lib/util/manifest_parser_utils.js +1 -2
  88. package/lib/util/mime_utils.js +12 -1
  89. package/lib/util/periods.js +5 -5
  90. package/lib/util/stream_utils.js +28 -16
  91. package/maintained-branches.md +2 -3
  92. package/package.json +1 -1
  93. package/test/dash/dash_parser_live_unit.js +3 -2
  94. package/test/hls/hls_parser_unit.js +156 -0
  95. package/test/media/content_workarounds_unit.js +43 -0
  96. package/test/media/drm_engine_integration.js +4 -17
  97. package/test/media/streaming_engine_unit.js +6 -1
  98. package/test/net/networking_engine_unit.js +6 -1
  99. package/test/player_integration.js +39 -0
  100. package/test/player_unit.js +57 -0
  101. package/test/polyfill/media_capabilities_unit.js +22 -22
  102. package/test/test/assets/text-clip-alt.vtt +63 -0
  103. package/test/test/util/fake_drm_engine.js +3 -0
  104. package/test/test/util/fake_media_source_engine.js +4 -0
  105. package/test/test/util/simple_fakes.js +6 -0
  106. package/test/test/util/test_scheme.js +9 -14
  107. package/test/text/mp4_vtt_parser_unit.js +8 -0
  108. package/test/text/vtt_text_parser_unit.js +26 -2
  109. package/test/text/web_vtt_layout_integration.js +0 -3
  110. package/test/util/mime_utils_unit.js +10 -0
  111. package/ui/less/containers.less +4 -0
@@ -311,7 +311,10 @@ shaka.hls.HlsParser = class {
311
311
  const PresentationType = shaka.hls.HlsParser.PresentationType_;
312
312
  this.setPresentationType_(PresentationType.VOD);
313
313
 
314
- const maxTimestamps = streamInfos.map((s) => s.maxTimestamp);
314
+ // The duration is the minimum of the end times of all active streams.
315
+ // Non-active streams are not guaranteed to have useful maxTimestamp
316
+ // values, due to the lazy-loading system, so they are ignored.
317
+ const maxTimestamps = activeStreamInfos.map((s) => s.maxTimestamp);
315
318
  // The duration is the minimum of the end times of all streams.
316
319
  this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
317
320
  this.playerInterface_.updateDuration();
@@ -1455,9 +1458,9 @@ shaka.hls.HlsParser = class {
1455
1458
  let codecs = '';
1456
1459
  /** @type {string} */
1457
1460
  const type = this.getType_(tag);
1458
- // Text does not require a codec.
1459
- if (type != shaka.util.ManifestParserUtils.ContentType.TEXT && groupId &&
1460
- this.groupIdToCodecsMap_.has(groupId)) {
1461
+ if (type == shaka.util.ManifestParserUtils.ContentType.TEXT) {
1462
+ codecs = tag.getAttributeValue('CODECS') || '';
1463
+ } else if (groupId && this.groupIdToCodecsMap_.has(groupId)) {
1461
1464
  codecs = this.groupIdToCodecsMap_.get(groupId);
1462
1465
  }
1463
1466
 
@@ -1733,6 +1736,13 @@ shaka.hls.HlsParser = class {
1733
1736
  }
1734
1737
  }
1735
1738
 
1739
+ if (type == ContentType.TEXT) {
1740
+ const firstSegment = realStream.segmentIndex.get(0);
1741
+ if (firstSegment && firstSegment.initSegmentReference) {
1742
+ stream.mimeType = 'application/mp4';
1743
+ }
1744
+ }
1745
+
1736
1746
  // Add finishing touches to the stream that can only be done once we have
1737
1747
  // more full context on the media as a whole.
1738
1748
  if (this.hasEnoughInfoToFinalizeStreams_()) {
@@ -2368,13 +2378,15 @@ shaka.hls.HlsParser = class {
2368
2378
  * @param {!Map.<string, string>} variables
2369
2379
  * @param {string} absoluteMediaPlaylistUri
2370
2380
  * @param {string} type
2381
+ * @param {number} timestampOffset
2371
2382
  * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
2372
2383
  * @return {shaka.media.SegmentReference}
2373
2384
  * @private
2374
2385
  */
2375
2386
  createSegmentReference_(
2376
2387
  initSegmentReference, previousReference, hlsSegment, startTime,
2377
- variables, absoluteMediaPlaylistUri, type, hlsAes128Key) {
2388
+ variables, absoluteMediaPlaylistUri, type, timestampOffset,
2389
+ hlsAes128Key) {
2378
2390
  const tags = hlsSegment.tags;
2379
2391
  const absoluteSegmentUri = this.variableSubstitution_(
2380
2392
  hlsSegment.absoluteUri, variables);
@@ -2544,7 +2556,7 @@ shaka.hls.HlsParser = class {
2544
2556
  startByte,
2545
2557
  endByte,
2546
2558
  initSegmentReference,
2547
- /* timestampOffset= */ 0, // This value is ignored in sequence mode.
2559
+ timestampOffset, // This value is ignored in sequence mode.
2548
2560
  /* appendWindowStart= */ 0,
2549
2561
  /* appendWindowEnd= */ Infinity,
2550
2562
  partialSegmentRefs,
@@ -2629,6 +2641,7 @@ shaka.hls.HlsParser = class {
2629
2641
  const references = [];
2630
2642
 
2631
2643
  let previousReference = null;
2644
+ let lastDiscontinuityStartTime = firstStartTime;
2632
2645
 
2633
2646
  for (let i = 0; i < hlsSegments.length; i++) {
2634
2647
  const item = hlsSegments[i];
@@ -2640,6 +2653,7 @@ shaka.hls.HlsParser = class {
2640
2653
  item.tags, 'EXT-X-DISCONTINUITY');
2641
2654
  if (discontinuityTag) {
2642
2655
  discontinuitySequence++;
2656
+ lastDiscontinuityStartTime = startTime;
2643
2657
  }
2644
2658
 
2645
2659
  // Apply new AES-128 tags as you see them, keeping a running total.
@@ -2678,6 +2692,7 @@ shaka.hls.HlsParser = class {
2678
2692
  variables,
2679
2693
  playlist.absoluteUri,
2680
2694
  type,
2695
+ lastDiscontinuityStartTime,
2681
2696
  hlsAes128Key);
2682
2697
  previousReference = reference;
2683
2698
 
@@ -3317,6 +3332,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
3317
3332
  'cmfa': 'audio/mp4',
3318
3333
  // MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
3319
3334
  'ts': 'video/mp2t',
3335
+ 'tsa': 'video/mp2t',
3320
3336
  };
3321
3337
 
3322
3338
 
@@ -3333,6 +3349,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
3333
3349
  'm4f': 'video/mp4',
3334
3350
  'cmfv': 'video/mp4',
3335
3351
  'ts': 'video/mp2t',
3352
+ 'tsv': 'video/mp2t',
3336
3353
  };
3337
3354
 
3338
3355
 
@@ -480,8 +480,10 @@ shaka.media.DrmEngine = class {
480
480
  () => this.closeOpenSessions_());
481
481
  }
482
482
 
483
- const manifestInitData = this.currentDrmInfo_.initData.find(
484
- (initDataOverride) => initDataOverride.initData.length > 0);
483
+ const manifestInitData = this.currentDrmInfo_ ?
484
+ this.currentDrmInfo_.initData.find(
485
+ (initDataOverride) => initDataOverride.initData.length > 0,
486
+ ) : null;
485
487
 
486
488
  /**
487
489
  * We can attach media keys before the playback actually begins when:
@@ -202,6 +202,9 @@ shaka.media.MediaSourceEngine = class {
202
202
  'audio/mp4; codecs="ec-3"',
203
203
  'audio/mp4; codecs="opus"',
204
204
  'audio/mp4; codecs="flac"',
205
+ 'audio/mp4; codecs="dtsc"', // DTS Digital Surround
206
+ 'audio/mp4; codecs="dtse"', // DTS Express
207
+ 'audio/mp4; codecs="dtsx"', // DTS:X
205
208
  // WebM types
206
209
  'video/webm; codecs="vp8"',
207
210
  'video/webm; codecs="vp9"',
@@ -375,7 +378,22 @@ shaka.media.MediaSourceEngine = class {
375
378
  shaka.media.Transmuxer.convertCodecs(contentType, mimeType);
376
379
  }
377
380
  const type = mimeType + this.config_.sourceBufferExtraFeatures;
378
- const sourceBuffer = this.mediaSource_.addSourceBuffer(type);
381
+
382
+ this.destroyer_.ensureNotDestroyed();
383
+
384
+ let sourceBuffer;
385
+
386
+ try {
387
+ sourceBuffer = this.mediaSource_.addSourceBuffer(type);
388
+ } catch (exception) {
389
+ throw new shaka.util.Error(
390
+ shaka.util.Error.Severity.CRITICAL,
391
+ shaka.util.Error.Category.MEDIA,
392
+ shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
393
+ exception,
394
+ 'The mediaSource_ status was' + this.mediaSource_.readyState +
395
+ 'expected \'open\'');
396
+ }
379
397
 
380
398
  this.eventManager_.listen(
381
399
  sourceBuffer, 'error',
@@ -628,7 +646,8 @@ shaka.media.MediaSourceEngine = class {
628
646
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
629
647
  // the video stream, so textEngine may not have been initialized.
630
648
  if (!this.textEngine_) {
631
- this.reinitText('text/vtt', this.sequenceMode_);
649
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
650
+ this.sequenceMode_);
632
651
  }
633
652
  // This doesn't work for native TS support (ex. Edge/Chromecast),
634
653
  // since no transmuxing is needed for native TS.
@@ -647,7 +666,8 @@ shaka.media.MediaSourceEngine = class {
647
666
  data = transmuxedData.data;
648
667
  } else if (hasClosedCaptions && contentType == ContentType.VIDEO) {
649
668
  if (!this.textEngine_) {
650
- this.reinitText('text/vtt', this.sequenceMode_);
669
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
670
+ this.sequenceMode_);
651
671
  }
652
672
  if (!this.captionParser_) {
653
673
  this.captionParser_ = this.getCaptionParser(mimeType);
@@ -937,7 +957,6 @@ shaka.media.MediaSourceEngine = class {
937
957
 
938
958
  /**
939
959
  * Adjust timestamp offset to maintain AV sync across discontinuities.
940
- * Only used in sequence mode.
941
960
  *
942
961
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
943
962
  * @param {number} timestampOffset
@@ -946,9 +965,6 @@ shaka.media.MediaSourceEngine = class {
946
965
  async resync(contentType, timestampOffset) {
947
966
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
948
967
 
949
- goog.asserts.assert(this.sequenceMode_,
950
- 'resync only used with sequence mode!');
951
-
952
968
  if (contentType == ContentType.TEXT) {
953
969
  // This operation is for audio and video only.
954
970
  return;
@@ -978,8 +994,10 @@ shaka.media.MediaSourceEngine = class {
978
994
  async endOfStream(reason) {
979
995
  await this.enqueueBlockingOperation_(() => {
980
996
  // If endOfStream() has already been called on the media source,
981
- // don't call it again.
982
- if (this.ended()) {
997
+ // don't call it again. Also do not call if readyState is
998
+ // 'closed' (not attached to video element) since it is not a
999
+ // valid operation.
1000
+ if (this.ended() || this.mediaSource_.readyState === 'closed') {
983
1001
  return;
984
1002
  }
985
1003
  // Tizen won't let us pass undefined, but it will let us omit the
@@ -236,7 +236,16 @@ shaka.media.PresentationTimeline = class {
236
236
 
237
237
  let firstReferenceStartTime = references[0].startTime;
238
238
  let lastReferenceEndTime = references[0].endTime;
239
+
240
+ // Date.now() is in milliseconds, from which we compute "now" in seconds.
241
+ const now = (Date.now() + this.clockOffset_) / 1000.0;
242
+
239
243
  for (const reference of references) {
244
+ // Exclude segments that are in the "future".
245
+ if (now < reference.startTime) {
246
+ continue;
247
+ }
248
+
240
249
  firstReferenceStartTime = Math.min(
241
250
  firstReferenceStartTime, reference.startTime);
242
251
  lastReferenceEndTime = Math.max(lastReferenceEndTime, reference.endTime);
@@ -251,8 +260,6 @@ shaka.media.PresentationTimeline = class {
251
260
  !this.startTimeLocked_) {
252
261
  // Since we have explicit segment end times, calculate a presentation
253
262
  // start based on them. This start time accounts for drift.
254
- // Date.now() is in milliseconds, from which we compute "now" in seconds.
255
- const now = (Date.now() + this.clockOffset_) / 1000.0;
256
263
  this.presentationStartTime_ =
257
264
  now - this.maxSegmentEndTime_ - this.maxSegmentDuration_;
258
265
  }
@@ -363,6 +363,8 @@ shaka.media.SegmentIndex = class {
363
363
  lastReference.status,
364
364
  lastReference.hlsAes128Key,
365
365
  );
366
+ this.references[this.references.length - 1].discontinuitySequence =
367
+ lastReference.discontinuitySequence;
366
368
  }
367
369
 
368
370
 
@@ -144,8 +144,9 @@ shaka.media.SegmentReference = class {
144
144
  * minus the first segment's tfdt box's 'baseMediaDecodeTime' field (after
145
145
  * it has been converted to seconds).
146
146
  * <br>
147
- * For HLS, this value should be 0 to keep the presentation time at the most
148
- * recent discontinuity minus the corresponding media time.
147
+ * For HLS, this value should be the start time of the most recent
148
+ * discontinuity, or 0 if there is no preceding discontinuity. Only used
149
+ * in segments mode.
149
150
  * @param {number} appendWindowStart
150
151
  * The start of the append window for this reference, relative to the
151
152
  * presentation. Any content from before this time will be removed by
@@ -78,6 +78,9 @@ shaka.media.StreamingEngine = class {
78
78
  /** @private {?shaka.extern.Stream} */
79
79
  this.currentTextStream_ = null;
80
80
 
81
+ /** @private {number} */
82
+ this.textStreamSequenceId_ = 0;
83
+
81
84
  /** @private {boolean} */
82
85
  this.parsedPrftEventRaised_ = false;
83
86
 
@@ -224,6 +227,8 @@ shaka.media.StreamingEngine = class {
224
227
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
225
228
  goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
226
229
  'Should not call loadNewTextStream_ while streaming text!');
230
+ this.textStreamSequenceId_++;
231
+ const currentSequenceId = this.textStreamSequenceId_;
227
232
 
228
233
  try {
229
234
  // Clear MediaSource's buffered text, so that the new text stream will
@@ -246,7 +251,7 @@ shaka.media.StreamingEngine = class {
246
251
  const streamText =
247
252
  textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
248
253
 
249
- if (streamText) {
254
+ if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
250
255
  const state = this.createMediaState_(stream);
251
256
  this.mediaStates_.set(ContentType.TEXT, state);
252
257
  this.scheduleUpdate_(state, 0);
@@ -357,7 +362,7 @@ shaka.media.StreamingEngine = class {
357
362
  /**
358
363
  * @param {shaka.extern.Stream} textStream
359
364
  */
360
- switchTextStream(textStream) {
365
+ async switchTextStream(textStream) {
361
366
  this.currentTextStream_ = textStream;
362
367
 
363
368
  if (!this.startupComplete_) {
@@ -369,6 +374,13 @@ shaka.media.StreamingEngine = class {
369
374
  goog.asserts.assert(textStream && textStream.type == ContentType.TEXT,
370
375
  'Wrong stream type passed to switchTextStream!');
371
376
 
377
+ // In HLS it is possible that the mimetype changes when the media
378
+ // playlist is downloaded, so it is necessary to have the updated data
379
+ // here.
380
+ if (!textStream.segmentIndex) {
381
+ await textStream.createSegmentIndex();
382
+ }
383
+
372
384
  this.switchInternal_(
373
385
  textStream, /* clearBuffer= */ true,
374
386
  /* safeMargin= */ 0, /* force= */ false);
@@ -1564,10 +1576,10 @@ shaka.media.StreamingEngine = class {
1564
1576
  'waiting for another stream to recover...');
1565
1577
  }
1566
1578
 
1567
- // QuotaExceededError gets thrown if evication didn't help to make room
1579
+ // QuotaExceededError gets thrown if eviction didn't help to make room
1568
1580
  // for a segment. We want to wait for a while (4 seconds is just an
1569
1581
  // arbitrary number) before updating to give the playhead a chance to
1570
- // advance, so we don't immidiately throw again.
1582
+ // advance, so we don't immediately throw again.
1571
1583
  this.scheduleUpdate_(mediaState, 4);
1572
1584
  }
1573
1585
 
@@ -1675,19 +1687,31 @@ shaka.media.StreamingEngine = class {
1675
1687
  }
1676
1688
  }
1677
1689
 
1690
+ const lastDiscontinuitySequence =
1691
+ mediaState.lastSegmentReference ?
1692
+ mediaState.lastSegmentReference.discontinuitySequence : null;
1678
1693
  if (this.manifest_.sequenceMode) {
1679
1694
  // Across discontinuity bounds, we should resync timestamps for
1680
1695
  // sequence mode playbacks. The next segment appended should
1681
1696
  // land at its theoretical timestamp from the segment index.
1682
- const lastDiscontinuitySequence =
1683
- mediaState.lastSegmentReference ?
1684
- mediaState.lastSegmentReference.discontinuitySequence : null;
1685
1697
  if (reference.discontinuitySequence != lastDiscontinuitySequence ||
1686
1698
  mediaState.needsResync) {
1687
1699
  mediaState.needsResync = false;
1688
1700
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1689
1701
  mediaState.type, reference.startTime));
1690
1702
  }
1703
+ } else {
1704
+ // In segments mode, we need to resync to set the timestampOffset
1705
+ // to the start of the current discontinuity sequence. This is
1706
+ // because individual discontinuity sequences may have internal
1707
+ // timestamps that overlap, so we adjust the timestampOffset to avoid
1708
+ // having the SourceBuffer get overwritten.
1709
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1710
+ operations.push(
1711
+ this.playerInterface_.mediaSourceEngine.resync(
1712
+ mediaState.type,
1713
+ reference.timestampOffset));
1714
+ }
1691
1715
  }
1692
1716
 
1693
1717
  await Promise.all(operations);
@@ -35,9 +35,7 @@ shaka.media.Transmuxer = class {
35
35
  this.muxjs_ = shaka.dependencies.muxjs();
36
36
 
37
37
  /** @private {muxjs.mp4.Transmuxer} */
38
- this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
39
- 'keepOriginalTimestamps': true,
40
- });
38
+ this.muxTransmuxer_ = null;
41
39
 
42
40
  /** @private {shaka.util.PublicPromise} */
43
41
  this.transmuxPromise_ = null;
@@ -51,16 +49,22 @@ shaka.media.Transmuxer = class {
51
49
  /** @private {boolean} */
52
50
  this.isTransmuxing_ = false;
53
51
 
54
- this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
55
-
56
- this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
52
+ if (this.muxjs_) {
53
+ this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
54
+ 'keepOriginalTimestamps': true,
55
+ });
56
+ this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
57
+ this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
58
+ }
57
59
  }
58
60
 
59
61
  /**
60
62
  * @override
61
63
  */
62
64
  destroy() {
63
- this.muxTransmuxer_.dispose();
65
+ if (this.muxTransmuxer_) {
66
+ this.muxTransmuxer_.dispose();
67
+ }
64
68
  this.muxTransmuxer_ = null;
65
69
  return Promise.resolve();
66
70
  }
@@ -216,6 +220,8 @@ shaka.media.Transmuxer = class {
216
220
  * captions: !Array.<!muxjs.mp4.ClosedCaption>}>}
217
221
  */
218
222
  transmux(data) {
223
+ goog.asserts.assert(this.muxTransmuxer_,
224
+ 'mux.js should be available.');
219
225
  goog.asserts.assert(!this.isTransmuxing_,
220
226
  'No transmuxing should be in progress.');
221
227
  this.isTransmuxing_ = true;
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
135
135
  // calling |Player.load|, meaning that |currentTime| is more meaningful than
136
136
  // |startTime|.
137
137
  //
138
- // Seeking to the current time is a work around for Issue 1298. If we don't
139
- // do this, the video may get stuck and not play.
138
+ // Seeking to the current time is a work around for Issue 1298 and 4888.
139
+ // If we don't do this, the video may get stuck and not play.
140
140
  //
141
141
  // TODO: Need further investigation why it happens. Before and after
142
142
  // setting the current time, video.readyState is 1, video.paused is true,
143
143
  // and video.buffered's TimeRanges length is 0.
144
144
  // See: https://github.com/shaka-project/shaka-player/issues/1298
145
145
  this.mover_.moveTo(
146
- this.video_.currentTime == 0 ?
146
+ (!this.video_.currentTime || this.video_.currentTime == 0) ?
147
147
  startTime :
148
148
  this.video_.currentTime);
149
149
  }
@@ -120,65 +120,77 @@ shaka.net.HttpFetchPlugin = class {
120
120
  // not the body yet.
121
121
  headersReceived(shaka.net.HttpFetchPlugin.headersToGenericObject_(
122
122
  response.headers));
123
- // Getting the reader in this way allows us to observe the process of
124
- // downloading the body, instead of just waiting for an opaque promise to
125
- // resolve.
126
- // We first clone the response because calling getReader locks the body
127
- // stream; if we didn't clone it here, we would be unable to get the
128
- // response's arrayBuffer later.
129
- const reader = response.clone().body.getReader();
130
-
131
- const contentLengthRaw = response.headers.get('Content-Length');
132
- const contentLength =
133
- contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
134
-
135
- const start = (controller) => {
136
- const push = async () => {
137
- let readObj;
138
- try {
139
- readObj = await reader.read();
140
- } catch (e) {
141
- // If we abort the request, we'll get an error here. Just ignore it
142
- // since real errors will be reported when we read the buffer below.
143
- shaka.log.v1('error reading from stream', e.message);
144
- return;
145
- }
146
-
147
- if (!readObj.done) {
148
- loaded += readObj.value.byteLength;
149
- if (streamDataCallback) {
150
- await streamDataCallback(readObj.value);
123
+
124
+ // In new versions of Chromium, HEAD requests now have a response body
125
+ // that is null.
126
+ // So just don't try to download the body at all, if it's a HEAD request,
127
+ // to avoid null reference errors.
128
+ // See: https://crbug.com/1297060
129
+ if (init.method != 'HEAD') {
130
+ goog.asserts.assert(response.body,
131
+ 'non-HEAD responses should have a body');
132
+
133
+ // Getting the reader in this way allows us to observe the process of
134
+ // downloading the body, instead of just waiting for an opaque promise
135
+ // to resolve.
136
+ // We first clone the response because calling getReader locks the body
137
+ // stream; if we didn't clone it here, we would be unable to get the
138
+ // response's arrayBuffer later.
139
+ const reader = response.clone().body.getReader();
140
+
141
+ const contentLengthRaw = response.headers.get('Content-Length');
142
+ const contentLength =
143
+ contentLengthRaw ? parseInt(contentLengthRaw, 10) : 0;
144
+
145
+ const start = (controller) => {
146
+ const push = async () => {
147
+ let readObj;
148
+ try {
149
+ readObj = await reader.read();
150
+ } catch (e) {
151
+ // If we abort the request, we'll get an error here. Just ignore
152
+ // it since real errors will be reported when we read the buffer
153
+ // below.
154
+ shaka.log.v1('error reading from stream', e.message);
155
+ return;
156
+ }
157
+
158
+ if (!readObj.done) {
159
+ loaded += readObj.value.byteLength;
160
+ if (streamDataCallback) {
161
+ await streamDataCallback(readObj.value);
162
+ }
163
+ }
164
+
165
+ const currentTime = Date.now();
166
+ // If the time between last time and this time we got progress event
167
+ // is long enough, or if a whole segment is downloaded, call
168
+ // progressUpdated().
169
+ if (currentTime - lastTime > 100 || readObj.done) {
170
+ progressUpdated(currentTime - lastTime, loaded - lastLoaded,
171
+ contentLength - loaded);
172
+ lastLoaded = loaded;
173
+ lastTime = currentTime;
174
+ }
175
+
176
+ if (readObj.done) {
177
+ goog.asserts.assert(!readObj.value,
178
+ 'readObj should be unset when "done" is true.');
179
+ controller.close();
180
+ } else {
181
+ controller.enqueue(readObj.value);
182
+ push();
151
183
  }
152
- }
153
-
154
- const currentTime = Date.now();
155
- // If the time between last time and this time we got progress event
156
- // is long enough, or if a whole segment is downloaded, call
157
- // progressUpdated().
158
- if (currentTime - lastTime > 100 || readObj.done) {
159
- progressUpdated(currentTime - lastTime, loaded - lastLoaded,
160
- contentLength - loaded);
161
- lastLoaded = loaded;
162
- lastTime = currentTime;
163
- }
164
-
165
- if (readObj.done) {
166
- goog.asserts.assert(!readObj.value,
167
- 'readObj should be unset when "done" is true.');
168
- controller.close();
169
- } else {
170
- controller.enqueue(readObj.value);
171
- push();
172
- }
184
+ };
185
+ push();
173
186
  };
174
- push();
175
- };
176
- // Create a ReadableStream to use the reader. We don't need to use the
177
- // actual stream for anything, though, as we are using the response's
178
- // arrayBuffer method to get the body, so we don't store the
179
- // ReadableStream.
180
- new ReadableStream({start}); // eslint-disable-line no-new
181
- arrayBuffer = await response.arrayBuffer();
187
+ // Create a ReadableStream to use the reader. We don't need to use the
188
+ // actual stream for anything, though, as we are using the response's
189
+ // arrayBuffer method to get the body, so we don't store the
190
+ // ReadableStream.
191
+ new ReadableStream({start}); // eslint-disable-line no-new
192
+ arrayBuffer = await response.arrayBuffer();
193
+ }
182
194
  } catch (error) {
183
195
  if (abortStatus.canceled) {
184
196
  throw new shaka.util.Error(
@@ -243,6 +255,16 @@ shaka.net.HttpFetchPlugin = class {
243
255
  } else {
244
256
  return false;
245
257
  }
258
+ // Old fetch implementations hasn't body and ReadableStream implementation
259
+ // See: https://github.com/shaka-project/shaka-player/issues/5088
260
+ if (window.Response) {
261
+ const response = new Response('');
262
+ if (!response.body) {
263
+ return false;
264
+ }
265
+ } else {
266
+ return false;
267
+ }
246
268
  return !!(window.fetch && window.AbortController);
247
269
  }
248
270
  };
@@ -312,6 +312,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
312
312
  if (!responseAndGotProgress.gotProgress &&
313
313
  this.onProgressUpdated_ &&
314
314
  !response.fromCache &&
315
+ request.method != 'HEAD' &&
315
316
  type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
316
317
  this.onProgressUpdated_(response.timeMs, response.data.byteLength);
317
318
  }
@@ -67,7 +67,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
67
67
  shaka.util.Error.Category.STORAGE,
68
68
  shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
69
69
  });
70
- timeOutTimer.tickAfter(2);
70
+ timeOutTimer.tickAfter(5);
71
71
 
72
72
  const open = window.indexedDB.open(name, version);
73
73
  open.onsuccess = (event) => {
package/lib/player.js CHANGED
@@ -897,7 +897,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
897
897
  }
898
898
 
899
899
  this.abrManagerFactory_ = null;
900
- this.abrManager_ = null;
901
900
  this.config_ = null;
902
901
  this.stats_ = null;
903
902
  this.videoContainer_ = null;
@@ -908,6 +907,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
908
907
  this.networkingEngine_ = null;
909
908
  }
910
909
 
910
+ if (this.abrManager_) {
911
+ this.abrManager_.release();
912
+ this.abrManager_ = null;
913
+ }
914
+
911
915
  // FakeEventTarget implements IReleasable
912
916
  super.release();
913
917
  }
@@ -1342,6 +1346,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1342
1346
  */
1343
1347
  shouldUseSrcEquals_(payload) {
1344
1348
  const Platform = shaka.util.Platform;
1349
+ const MimeUtils = shaka.util.MimeUtils;
1345
1350
 
1346
1351
  // If we are using a platform that does not support media source, we will
1347
1352
  // fall back to src= to handle all playback.
@@ -1396,7 +1401,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1396
1401
  // version there.
1397
1402
 
1398
1403
  // Native HLS can be preferred on any platform via this flag:
1399
- if (this.config_.streaming.preferNativeHls) {
1404
+ if (MimeUtils.isHlsType(mimeType) &&
1405
+ this.config_.streaming.preferNativeHls) {
1400
1406
  return true;
1401
1407
  }
1402
1408
 
@@ -2584,6 +2590,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2584
2590
  // goes wrong.
2585
2591
  new shaka.util.Timer(resolve).tickAfter(1);
2586
2592
  });
2593
+ } else if (textTracks.length > 0) {
2594
+ this.isTextVisible_ = true;
2587
2595
  }
2588
2596
 
2589
2597
  // If we have moved on to another piece of content while waiting for
@@ -4274,6 +4282,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4274
4282
  firstVariantWithLabel.language, '', 0, label);
4275
4283
 
4276
4284
  this.chooseVariantAndSwitch_();
4285
+ } else if (this.video_ && this.video_.audioTracks) {
4286
+ const audioTracks = Array.from(this.video_.audioTracks);
4287
+
4288
+ let trackMatch = null;
4289
+
4290
+ for (const audioTrack of audioTracks) {
4291
+ if (audioTrack.label == label) {
4292
+ trackMatch = audioTrack;
4293
+ }
4294
+ }
4295
+ if (trackMatch) {
4296
+ this.switchHtml5Track_(trackMatch);
4297
+ }
4277
4298
  }
4278
4299
  }
4279
4300
 
@@ -6984,7 +7005,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6984
7005
  * @define {string} A version number taken from git at compile time.
6985
7006
  * @export
6986
7007
  */
6987
- shaka.Player.version = 'v4.3.4-uncompiled';
7008
+ shaka.Player.version = 'v4.3.6-uncompiled';
6988
7009
 
6989
7010
  // Initialize the deprecation system using the version string we just set
6990
7011
  // on the player.