shaka-player 4.9.30 → 4.9.32

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 (50) hide show
  1. package/.eslintrc.js +1 -0
  2. package/AUTHORS +2 -0
  3. package/CONTRIBUTORS +2 -0
  4. package/dist/controls.css +1 -1
  5. package/dist/controls.css.map +1 -1
  6. package/dist/deps.js +2 -2
  7. package/dist/locales.js +33 -33
  8. package/dist/shaka-player.compiled.d.ts +8 -7
  9. package/dist/shaka-player.compiled.debug.d.ts +8 -7
  10. package/dist/shaka-player.compiled.debug.externs.js +3 -0
  11. package/dist/shaka-player.compiled.debug.js +264 -260
  12. package/dist/shaka-player.compiled.debug.map +3 -3
  13. package/dist/shaka-player.compiled.externs.js +3 -0
  14. package/dist/shaka-player.compiled.js +1214 -1212
  15. package/dist/shaka-player.compiled.map +3 -3
  16. package/dist/shaka-player.ui.d.ts +8 -7
  17. package/dist/shaka-player.ui.debug.d.ts +8 -7
  18. package/dist/shaka-player.ui.debug.externs.js +3 -0
  19. package/dist/shaka-player.ui.debug.js +290 -285
  20. package/dist/shaka-player.ui.debug.map +3 -3
  21. package/dist/shaka-player.ui.externs.js +3 -0
  22. package/dist/shaka-player.ui.js +1417 -1414
  23. package/dist/shaka-player.ui.map +3 -3
  24. package/externs/shaka/abortable.js +36 -36
  25. package/lib/dash/segment_template.js +19 -2
  26. package/lib/hls/hls_parser.js +1 -1
  27. package/lib/media/adaptation_set_criteria.js +8 -1
  28. package/lib/media/content_workarounds.js +2 -1
  29. package/lib/media/drm_engine.js +10 -115
  30. package/lib/media/media_source_engine.js +99 -36
  31. package/lib/media/preload_manager.js +6 -1
  32. package/lib/media/segment_index.js +20 -0
  33. package/lib/media/segment_prefetch.js +7 -0
  34. package/lib/media/segment_reference.js +1 -1
  35. package/lib/media/streaming_engine.js +16 -13
  36. package/lib/mss/mss_parser.js +6 -0
  37. package/lib/net/networking_engine.js +3 -3
  38. package/lib/player.js +16 -2
  39. package/lib/polyfill/media_capabilities.js +48 -9
  40. package/lib/polyfill/mediasource.js +12 -0
  41. package/lib/text/ui_text_displayer.js +8 -0
  42. package/lib/text/vtt_text_parser.js +2 -8
  43. package/lib/util/abortable_operation.js +7 -5
  44. package/lib/util/mp4_generator.js +1 -1
  45. package/lib/util/platform.js +22 -11
  46. package/package.json +2 -2
  47. package/ui/controls.js +2 -2
  48. package/ui/less/containers.less +8 -0
  49. package/ui/remote_button.js +42 -17
  50. package/ui/statistics_button.js +0 -2
@@ -18,39 +18,39 @@
18
18
  * @template T
19
19
  * @exportDoc
20
20
  */
