shaka-player 4.7.11 → 4.7.13

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 (60) hide show
  1. package/README.md +2 -4
  2. package/dist/deps.js +6 -7
  3. package/dist/locales.js +34 -34
  4. package/dist/shaka-player.compiled.d.ts +14 -12
  5. package/dist/shaka-player.compiled.debug.d.ts +14 -12
  6. package/dist/shaka-player.compiled.debug.externs.js +78 -64
  7. package/dist/shaka-player.compiled.debug.js +321 -312
  8. package/dist/shaka-player.compiled.debug.map +4 -4
  9. package/dist/shaka-player.compiled.externs.js +78 -64
  10. package/dist/shaka-player.compiled.js +1167 -1153
  11. package/dist/shaka-player.compiled.map +4 -4
  12. package/dist/shaka-player.ui.d.ts +14 -12
  13. package/dist/shaka-player.ui.debug.d.ts +14 -12
  14. package/dist/shaka-player.ui.debug.externs.js +78 -64
  15. package/dist/shaka-player.ui.debug.js +443 -434
  16. package/dist/shaka-player.ui.debug.map +4 -4
  17. package/dist/shaka-player.ui.externs.js +78 -64
  18. package/dist/shaka-player.ui.js +1319 -1305
  19. package/dist/shaka-player.ui.map +4 -4
  20. package/externs/shaka/manifest.js +11 -1
  21. package/externs/shaka/offline.js +7 -1
  22. package/externs/shaka/player.js +32 -19
  23. package/lib/abr/simple_abr_manager.js +21 -26
  24. package/lib/cast/cast_proxy.js +1 -3
  25. package/lib/cast/cast_sender.js +8 -11
  26. package/lib/cea/cea608_memory.js +1 -1
  27. package/lib/cea/ts_cea_parser.js +10 -1
  28. package/lib/dash/dash_parser.js +13 -3
  29. package/lib/dash/segment_base.js +8 -1
  30. package/lib/dash/segment_list.js +24 -18
  31. package/lib/dash/segment_template.js +21 -2
  32. package/lib/hls/hls_parser.js +36 -17
  33. package/lib/media/drm_engine.js +6 -3
  34. package/lib/media/media_source_engine.js +96 -33
  35. package/lib/media/playhead.js +3 -3
  36. package/lib/media/presentation_timeline.js +23 -0
  37. package/lib/media/segment_index.js +21 -20
  38. package/lib/media/segment_utils.js +10 -1
  39. package/lib/media/streaming_engine.js +2 -14
  40. package/lib/mss/mss_parser.js +4 -0
  41. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  42. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  43. package/lib/offline/manifest_converter.js +10 -3
  44. package/lib/offline/storage.js +2 -0
  45. package/lib/player.js +30 -4
  46. package/lib/polyfill/media_capabilities.js +20 -1
  47. package/lib/polyfill/mediasource.js +37 -14
  48. package/lib/text/vtt_text_parser.js +51 -51
  49. package/lib/transmuxer/h264.js +3 -3
  50. package/lib/transmuxer/h265.js +3 -3
  51. package/lib/transmuxer/ts_transmuxer.js +42 -3
  52. package/lib/util/manifest_parser_utils.js +1 -0
  53. package/lib/util/mime_utils.js +10 -5
  54. package/lib/util/mp4_parser.js +57 -0
  55. package/lib/util/periods.js +5 -37
  56. package/lib/util/platform.js +4 -0
  57. package/lib/util/stream_utils.js +334 -271
  58. package/lib/util/ts_parser.js +99 -10
  59. package/package.json +1 -1
  60. package/lib/polyfill/storage_estimate.js +0 -57