21
- shaka.extern.IAbortableOperation = function() {};
22
-
23
-
24
- /**
25
- * A Promise which represents the underlying operation. It is resolved when
26
- * the operation is complete, and rejected if the operation fails or is
27
- * aborted. Aborted operations should be rejected with a shaka.util.Error
28
- * object using the error code OPERATION_ABORTED.
29
- *
30
- * @const {!Promise.<T>}
31
- * @exportDoc
32
- */
33
- shaka.extern.IAbortableOperation.prototype.promise;
34
-
35
-
36
- /**
37
- * Can be called by anyone holding this object to abort the underlying
38
- * operation. This is not cancelation, and will not necessarily result in
39
- * any work being undone. abort() should return a Promise which is resolved
40
- * when the underlying operation has been aborted. The returned Promise
41
- * should never be rejected.
42
- *
43
- * @return {!Promise}
44
- * @exportDoc
45
- */
46
- shaka.extern.IAbortableOperation.prototype.abort = function() {};
47
-
48
-
49
- /**
50
- * @param {function(boolean)} onFinal A callback to be invoked after the
51
- * operation succeeds or fails. The boolean argument is true if the operation
52
- * succeeded and false if it failed.
53
- * @return {!shaka.extern.IAbortableOperation.<T>} Returns this.
54
- * @exportDoc
55
- */
56
- shaka.extern.IAbortableOperation.prototype.finally = function(onFinal) {};
21
+ shaka.extern.IAbortableOperation = class {
22
+ constructor() {
23
+ /**
24
+ * A Promise which represents the underlying operation. It is resolved when
25
+ * the operation is complete, and rejected if the operation fails or is
26
+ * aborted. Aborted operations should be rejected with a shaka.util.Error
27
+ * object using the error code OPERATION_ABORTED.
28
+ *
29
+ * @const {!Promise.<T>}
30
+ * @exportDoc
31
+ */
32
+ this.promise;
33
+ }
34
+
35
+ /**
36
+ * Can be called by anyone holding this object to abort the underlying
37
+ * operation. This is not cancelation, and will not necessarily result in
38
+ * any work being undone. abort() should return a Promise which is resolved
39
+ * when the underlying operation has been aborted. The returned Promise
40
+ * should never be rejected.
41
+ *
42
+ * @return {!Promise}
43
+ * @exportDoc
44
+ */
45
+ abort() {}
46
+
47
+
48
+ /**
49
+ * @param {function(boolean)} onFinal A callback to be invoked after the
50
+ * operation succeeds or fails. The boolean argument is true
51
+ * if the operation succeeded and false if it failed.
52
+ * @return {!shaka.extern.IAbortableOperation.<T>} Returns this.
53
+ * @exportDoc
54
+ */
55
+ finally(onFinal) {}
56
+ };
@@ -85,7 +85,9 @@ shaka.dash.SegmentTemplate = class {
85
85
  },
86
86
  };