@@ -552,7 +552,7 @@ shaka.hls.HlsParser = class {
552
552
  if (goog.DEBUG) {
553
553
  const firstSequenceStartTime =
554
554
  mediaSequenceToStartTime.values().next().value;
555
- goog.asserts.assert(
555
+ shaka.log.warning(
556
556
  firstSequenceStartTime == segment0.startTime,
557
557
  'Sequence number map is not ordered as expected!');
558
558
  }
@@ -586,16 +586,18 @@ shaka.hls.HlsParser = class {
586
586
  // Drop any earlier references.
587
587
  const numSegmentsToDrop =
588
588
  this.minSequenceNumber_ - streamInfo.firstSequenceNumber;
589
- segmentIndex.dropFirstReferences(numSegmentsToDrop);
590
-
591
- // Now adjust timestamps back to begin at 0.
592
- const segmentN = segmentIndex.earliestReference();
593
- if (segmentN) {
594
- const streamOffset = -segmentN.startTime;
595
- // Modify all SegmentReferences equally.
596
- streamInfo.stream.segmentIndex.offset(streamOffset);
597
- // Update other parts of streamInfo the same way.
598
- this.offsetStreamInfo_(streamInfo, streamOffset);
589
+ if (numSegmentsToDrop > 0) {
590
+ segmentIndex.dropFirstReferences(numSegmentsToDrop);
591
+
592
+ // Now adjust timestamps back to begin at 0.
593
+ const segmentN = segmentIndex.earliestReference();
594
+ if (segmentN) {
595
+ const streamOffset = -segmentN.startTime;
596
+ // Modify all SegmentReferences equally.
597
+ streamInfo.stream.segmentIndex.offset(streamOffset);
598
+ // Update other parts of streamInfo the same way.
599
+ this.offsetStreamInfo_(streamInfo, streamOffset);
600
+ }
599
601
  }
600
602
  }
601
603
  }
@@ -986,17 +988,17 @@ shaka.hls.HlsParser = class {
986
988
  }
987
989
 
988
990
  if (!this.presentationTimeline_.isStartTimeLocked()) {
989
- const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
990
- const activeStreamInfos =
991
- streamInfos.filter((s) => s.stream.segmentIndex);
992
- for (const streamInfo of activeStreamInfos) {
991
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
992
+ if (!streamInfo.stream.segmentIndex) {
993
+ continue; // Not active.
994
+ }
993
995
  if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
994
996
  continue;
995
997
  }
996
998
  const firstReference = streamInfo.stream.segmentIndex.get(0);
997
999
  if (firstReference && firstReference.syncTime) {
998
1000
  const syncTime = firstReference.syncTime;
999
- this.presentationTimeline_.setPresentationStartTime(syncTime);
1001
+ this.presentationTimeline_.setInitialProgramDateTime(syncTime);
1000
1002
  }
1001
1003
  }
1002
1004
  }
@@ -1222,6 +1224,7 @@ shaka.hls.HlsParser = class {
1222
1224
  textStreamInfo.stream.mimeType =
1223
1225
  this.guessMimeTypeBeforeLoading_(type, codecs) ||
1224
1226
  this.guessMimeTypeFallback_(type);
1227
+ this.setFullTypeForStream_(textStreamInfo.stream);
1225
1228
  }
1226
1229
  }
1227
1230
  }
@@ -1231,6 +1234,15 @@ shaka.hls.HlsParser = class {
1231
1234
  return textStreams.filter((s) => s);
1232
1235
  }
1233
1236
 