87
87
  } else if (info.segmentDuration) {
88
- if (!isUpdate && context.adaptationSet.contentType !== 'image') {
88
+ if (!isUpdate &&
89
+ context.adaptationSet.contentType !== 'image' &&
90
+ context.adaptationSet.contentType !== 'text') {
89
91
  context.presentationTimeline.notifyMaxSegmentDuration(
90
92
  info.segmentDuration);
91
93
  context.presentationTimeline.notifyMinSegmentStartTime(
@@ -161,7 +163,9 @@ shaka.dash.SegmentTemplate = class {
161
163
  tsi.evict(availabilityStart);
162
164
  }
163
165
 
164
- if (info.timeline && context.adaptationSet.contentType !== 'image') {
166
+ if (info.timeline &&
167
+ context.adaptationSet.contentType !== 'image' &&
168
+ context.adaptationSet.contentType !== 'text') {
165
169
  const timeline = info.timeline;
166
170
  context.presentationTimeline.notifyTimeRange(
167
171
  timeline,
@@ -1029,6 +1033,19 @@ shaka.dash.TimelineSegmentIndex = class extends shaka.media.SegmentIndex {
1029
1033
  return ref;
1030
1034
  }
1031
1035
 
1036
+ /**
1037
+ * @override
1038
+ */
1039
+ forEachTopLevelReference(fn) {
1040
+ this.fitTimeline();
1041
+ for (let i = 0; i < this.getNumReferences(); i++) {
1042
+ const reference = this.get(i + this.numEvicted_);
1043
+ if (reference) {
1044
+ fn(reference);
1045
+ }
1046
+ }
1047
+ }
1048
+
1032
1049
  /**
1033
1050
  * Fill in a specific template with values to get the segment uris
1034
1051
  *
@@ -3705,7 +3705,7 @@ shaka.hls.HlsParser = class {
3705
3705
  let aesKey = undefined;
3706
3706
 
3707
3707
  let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
3708
- playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
3708
+ playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', -1);
3709
3709
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
3710
3710
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
3711
3711
  const skipTag = shaka.hls.Utils.getFirstTagWithName(
@@ -356,7 +356,14 @@ shaka.media.PreferenceBasedCriteria = class {
356
356
  if (hdrLevel == 'AUTO') {
357
357
  // Auto detect the ideal HDR level.
358
358
  if (window.matchMedia('(color-gamut: p3)').matches) {
359
- hdrLevel = 'PQ';
359
+ const someHLG = variants.some((variant) => {
360
+ if (variant.video && variant.video.hdr &&
361
+ variant.video.hdr == 'HLG') {
362
+ return true;
363
+ }
364
+ return false;
365
+ });
366
+ hdrLevel = someHLG ? 'HLG' : 'PQ';
360
367
  } else {
361
368
  hdrLevel = 'SDR';
362
369
  }
@@ -176,7 +176,8 @@ shaka.media.ContentWorkarounds = class {
176
176
  // patched one, otherwise video element throws following error:
177
177
  // CHUNK_DEMUXER_ERROR_APPEND_FAILED: Sample encryption info is not
178
178
  // available.
179
- if (shaka.util.Platform.isEdge() && shaka.util.Platform.isWindows()) {
179
+ if (shaka.util.Platform.isEdge() && shaka.util.Platform.isWindows() &&
180
+ !shaka.util.Platform.isXboxOne()) {
180
181
  const doubleInitSegment = new Uint8Array(initSegment.byteLength +
181
182
  modifiedInitSegment.byteLength);
182
183
  doubleInitSegment.set(modifiedInitSegment);
@@ -9,7 +9,6 @@ goog.provide('shaka.media.DrmEngine');
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
11
  goog.require('shaka.net.NetworkingEngine');
12
- goog.require('shaka.transmuxer.TransmuxerEngine');
13
12
  goog.require('shaka.util.BufferUtils');
14
13
  goog.require('shaka.util.Destroyer');
15
14
  goog.require('shaka.util.DrmUtils');
@@ -20,7 +19,6 @@ goog.require('shaka.util.IDestroyable');
20
19
  goog.require('shaka.util.Iterables');
21
20
  goog.require('shaka.util.ManifestParserUtils');
22
21
  goog.require('shaka.util.MapUtils');
23
- goog.require('shaka.util.MimeUtils');
24
22
  goog.require('shaka.util.ObjectUtils');
25
23
  goog.require('shaka.util.Platform');
26
24
  goog.require('shaka.util.Pssh');
@@ -41,9 +39,6 @@ shaka.media.DrmEngine = class {
41
39
  /** @private {?shaka.media.DrmEngine.PlayerInterface} */
42
40
  this.playerInterface_ = playerInterface;
43
41
 
44
- /** @private {!Set.<string>} */
45
- this.supportedTypes_ = new Set();
46
-
47
42
  /** @private {MediaKeys} */
48
43
  this.mediaKeys_ = null;
49
44
 
@@ -197,7 +192,6 @@ shaka.media.DrmEngine = class {
197
192
 
198
193
  // Break references to everything else we hold internally.
199
194
  this.currentDrmInfo_ = null;
200
- this.supportedTypes_.clear();
201
195
  this.mediaKeys_ = null;
202
196
  this.storedPersistentSessions_ = new Map();
203
197
  this.config_ = null;
@@ -512,7 +506,14 @@ shaka.media.DrmEngine = class {
512
506
  this.video_ = video;
513
507
 
514
508
  this.eventManager_.listenOnce(this.video_, 'play', () => this.onPlay_());
515
- if ('webkitCurrentPlaybackTargetIsWireless' in this.video_) {
509
+ if (this.video_.remote) {
510
+ this.eventManager_.listen(this.video_.remote, 'connect',
511
+ () => this.closeOpenSessions_());
512
+ this.eventManager_.listen(this.video_.remote, 'connecting',
513
+ () => this.closeOpenSessions_());
514
+ this.eventManager_.listen(this.video_.remote, 'disconnect',
515
+ () => this.closeOpenSessions_());
516
+ } else if ('webkitCurrentPlaybackTargetIsWireless' in this.video_) {
516
517
  this.eventManager_.listen(this.video_,
517
518
  'webkitcurrentplaybacktargetiswirelesschanged',
518
519
  () => this.closeOpenSessions_());
@@ -773,40 +774,6 @@ shaka.media.DrmEngine = class {
773
774
  return this.initialized_;
774
775
  }
775
776
 
776
- /**
777
- * Check if DrmEngine (as initialized) will likely be able to support the
778
- * given content type.
779
- *
780
- * @param {string} contentType
781
- * @return {boolean}
782
- */
783
- willSupport(contentType) {
784
- // Edge 14 does not report correct capabilities. It will only report the
785
- // first MIME type even if the others are supported. To work around this,
786
- // we say that Edge supports everything.
787
- //
788
- // See https://github.com/shaka-project/shaka-player/issues/1495 for details.
789
- if (shaka.util.Platform.isLegacyEdge()) {
790
- return true;
791
- }
792
-
793
- contentType = contentType.toLowerCase();
794
-
795
- if (shaka.util.Platform.isTizen() &&
796
- contentType.includes('codecs="ac-3"')) {
797
- // Some Tizen devices seem to misreport AC-3 support. This works around
798
- // the issue, by falling back to EC-3, which seems to be supported on the
799
- // same devices and be correctly reported in all cases we have observed.
800
- // See https://github.com/shaka-project/shaka-player/issues/2989 for
801
- // details.
802
- const fallback = contentType.replace('ac-3', 'ec-3');
803
- return this.supportedTypes_.has(contentType) ||
804
- this.supportedTypes_.has(fallback);
805
- }
806
-
807
- return this.supportedTypes_.has(contentType);
808
- }
809
-
810
777
  /**
811
778
  * Returns the ID of the sessions currently active.
812
779
  *
@@ -907,25 +874,6 @@ shaka.media.DrmEngine = class {
907
874
  return Array.from(this.activeSessions_.keys());
908
875
  }
909
876
 
910
-
911
- /**
912
- * @param {shaka.extern.Stream} stream
913
- * @param {string=} codecOverride
914
- * @return {string}
915
- * @private
916
- */
917
- static computeMimeType_(stream, codecOverride) {
918
- const realMimeType = shaka.util.MimeUtils.getFullType(stream.mimeType,
919
- codecOverride || stream.codecs);
920
- const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
921
- if (TransmuxerEngine.isSupported(realMimeType, stream.type)) {
922
- // This will be handled by the Transmuxer, so use the MIME type that the
923
- // Transmuxer will produce.
924
- return TransmuxerEngine.convertCodecs(stream.type, realMimeType);
925
- }
926
- return realMimeType;
927
- }
928
-
929
877
  /**
930
878
  * @param {!Map.<string, MediaKeySystemConfiguration>} configsByKeySystem
931
879
  * A dictionary of configs, indexed by key system, with an iteration order
@@ -950,11 +898,6 @@ shaka.media.DrmEngine = class {
950
898
  this.destroyer_.ensureNotDestroyed();
951
899
 
952
900
  try {
953
- // Get the set of supported content types from the audio and video
954
- // capabilities. Avoid duplicates so that it is easier to read what is
955
- // supported.
956
- this.supportedTypes_.clear();
957
-
958
901
  // Store the capabilities of the key system.
959
902
  const realConfig = mediaKeySystemAccess.getConfiguration();
960
903
 
@@ -962,20 +905,6 @@ shaka.media.DrmEngine = class {
962
905
  'Got MediaKeySystemAccess with configuration',
963
906
  realConfig);
964
907
 
965
- const audioCaps = realConfig.audioCapabilities || [];
966
- const videoCaps = realConfig.videoCapabilities || [];
967
-
968
- for (const cap of audioCaps) {
969
- this.supportedTypes_.add(cap.contentType.toLowerCase());
970
- }
971
-
972
- for (const cap of videoCaps) {
973
- this.supportedTypes_.add(cap.contentType.toLowerCase());
974
- }
975
-
976
- goog.asserts.assert(this.supportedTypes_.size,
977
- 'We should get at least one supported MIME type');
978
-
979
908
  if (variants.length) {
980
909
  this.currentDrmInfo_ = this.createDrmInfoByInfos_(
981
910
  mediaKeySystemAccess.keySystem,
@@ -1032,7 +961,6 @@ shaka.media.DrmEngine = class {
1032
961
 
1033
962
  // Don't rewrap a shaka.util.Error from earlier in the chain:
1034
963
  this.currentDrmInfo_ = null;
1035
- this.supportedTypes_.clear();
1036
964
  if (exception instanceof shaka.util.Error) {
1037
965
  throw exception;
1038
966
  }
@@ -1779,6 +1707,7 @@ shaka.media.DrmEngine = class {
1779
1707
  const testKeySystems = [
1780
1708
  'org.w3.clearkey',
1781
1709
  'com.widevine.alpha',
1710
+ 'com.widevine.alpha.experiment', // Widevine L1 in Windows
1782
1711
  'com.microsoft.playready',
1783
1712
  'com.microsoft.playready.hardware',
1784
1713
  'com.microsoft.playready.recommendation',
@@ -1803,6 +1732,7 @@ shaka.media.DrmEngine = class {
1803
1732
 
1804
1733
  const testRobustness = {
1805
1734
  'com.widevine.alpha': widevineRobustness,
1735
+ 'com.widevine.alpha.experiment': widevineRobustness,
1806
1736
  'com.microsoft.playready.recommendation': playreadyRobustness,
1807
1737
  };
1808
1738
 
@@ -2074,41 +2004,6 @@ shaka.media.DrmEngine = class {
2074
2004
  }));
2075
2005
  }
2076
2006
 
2077
- /**
2078
- * Check if a variant is likely to be supported by DrmEngine. This will err on
2079
- * the side of being too accepting and may not reject a variant that it will
2080
- * later fail to play.
2081
- *
2082
- * @param {!shaka.extern.Variant} variant
2083
- * @return {boolean}
2084
- */
2085
- supportsVariant(variant) {
2086
- /** @type {?shaka.extern.Stream} */
2087
- const audio = variant.audio;
2088
- /** @type {?shaka.extern.Stream} */
2089
- const video = variant.video;
2090
-
2091
- if (audio && audio.encrypted) {
2092
- const audioContentType = shaka.media.DrmEngine.computeMimeType_(audio);
2093
- if (!this.willSupport(audioContentType)) {
2094
- return false;
2095
- }
2096
- }
2097
-
2098
- if (video && video.encrypted) {
2099
- const videoContentType = shaka.media.DrmEngine.computeMimeType_(video);
2100
- if (!this.willSupport(videoContentType)) {
2101
- return false;
2102
- }
2103
- }
2104
-
2105
- const keySystem = shaka.util.DrmUtils.keySystem(this.currentDrmInfo_);
2106
- const drmInfos = this.getVariantDrmInfos_(variant);
2107
-
2108
- return drmInfos.length == 0 ||
2109
- drmInfos.some((drmInfo) => drmInfo.keySystem == keySystem);
2110
- }
2111
-
2112
2007
  /**
2113
2008
  * Concat the audio and video drmInfos in a variant.
2114
2009
  * @param {shaka.extern.Variant} variant
@@ -149,8 +149,9 @@ shaka.media.MediaSourceEngine = class {
149
149
  /** @private {?number} */
150
150
  this.lastDuration_ = null;
151
151
 
152
- /** @private {?shaka.util.TsParser} */
153
- this.tsParser_ = null;
152
+ /** @private {!Object.<shaka.util.ManifestParserUtils.ContentType,
153
+ !shaka.util.TsParser>} */
154
+ this.tsParsers_ = {};
154
155
 
155
156
  /** @private {?number} */
156
157
  this.firstVideoTimestamp_ = null;
@@ -178,6 +179,8 @@ shaka.media.MediaSourceEngine = class {
178
179
  * @return {!MediaSource}
179
180
  */
180
181
  createMediaSource(p) {
182
+ this.streamingAllowed_ = true;
183
+
181
184
  /** @type {!MediaSource} */
182
185
  let mediaSource;
183
186
 
@@ -188,11 +191,13 @@ shaka.media.MediaSourceEngine = class {
188
191
 
189
192
  this.eventManager_.listen(
190
193
  mediaSource, 'startstreaming', () => {
194
+ shaka.log.info('MMS startstreaming');
191
195
  this.streamingAllowed_ = true;
192
196
  });
193
197
 
194
198
  this.eventManager_.listen(
195
199
  mediaSource, 'endstreaming', () => {
200
+ shaka.log.info('MMS endstreaming');
196
201
  this.streamingAllowed_ = false;
197
202
  });
198
203
  } else {
@@ -437,7 +442,7 @@ shaka.media.MediaSourceEngine = class {
437
442
  // This object is owned by Player
438
443
  this.lcevcDec_ = null;
439
444
 
440
- this.tsParser_ = null;
445
+ this.tsParsers_ = {};
441
446
  this.playerInterface_ = null;
442
447
  }
443
448
 
@@ -489,7 +494,12 @@ shaka.media.MediaSourceEngine = class {
489
494
  this.manifestType_ == shaka.media.ManifestParser.HLS &&
490
495
  !this.ignoreManifestTimestampsInSegmentsMode_;
491
496
 
492
- this.tsParser_ = null;
497
+ this.tsParsers_ = {};
498
+ this.firstVideoTimestamp_ = null;
499
+ this.firstVideoReferenceStartTime_ = null;
500
+ this.firstAudioTimestamp_ = null;
501
+ this.firstAudioReferenceStartTime_ = null;
502
+ this.audioCompensation_ = new shaka.util.PublicPromise();
493
503
 
494
504
  for (const contentType of streamsByType.keys()) {
495
505
  const stream = streamsByType.get(contentType);
@@ -649,8 +659,7 @@ shaka.media.MediaSourceEngine = class {
649
659
  * @return {?number} The timestamp in seconds, or null if nothing is buffered.
650
660
  */
651
661
  bufferStart(contentType) {
652
- if (this.reloadingMediaSource_ ||
653
- !Object.keys(this.sourceBuffers_).length) {
662
+ if (!Object.keys(this.sourceBuffers_).length) {
654
663
  return null;
655
664
  }
656
665
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
@@ -668,7 +677,7 @@ shaka.media.MediaSourceEngine = class {
668
677
  * @return {?number} The timestamp in seconds, or null if nothing is buffered.
669
678
  */
670
679
  bufferEnd(contentType) {
671
- if (this.reloadingMediaSource_) {
680
+ if (!Object.keys(this.sourceBuffers_).length) {
672
681
  return null;
673
682
  }
674
683
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
@@ -688,9 +697,6 @@ shaka.media.MediaSourceEngine = class {
688
697
  * @return {boolean}
689
698
  */
690
699
  isBuffered(contentType, time) {
691
- if (this.reloadingMediaSource_) {
692
- return false;
693
- }
694
700
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
695
701
  if (contentType == ContentType.TEXT) {
696
702
  return this.textEngine_.isBuffered(time);
@@ -709,9 +715,6 @@ shaka.media.MediaSourceEngine = class {
709
715
  * @return {number} The amount of time buffered ahead in seconds.
710
716
  */
711
717
  bufferedAheadOf(contentType, time) {
712
- if (this.reloadingMediaSource_) {
713
- return 0;
714
- }
715
718
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
716
719
  if (contentType == ContentType.TEXT) {
717
720
  return this.textEngine_.bufferedAheadOf(time);
@@ -732,9 +735,9 @@ shaka.media.MediaSourceEngine = class {
732
735
  const info = {
733
736
  total: this.reloadingMediaSource_ ? [] :
734
737
  TimeRangesUtils.getBufferedInfo(this.video_.buffered),
735
- audio: this.reloadingMediaSource_ ? [] :
738
+ audio:
736
739
  TimeRangesUtils.getBufferedInfo(this.getBuffered_(ContentType.AUDIO)),
737
- video: this.reloadingMediaSource_ ? [] :
740
+ video:
738
741
  TimeRangesUtils.getBufferedInfo(this.getBuffered_(ContentType.VIDEO)),
739
742
  text: [],
740
743
  };
@@ -757,6 +760,9 @@ shaka.media.MediaSourceEngine = class {
757
760
  * @private
758
761
  */
759
762
  getBuffered_(contentType) {
763
+ if (this.reloadingMediaSource_) {
764
+ return null;
765
+ }
760
766
  try {
761
767
  return this.sourceBuffers_[contentType].buffered;
762
768
  } catch (exception) {
@@ -787,13 +793,12 @@ shaka.media.MediaSourceEngine = class {
787
793
  * @param {?shaka.media.SegmentReference} reference The segment reference
788
794
  * we are appending, or null for init segments
789
795
  * @param {!string} mimeType
790
- * @param {!number} timestampOffset
791
- * @return {?number}
796
+ * @return {{timestamp: ?number, metadata: !Array.<shaka.extern.ID3Metadata>}}
792
797
  * @private
793
798
  */
794
- getTimestampAndDispatchMetadata_(contentType, data, reference, mimeType,
795
- timestampOffset) {
799
+ getTimestampAndDispatchMetadata_(contentType, data, reference, mimeType) {
796
800
  let timestamp = null;
801
+ let metadata = [];
797
802
 
798
803
  const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
799
804
  if (shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType)) {
@@ -807,7 +812,7 @@ shaka.media.MediaSourceEngine = class {
807
812
  timestamp = Math.round(metadataTimestamp.data) / 1000;
808
813
  }
809
814
  /** @private {shaka.extern.ID3Metadata} */
810
- const metadata = {
815
+ const id3Metadata = {
811
816
  cueTime: reference.startTime,
812
817
  data: uint8ArrayData,
813
818
  frames: frames,
@@ -815,7 +820,7 @@ shaka.media.MediaSourceEngine = class {
815
820
  pts: reference.startTime,
816
821
  };
817
822
  this.playerInterface_.onMetadata(
818
- [metadata], /* offset= */ 0, reference.endTime);
823
+ [id3Metadata], /* offset= */ 0, reference.endTime);
819
824
  }
820
825
  } else if (mimeType.includes('/mp4') &&
821
826
  reference && reference.timestampOffset == 0 &&
@@ -845,23 +850,19 @@ shaka.media.MediaSourceEngine = class {
845
850
  }
846
851
  } else if (!mimeType.includes('/mp4') && !mimeType.includes('/webm') &&
847
852
  shaka.util.TsParser.probe(uint8ArrayData)) {
848
- if (!this.tsParser_) {
849
- this.tsParser_ = new shaka.util.TsParser();
853
+ if (!this.tsParsers_[contentType]) {
854
+ this.tsParsers_[contentType] = new shaka.util.TsParser();
850
855
  } else {
851
- this.tsParser_.clearData();
856
+ this.tsParsers_[contentType].clearData();
852
857
  }
853
- const tsParser = this.tsParser_.parse(uint8ArrayData);
858
+ const tsParser = this.tsParsers_[contentType].parse(uint8ArrayData);
854
859
  const startTime = tsParser.getStartTime(contentType);
855
860
  if (startTime != null) {
856
861
  timestamp = startTime;
857
862
  }
858
- const metadata = tsParser.getMetadata();
859
- if (metadata.length) {
860
- this.playerInterface_.onMetadata(metadata, timestampOffset,
861
- reference ? reference.endTime : null);
862
- }
863
+ metadata = tsParser.getMetadata();
863
864
  }
864
- return timestamp;
865
+ return {timestamp, metadata};
865
866
  }
866
867
 
867
868
  /**
@@ -890,7 +891,8 @@ shaka.media.MediaSourceEngine = class {
890
891
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
891
892
 
892
893
  if (contentType == ContentType.TEXT) {
893
- if (this.sequenceMode_) {
894
+ if (this.manifestType_ == shaka.media.ManifestParser.HLS &&
895
+ reference.discontinuitySequence >= 0) {
894
896
  // This won't be known until the first video segment is appended.
895
897
  const offset = await this.textSequenceModeOffset_;
896
898
  this.textEngine_.setTimestampOffset(offset);
@@ -925,8 +927,8 @@ shaka.media.MediaSourceEngine = class {
925
927
  mimeType = this.transmuxers_[contentType].getOriginalMimeType();
926
928
  }
927
929
  if (reference) {
928
- const timestamp = this.getTimestampAndDispatchMetadata_(
929
- contentType, data, reference, mimeType, timestampOffset);
930
+ const {timestamp, metadata} = this.getTimestampAndDispatchMetadata_(
931
+ contentType, data, reference, mimeType);
930
932
  if (timestamp != null) {
931
933
  if (this.firstVideoTimestamp_ == null &&
932
934
  contentType == ContentType.VIDEO) {
@@ -984,10 +986,14 @@ shaka.media.MediaSourceEngine = class {
984
986
  const isBestSourceBufferForTimestamps =
985
987
  contentType == ContentType.VIDEO ||
986
988
  !(ContentType.VIDEO in this.sourceBuffers_);
987
- if (this.sequenceMode_ && isBestSourceBufferForTimestamps) {
989
+ if (isBestSourceBufferForTimestamps) {
988
990
  this.textSequenceModeOffset_.resolve(timestampOffset);
989
991
  }
990
992
  }
993
+ if (metadata.length) {
994
+ this.playerInterface_.onMetadata(metadata, timestampOffset,
995
+ reference ? reference.endTime : null);
996
+ }
991
997
  }
992
998
  if (hasClosedCaptions && contentType == ContentType.VIDEO) {
993
999
  if (!this.textEngine_) {
@@ -1309,6 +1315,10 @@ shaka.media.MediaSourceEngine = class {
1309
1315
  this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
1310
1316
  }
1311
1317
 
1318
+ if (!this.sequenceMode_) {
1319
+ return;
1320
+ }
1321
+
1312
1322
  // Queue an abort() to help MSE splice together overlapping segments.
1313
1323
  // We set appendWindowEnd when we change periods in DASH content, and the
1314
1324
  // period transition may result in overlap.
@@ -1847,6 +1857,48 @@ shaka.media.MediaSourceEngine = class {
1847
1857
  null);
1848
1858
  }
1849
1859
 
1860
+ /**
1861
+ * Returns the source buffer parameters
1862
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1863
+ * @return {?shaka.media.MediaSourceEngine.SourceBufferParams}
1864
+ * @private
1865
+ */
1866
+ getSourceBufferParams_(contentType) {
1867
+ if (!this.sourceBuffers_[contentType]) {
1868
+ return null;
1869
+ }
1870
+ return {
1871
+ timestampOffset: this.sourceBuffers_[contentType].timestampOffset,
1872
+ appendWindowStart: this.sourceBuffers_[contentType].appendWindowStart,
1873
+ appendWindowEnd: this.sourceBuffers_[contentType].appendWindowEnd,
1874
+ };
1875
+ }
1876
+
1877
+ /**
1878
+ * Restore source buffer parameters
1879
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1880
+ * @param {?shaka.media.MediaSourceEngine.SourceBufferParams} params
1881
+ * @private
1882
+ */
1883
+ restoreSourceBufferParams_(contentType, params) {
1884
+ if (!params) {
1885
+ return;
1886
+ }
1887
+
1888
+ if (!this.sourceBuffers_[contentType]) {
1889
+ shaka.log.warning('Attempted to restore a non-existent source buffer');
1890
+ return;
1891
+ }
1892
+
1893
+ this.sourceBuffers_[contentType].timestampOffset =
1894
+ params.timestampOffset;
1895
+ // `end` needs to be set before `start`
1896
+ this.sourceBuffers_[contentType].appendWindowEnd =
1897
+ params.appendWindowEnd;
1898
+ this.sourceBuffers_[contentType].appendWindowStart =
1899
+ params.appendWindowStart;
1900
+ }
1901
+
1850
1902
  /**
1851
1903
  * Resets the MediaSource and re-adds source buffers due to codec mismatch
1852
1904
  *
@@ -1907,7 +1959,6 @@ shaka.media.MediaSourceEngine = class {
1907
1959
  }
1908
1960
  await Promise.all(cleanup);
1909
1961
  this.transmuxers_ = {};
1910
- this.sourceBuffers_ = {};
1911
1962
 
1912
1963
  const previousDuration = this.mediaSource_.duration;
1913
1964
  this.mediaSourceOpen_ = new shaka.util.PublicPromise();
@@ -1938,6 +1989,7 @@ shaka.media.MediaSourceEngine = class {
1938
1989
  onSourceBufferAdded);
1939
1990
 
1940
1991
  for (const contentType of streamsByType.keys()) {
1992
+ const previousParams = this.getSourceBufferParams_(contentType);
1941
1993
  const stream = streamsByType.get(contentType);
1942
1994
  // eslint-disable-next-line no-await-in-loop
1943
1995
  await this.initSourceBuffer_(contentType, stream, stream.codecs);
@@ -1947,6 +1999,8 @@ shaka.media.MediaSourceEngine = class {
1947
1999
  } else {
1948
2000
  this.queues_[contentType] = [];
1949
2001
  }
2002
+
2003
+ this.restoreSourceBufferParams_(contentType, previousParams);
1950
2004
  }
1951
2005
 
1952
2006
  // Fake a seek to catchup the playhead.
@@ -2234,3 +2288,12 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
2234
2288
  * Callback to use when metadata arrives.
2235
2289
  */
2236
2290
  shaka.media.MediaSourceEngine.PlayerInterface;
2291
+
2292
+ /**
2293
+ * @typedef {{
2294
+ * timestampOffset: number,
2295
+ * appendWindowStart: number,
2296
+ * appendWindowEnd: number
2297
+ * }}
2298
+ */
2299
+ shaka.media.MediaSourceEngine.SourceBufferParams;