1237
+ /**
1238
+ * @param {!shaka.extern.Stream} stream
1239
+ * @private
1240
+ */
1241
+ setFullTypeForStream_(stream) {
1242
+ stream.fullMimeTypes = new Set([shaka.util.MimeUtils.getFullType(
1243
+ stream.mimeType, stream.codecs)]);
1244
+ }
1245
+
1234
1246
  /**
1235
1247
  * @param {!Array.<!shaka.hls.Tag>} imageTags from the playlist.
1236
1248
  * @param {!Array.<!shaka.hls.Tag>} iFrameTags from the playlist.
@@ -2264,6 +2276,7 @@ shaka.hls.HlsParser = class {
2264
2276
  stream.mimeType = realStream.mimeType;
2265
2277
  stream.bandwidth = stream.bandwidth || realStream.bandwidth;
2266
2278
  stream.codecs = stream.codecs || realStream.codecs;
2279
+ this.setFullTypeForStream_(stream);
2267
2280
 
2268
2281
  // Since we lazy-loaded this content, the player may need to create new
2269
2282
  // sessions for the DRM info in this stream.
@@ -2278,6 +2291,7 @@ shaka.hls.HlsParser = class {
2278
2291
  // To aid manifest filtering, assume before loading that all video
2279
2292
  // renditions have the same MIME type. (And likewise for audio.)
2280
2293
  otherStreamInfo.stream.mimeType = realStream.mimeType;
2294
+ this.setFullTypeForStream_(otherStreamInfo.stream);
2281
2295
  }
2282
2296
  }
2283
2297
  }
@@ -2286,6 +2300,7 @@ shaka.hls.HlsParser = class {
2286
2300
  const firstSegment = realStream.segmentIndex.get(0);
2287
2301
  if (firstSegment && firstSegment.initSegmentReference) {
2288
2302
  stream.mimeType = 'application/mp4';
2303
+ this.setFullTypeForStream_(stream);
2289
2304
  }
2290
2305
  }
2291
2306
 
@@ -2535,6 +2550,7 @@ shaka.hls.HlsParser = class {
2535
2550
  stream.drmInfos = drmInfos;
2536
2551
  stream.keyIds = keyIds;
2537
2552
  stream.mimeType = mimeType;
2553
+ this.setFullTypeForStream_(stream);
2538
2554
 
2539
2555
  const mediaSequenceToStartTime = this.isLive_() ?
2540
2556
  this.mediaSequenceToStartTimeByType_.get(type) : new Map();
@@ -2690,7 +2706,7 @@ shaka.hls.HlsParser = class {
2690
2706
  roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
2691
2707
  }
2692
2708
 
2693
- return {
2709
+ const stream = {
2694
2710
  id: this.globalId_++,
2695
2711
  originalId: name,
2696
2712
  groupId: null,
@@ -2728,7 +2744,10 @@ shaka.hls.HlsParser = class {
2728
2744
  accessibilityPurpose: accessibilityPurpose,
2729
2745
  external: false,
2730
2746
  fastSwitching: false,
2747
+ fullMimeTypes: new Set(),
2731
2748
  };
2749
+ this.setFullTypeForStream_(stream);
2750
+ return stream;
2732
2751
  }
2733
2752
 
2734
2753
  /**
@@ -697,9 +697,12 @@ shaka.media.DrmEngine = class {
697
697
  initDataOverride.initDataType, initDataOverride.initData);
698
698
  }
699
699
 
700
- // If we have no sessions, we need to resolve the promise right now or else
701
- // it will never get resolved.
702
- if (!initDatas.length) {
700
+ // If there were no sessions to load, we need to resolve the promise right
701
+ // now or else it will never get resolved.
702
+ // We determine this by checking areAllSessionsLoaded_, rather than checking
703
+ // the number of initDatas, since the newInitData method can reject init
704
+ // datas in some circumstances.
705
+ if (this.areAllSessionsLoaded_()) {
703
706
  this.allSessionsLoaded_.resolve();
704
707
  }
705
708
 
@@ -31,6 +31,7 @@ goog.require('shaka.util.Mp4BoxParsers');
31
31
  goog.require('shaka.util.Mp4Parser');
32
32
  goog.require('shaka.util.Platform');
33
33
  goog.require('shaka.util.PublicPromise');
34
+ goog.require('shaka.util.StreamUtils');
34
35
  goog.require('shaka.util.TsParser');
35
36
  goog.require('shaka.lcevc.Dec');
36
37
 
@@ -151,6 +152,9 @@ shaka.media.MediaSourceEngine = class {
151
152
 
152
153
  /** @private {?number} */
153
154
  this.lastDuration_ = null;
155
+
156
+ /** @private {?shaka.util.TsParser} */
157
+ this.tsParser_ = null;
154
158
  }
155
159
 
156
160
  /**
@@ -234,18 +238,61 @@ shaka.media.MediaSourceEngine = class {
234
238
  * Checks if a certain type is supported.
235
239
  *
236
240
  * @param {shaka.extern.Stream} stream
237
- * @return {boolean}
241
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
242
+ * @return {!Promise.<boolean>}
238
243
  */
239
- static isStreamSupported(stream) {
244
+ static async isStreamSupported(stream, contentType) {
245
+ if (stream.createSegmentIndex) {
246
+ await stream.createSegmentIndex();
247
+ }
248
+ if (!stream.segmentIndex) {
249
+ return false;
250
+ }
251
+ if (stream.segmentIndex.isEmpty()) {
252
+ return true;
253
+ }
240
254
  const MimeUtils = shaka.util.MimeUtils;
241
- const fullMimeType = MimeUtils.getFullType(stream.mimeType, stream.codecs);
242
- const extendedMimeType = MimeUtils.getExtendedType(stream);
243
- const fullMimeTypeWithAllCodecs = MimeUtils.getFullTypeWithAllCodecs(
244
- stream.mimeType, stream.codecs);
245
255
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
246
- return shaka.text.TextEngine.isTypeSupported(fullMimeType) ||
247
- shaka.media.Capabilities.isTypeSupported(extendedMimeType) ||
248
- TransmuxerEngine.isSupported(fullMimeTypeWithAllCodecs, stream.type);
256
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
257
+ const StreamUtils = shaka.util.StreamUtils;
258
+ const seenCombos = new Set();
259
+ // Check each combination of mimeType and codecs within the segment index.
260
+ // Unfortunately we cannot use fullMimeTypes, as we ALSO need to check the
261
+ // getFullTypeWithAllCodecs (for the sake of the transmuxer) and we have no
262
+ // way of going from a full mimeType to a full mimeType with all codecs.
263
+ // As this function is only called in debug mode, a little inefficiency is
264
+ // acceptable.
265
+ for (const ref of stream.segmentIndex) {
266
+ const mimeType = ref.mimeType || stream.mimeType || '';
267
+ let codecs = ref.codecs || stream.codecs || '';
268
+ // Don't check the same combination of mimetype + codecs twice.
269
+ const combo = mimeType + ':' + codecs;
270
+ if (seenCombos.has(combo)) {
271
+ continue;
272
+ }
273
+ seenCombos.add(combo);
274
+ if (contentType == ContentType.TEXT) {
275
+ const fullMimeType = MimeUtils.getFullType(mimeType, codecs);
276
+ if (!shaka.text.TextEngine.isTypeSupported(fullMimeType)) {
277
+ return false;
278
+ }
279
+ } else {
280
+ if (contentType == ContentType.VIDEO) {
281
+ codecs = StreamUtils.getCorrectVideoCodecs(codecs);
282
+ } else if (contentType == ContentType.AUDIO) {
283
+ codecs = StreamUtils.getCorrectAudioCodecs(codecs);
284
+ }
285
+ const extendedMimeType = MimeUtils.getExtendedType(
286
+ stream, mimeType, codecs);
287
+ const fullMimeType = MimeUtils.getFullTypeWithAllCodecs(
288
+ mimeType, codecs);
289
+ if (!shaka.media.Capabilities.isTypeSupported(extendedMimeType) &&
290
+ !TransmuxerEngine.isSupported(fullMimeType, stream.type)) {
291
+ return false;
292
+ }
293
+ }
294
+ }
295
+ return true;
249
296
  }
250
297
 
251
298
  /**
@@ -392,6 +439,8 @@ shaka.media.MediaSourceEngine = class {
392
439
 
393
440
  // This object is owned by Player
394
441
  this.lcevcDec_ = null;
442
+
443
+ this.tsParser_ = null;
395
444
  }
396
445
 
397
446
  /**
@@ -442,9 +491,12 @@ shaka.media.MediaSourceEngine = class {
442
491
  this.manifestType_ == shaka.media.ManifestParser.HLS &&
443
492
  !this.ignoreManifestTimestampsInSegmentsMode_;
444
493
 
494
+ this.tsParser_ = null;
495
+
445
496
  for (const contentType of streamsByType.keys()) {
446
497
  const stream = streamsByType.get(contentType);
447
- this.initSourceBuffer_(contentType, stream, stream.codecs);
498
+ // eslint-disable-next-line no-await-in-loop
499
+ await this.initSourceBuffer_(contentType, stream, stream.codecs);
448
500
  if (this.needSplitMuxedContent_) {
449
501
  this.queues_[ContentType.AUDIO] = [];
450
502
  this.queues_[ContentType.VIDEO] = [];
@@ -460,14 +512,15 @@ shaka.media.MediaSourceEngine = class {
460
512
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
461
513
  * @param {shaka.extern.Stream} stream
462
514
  * @param {string} codecs
463
- * @param {boolean=} forceTransmux
515
+ * @return {!Promise}
464
516
  * @private
465
517
  */
466
- initSourceBuffer_(contentType, stream, codecs, forceTransmux = false) {
518
+ async initSourceBuffer_(contentType, stream, codecs) {
467
519
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
468
520
 
469
521
  goog.asserts.assert(
470
- shaka.media.MediaSourceEngine.isStreamSupported(stream),
522
+ await shaka.media.MediaSourceEngine.isStreamSupported(
523
+ stream, contentType),
471
524
  'Type negotiation should happen before MediaSourceEngine.init!');
472
525
 
473
526
  let mimeType = shaka.util.MimeUtils.getFullType(
@@ -475,7 +528,7 @@ shaka.media.MediaSourceEngine = class {
475
528
  if (contentType == ContentType.TEXT) {
476
529
  this.reinitText(mimeType, this.sequenceMode_, stream.external);
477
530
  } else {
478
- let needTransmux = this.config_.forceTransmux || forceTransmux;
531
+ let needTransmux = this.config_.forceTransmux;
479
532
  if (!shaka.media.Capabilities.isTypeSupported(mimeType) ||
480
533
  (!this.sequenceMode_ &&
481
534
  shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType))) {
@@ -484,7 +537,6 @@ shaka.media.MediaSourceEngine = class {
484
537
  const mimeTypeWithAllCodecs =
485
538
  shaka.util.MimeUtils.getFullTypeWithAllCodecs(
486
539
  stream.mimeType, codecs);
487
- const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
488
540
  if (needTransmux) {
489
541
  const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
490
542
  ContentType.AUDIO, (codecs || '').split(','));
@@ -492,14 +544,12 @@ shaka.media.MediaSourceEngine = class {
492
544
  ContentType.VIDEO, (codecs || '').split(','));
493
545
  if (audioCodec && videoCodec) {
494
546
  this.needSplitMuxedContent_ = true;
495
- this.initSourceBuffer_(
496
- ContentType.AUDIO, stream, audioCodec, /* forceTransmux= */ true);
497
- this.initSourceBuffer_(
498
- ContentType.VIDEO, stream, videoCodec, /* forceTransmux= */ true);
547
+ await this.initSourceBuffer_(ContentType.AUDIO, stream, audioCodec);
548
+ await this.initSourceBuffer_(ContentType.VIDEO, stream, videoCodec);
499
549
  return;
500
550
  }
501
- const transmuxerPlugin =
502
- TransmuxerEngine.findTransmuxer(mimeTypeWithAllCodecs);
551
+ const transmuxerPlugin = shaka.transmuxer.TransmuxerEngine
552
+ .findTransmuxer(mimeTypeWithAllCodecs);
503
553
  if (transmuxerPlugin) {
504
554
  const transmuxer = transmuxerPlugin();
505
555
  this.transmuxers_[contentType] = transmuxer;
@@ -789,7 +839,12 @@ shaka.media.MediaSourceEngine = class {
789
839
  }
790
840
  } else if (!mimeType.includes('/mp4') && !mimeType.includes('/webm') &&
791
841
  shaka.util.TsParser.probe(uint8ArrayData)) {
792
- const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
842
+ if (!this.tsParser_) {
843
+ this.tsParser_ = new shaka.util.TsParser();
844
+ } else {
845
+ this.tsParser_.clearData();
846
+ }
847
+ const tsParser = this.tsParser_.parse(uint8ArrayData);
793
848
  const startTime = tsParser.getStartTime(contentType);
794
849
  if (startTime != null) {
795
850
  timestamp = startTime;
@@ -1177,6 +1232,12 @@ shaka.media.MediaSourceEngine = class {
1177
1232
  return;
1178
1233
  }
1179
1234
 
1235
+ // Reset the promise in case the timestamp offset changed during
1236
+ // a period/discontinuity transition.
1237
+ if (contentType == ContentType.VIDEO) {
1238
+ this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
1239
+ }
1240
+
1180
1241
  // Queue an abort() to help MSE splice together overlapping segments.
1181
1242
  // We set appendWindowEnd when we change periods in DASH content, and the
1182
1243
  // period transition may result in overlap.
@@ -1787,7 +1848,8 @@ shaka.media.MediaSourceEngine = class {
1787
1848
 
1788
1849
  for (const contentType of streamsByType.keys()) {
1789
1850
  const stream = streamsByType.get(contentType);
1790
- this.initSourceBuffer_(contentType, stream, stream.codecs);
1851
+ // eslint-disable-next-line no-await-in-loop
1852
+ await this.initSourceBuffer_(contentType, stream, stream.codecs);
1791
1853
  if (this.needSplitMuxedContent_) {
1792
1854
  this.queues_[ContentType.AUDIO] = [];
1793
1855
  this.queues_[ContentType.VIDEO] = [];
@@ -1857,8 +1919,7 @@ shaka.media.MediaSourceEngine = class {
1857
1919
  let needTransmux = this.config_.forceTransmux;
1858
1920
  if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
1859
1921
  (!this.sequenceMode_ &&
1860
- shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType)) ||
1861
- codecs.includes(',')) {
1922
+ shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType))) {
1862
1923
  needTransmux = true;
1863
1924
  }
1864
1925
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
@@ -1921,10 +1982,12 @@ shaka.media.MediaSourceEngine = class {
1921
1982
  *
1922
1983
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1923
1984
  * @param {shaka.extern.Stream} stream
1985
+ * @param {string} refMimeType
1986
+ * @param {string} refCodecs
1924
1987
  * @return {boolean}
1925
1988
  * @private
1926
1989
  */
1927
- isCodecSwitchNecessary_(contentType, stream) {
1990
+ isCodecSwitchNecessary_(contentType, stream, refMimeType, refCodecs) {
1928
1991
  if (contentType == shaka.util.ManifestParserUtils.ContentType.TEXT) {
1929
1992
  return false;
1930
1993
  }
@@ -1934,18 +1997,16 @@ shaka.media.MediaSourceEngine = class {
1934
1997
  const currentBasicType = MimeUtils.getBasicType(
1935
1998
  this.sourceBufferTypes_[contentType]);
1936
1999
 
1937
- let newMimeType = shaka.util.MimeUtils.getFullType(
1938
- stream.mimeType, stream.codecs);
2000
+ let newMimeType = shaka.util.MimeUtils.getFullType(refMimeType, refCodecs);
1939
2001
  let needTransmux = this.config_.forceTransmux;
1940
2002
  if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
1941
2003
  (!this.sequenceMode_ &&
1942
- shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType)) ||
1943
- stream.codecs.includes(',')) {
2004
+ shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType))) {
1944
2005
  needTransmux = true;
1945
2006
  }
1946
2007
  const newMimeTypeWithAllCodecs =
1947
2008
  shaka.util.MimeUtils.getFullTypeWithAllCodecs(
1948
- stream.mimeType, stream.codecs);
2009
+ refMimeType, refCodecs);
1949
2010
  const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
1950
2011
  if (needTransmux) {
1951
2012
  const transmuxerPlugin =
@@ -1971,10 +2032,12 @@ shaka.media.MediaSourceEngine = class {
1971
2032
  *
1972
2033
  * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1973
2034
  * @param {shaka.extern.Stream} stream
2035
+ * @param {string} mimeType
2036
+ * @param {string} codecs
1974
2037
  * @return {boolean}
1975
2038
  */
1976
- isResetMediaSourceNecessary(contentType, stream) {
1977
- if (!this.isCodecSwitchNecessary_(contentType, stream)) {
2039
+ isResetMediaSourceNecessary(contentType, stream, mimeType, codecs) {
2040
+ if (!this.isCodecSwitchNecessary_(contentType, stream, mimeType, codecs)) {
1978
2041
  return false;
1979
2042
  }
1980
2043
 
@@ -382,10 +382,10 @@ shaka.media.MediaSourcePlayhead = class {
382
382
  seekStart = seekEnd - this.minSeekRange_;
383
383
  }
384
384
 
385
- if (currentTime < seekStart) {
386
- // The seek range has moved past the playhead. Move ahead to catch up.
385
+ if (currentTime < seekStart || currentTime > seekEnd) {
386
+ // playhead is outside of the seekrange. Move playhead to catch up.
387
387
  const targetTime = this.reposition_(currentTime);
388
- shaka.log.info('Jumping forward ' + (targetTime - currentTime) +
388
+ shaka.log.info('Jumping by ' + (targetTime - currentTime) +
389
389
  ' seconds to catch up with the seek range.');
390
390
  this.mediaElement_.currentTime = targetTime;
391
391
  }
@@ -96,6 +96,9 @@ shaka.media.PresentationTimeline = class {
96
96
 
97
97
  /** @private {boolean} */
98
98
  this.startTimeLocked_ = false;
99
+
100
+ /** @private {?number} */
101
+ this.initialProgramDateTime_ = null;
99
102
  }
100
103
 
101
104
 
@@ -339,6 +342,26 @@ shaka.media.PresentationTimeline = class {
339
342
  }
340
343
 
341
344
 
345
+ /**
346
+ * Sets the initial program date time.
347
+ *
348
+ * @param {number} initialProgramDateTime
349
+ * @export
350
+ */
351
+ setInitialProgramDateTime(initialProgramDateTime) {
352
+ this.initialProgramDateTime_ = initialProgramDateTime;
353
+ }
354
+
355
+
356
+ /**
357
+ * @return {?number} The initial program date time in seconds.
358
+ * @export
359
+ */
360
+ getInitialProgramDateTime() {
361
+ return this.initialProgramDateTime_;
362
+ }
363
+
364
+
342
365
  /**
343
366
  * Gives PresentationTimeline a Stream's minimum segment start time.
344
367
  *
@@ -363,26 +363,27 @@ shaka.media.SegmentIndex = class {
363
363
 
364
364
  // Adjust the last SegmentReference.
365
365
  const lastReference = this.references[this.references.length - 1];
366
- this.references[this.references.length - 1] =
367
- new shaka.media.SegmentReference(
368
- lastReference.startTime,
369
- /* endTime= */ windowEnd,
370
- lastReference.getUrisInner,
371
- lastReference.startByte,
372
- lastReference.endByte,
373
- lastReference.initSegmentReference,
374
- lastReference.timestampOffset,
375
- lastReference.appendWindowStart,
376
- lastReference.appendWindowEnd,
377
- lastReference.partialReferences,
378
- lastReference.tilesLayout,
379
- lastReference.tileDuration,
380
- lastReference.syncTime,
381
- lastReference.status,
382
- lastReference.aes128Key,
383
- );
384
- this.references[this.references.length - 1].discontinuitySequence =
385
- lastReference.discontinuitySequence;
366
+ const newReference = new shaka.media.SegmentReference(
367
+ lastReference.startTime,
368
+ /* endTime= */ windowEnd,
369
+ lastReference.getUrisInner,
370
+ lastReference.startByte,
371
+ lastReference.endByte,
372
+ lastReference.initSegmentReference,
373
+ lastReference.timestampOffset,
374
+ lastReference.appendWindowStart,
375
+ lastReference.appendWindowEnd,
376
+ lastReference.partialReferences,
377
+ lastReference.tilesLayout,
378
+ lastReference.tileDuration,
379
+ lastReference.syncTime,
380
+ lastReference.status,
381
+ lastReference.aes128Key,
382
+ );
383
+ newReference.mimeType = lastReference.mimeType;
384
+ newReference.codecs = lastReference.codecs;
385
+ newReference.discontinuitySequence = lastReference.discontinuitySequence;
386
+ this.references[this.references.length - 1] = newReference;
386
387
  }
387
388
 
388
389
 
@@ -55,6 +55,7 @@ shaka.media.SegmentUtils = class {
55
55
  let hasVideo = false;
56
56
  switch (tsCodecs.audio) {
57
57
  case 'aac':
58
+ case 'aac-loas':
58
59
  codecs.push('mp4a.40.2');
59
60
  hasAudio = true;
60
61
  break;
@@ -70,6 +71,10 @@ shaka.media.SegmentUtils = class {
70
71
  codecs.push('ec-3');
71
72
  hasAudio = true;
72
73
  break;
74
+ case 'opus':
75
+ codecs.push('opus');
76
+ hasAudio = true;
77
+ break;
73
78
  }
74
79
  switch (tsCodecs.video) {
75
80
  case 'avc':
@@ -88,6 +93,10 @@ shaka.media.SegmentUtils = class {
88
93
  }
89
94
  hasVideo = true;
90
95
  break;
96
+ case 'av1':
97
+ codecs.push('av01.0.01M.08');
98
+ hasVideo = true;
99
+ break;
91
100
  }
92
101
  if (!codecs.length) {
93
102
  return null;
@@ -256,7 +265,7 @@ shaka.media.SegmentUtils = class {
256
265
  // This signals an encrypted sample, which we can go inside of to
257
266
  // find the codec used.
258
267
  // Note: If encrypted, you can only have audio or video, not both.
259
- .box('enca', Mp4Parser.visualSampleEntry)
268
+ .box('enca', Mp4Parser.audioSampleEntry)
260
269
  .box('encv', Mp4Parser.visualSampleEntry)
261
270
  .box('sinf', Mp4Parser.children)
262
271
  .box('frma', (box) => {
@@ -881,7 +881,6 @@ shaka.media.StreamingEngine = class {
881
881
  // The playhead might be seeking on startup, if a start time is set, so
882
882
  // start "seeked" as true.
883
883
  seeked: true,
884
- needsResync: false,
885
884
  recovering: false,
886
885
  hasError: false,
887
886
  operation: null,
@@ -1248,11 +1247,6 @@ shaka.media.StreamingEngine = class {
1248
1247
  shaka.log.v1(
1249
1248
  logPrefix, 'looking up segment from new stream endTime:', time);
1250
1249
 
1251
- // Using a new iterator means we need to resync the stream in sequence
1252
- // mode. The buffered range might not align perfectly with the last
1253
- // segment end time, so we may end up repeating a segment. Resyncing
1254
- // makes this safe to do.
1255
- mediaState.needsResync = true;
1256
1250
  mediaState.segmentIterator =
1257
1251
  mediaState.stream.segmentIndex.getIteratorForTime(time);
1258
1252
  const ref = mediaState.segmentIterator &&
@@ -1730,7 +1724,7 @@ shaka.media.StreamingEngine = class {
1730
1724
  const isResetMediaSourceNecessary =
1731
1725
  mediaState.lastCodecs && mediaState.lastMimeType &&
1732
1726
  this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
1733
- mediaState.type, mediaState.stream);
1727
+ mediaState.type, mediaState.stream, mimeType, codecs);
1734
1728
  if (isResetMediaSourceNecessary) {
1735
1729
  let otherState = null;
1736
1730
  if (mediaState.type === ContentType.VIDEO) {
@@ -1879,9 +1873,7 @@ shaka.media.StreamingEngine = class {
1879
1873
  // Across discontinuity bounds, we should resync timestamps for
1880
1874
  // sequence mode playbacks. The next segment appended should
1881
1875
  // land at its theoretical timestamp from the segment index.
1882
- if (reference.discontinuitySequence != lastDiscontinuitySequence ||
1883
- mediaState.needsResync) {
1884
- mediaState.needsResync = false;
1876
+ if (reference.discontinuitySequence != lastDiscontinuitySequence) {
1885
1877
  operations.push(this.playerInterface_.mediaSourceEngine.resync(
1886
1878
  mediaState.type, reference.startTime));
1887
1879
  }
@@ -2591,7 +2583,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2591
2583
  * clearBufferSafeMargin: number,
2592
2584
  * clearingBuffer: boolean,
2593
2585
  * seeked: boolean,
2594
- * needsResync: boolean,
2595
2586
  * adaptation: boolean,
2596
2587
  * recovering: boolean,
2597
2588
  * hasError: boolean,
@@ -2646,9 +2637,6 @@ shaka.media.StreamingEngine.PlayerInterface;
2646
2637
  * True indicates that the presentation just seeked.
2647
2638
  * @property {boolean} adaptation
2648
2639
  * True indicates that the presentation just automatically switched variants.
2649
- * @property {boolean} needsResync
2650
- * True indicates that the stream needs to be resynced in sequence mode,
2651
- * regardless of discontinuity sequence.
2652
2640
  * @property {boolean} recovering
2653
2641
  * True indicates that the last segment was not appended because it could not
2654
2642
  * fit in the buffer.
@@ -19,6 +19,7 @@ goog.require('shaka.net.NetworkingEngine');
19
19
  goog.require('shaka.util.Error');
20
20
  goog.require('shaka.util.LanguageUtils');
21
21
  goog.require('shaka.util.ManifestParserUtils');
22
+ goog.require('shaka.util.MimeUtils');
22
23
  goog.require('shaka.util.Mp4Generator');
23
24
  goog.require('shaka.util.OperationManager');
24
25
  goog.require('shaka.util.Timer');
@@ -556,6 +557,7 @@ shaka.mss.MssParser = class {
556
557
  accessibilityPurpose: null,
557
558
  external: false,
558
559
  fastSwitching: false,
560
+ fullMimeTypes: new Set(),
559
561
  };
560
562
 
561
563
  // This is specifically for text tracks.
@@ -634,6 +636,8 @@ shaka.mss.MssParser = class {
634
636
  }
635
637
  break;
636
638
  }
639
+ stream.fullMimeTypes.add(shaka.util.MimeUtils.getFullType(
640
+ stream.mimeType, stream.codecs));
637
641
 
638
642
  // Lazy-Load the segment index to avoid create all init segment at the
639
643
  // same time
@@ -211,6 +211,8 @@ shaka.offline.indexeddb.V1StorageCell = class
211
211
  appendWindowEnd,
212
212
  timestampOffset,
213
213
  tilesLayout: '',
214
+ mimeType: null,
215
+ codecs: null,
214
216
  };
215
217
  }
216
218
 
@@ -155,6 +155,8 @@ shaka.offline.indexeddb.V2StorageCell = class
155
155
  timestampOffset,
156
156
  dataKey: old.dataKey,
157
157
  tilesLayout: '',
158
+ mimeType: null,
159
+ codecs: null,
158
160
  };
159
161
  }
160
162
  };