shaka-player 4.13.1 → 4.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -92,7 +92,10 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
92
92
  /** @private {boolean} */
93
93
  this.parserEntrusted_ = false;
94
94
 
95
- /** @private {!shaka.media.RegionTimeline} */
95
+ /**
96
+ * @private {!shaka.media.RegionTimeline<
97
+ * shaka.extern.TimelineRegionInfo>}
98
+ */
96
99
  this.regionTimeline_ = typedPlayerInterface.regionTimeline;
97
100
 
98
101
  /** @private {boolean} */
@@ -288,7 +291,7 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
288
291
  /**
289
292
  * Gets the region timeline, if it exists. Also marks that the timeline should
290
293
  * not be released if this manager is destroyed.
291
- * @return {?shaka.media.RegionTimeline}
294
+ * @return {?shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>}
292
295
  */
293
296
  receiveRegionTimeline() {
294
297
  this.regionTimelineEntrusted_ = true;
@@ -296,7 +299,7 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
296
299
  }
297
300
 
298
301
  /**
299
- * @return {?shaka.media.RegionTimeline}
302
+ * @return {?shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>}
300
303
  */
301
304
  getRegionTimeline() {
302
305
  return this.regionTimeline_;
@@ -837,7 +840,8 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
837
840
  * @typedef {{
838
841
  * config: !shaka.extern.PlayerConfiguration,
839
842
  * manifestPlayerInterface: !shaka.extern.ManifestParser.PlayerInterface,
840
- * regionTimeline: !shaka.media.RegionTimeline,
843
+ * regionTimeline: !shaka.media.RegionTimeline<
844
+ * shaka.extern.TimelineRegionInfo>,
841
845
  * qualityObserver: ?shaka.media.QualityObserver,
842
846
  * createDrmEngine: function():!shaka.drm.DrmEngine,
843
847
  * networkingEngine: !shaka.net.NetworkingEngine,
@@ -849,7 +853,8 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
849
853
  * @property {!shaka.extern.PlayerConfiguration} config
850
854
  * @property {!shaka.extern.ManifestParser.PlayerInterface
851
855
  * } manifestPlayerInterface
852
- * @property {!shaka.media.RegionTimeline} regionTimeline
856
+ * @property {!shaka.media.RegionTimeline<shaka.extern.TimelineRegionInfo>
857
+ * } regionTimeline
853
858
  * @property {?shaka.media.QualityObserver} qualityObserver
854
859
  * @property {function():!shaka.drm.DrmEngine} createDrmEngine
855
860
  * @property {!shaka.net.NetworkingEngine} networkingEngine
@@ -18,6 +18,7 @@ goog.require('shaka.util.FakeEventTarget');
18
18
  * ('enter', 'exit', 'skip') as the playhead moves.
19
19
  *
20
20
  * @implements {shaka.media.IPlayheadObserver}
21
+ * @template T
21
22
  * @final
22
23
  */
23
24
  shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
@@ -26,13 +27,13 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
26
27
  * own the timeline, only uses it. This means that the observer should NOT
27
28
  * destroy the timeline.
28
29
  *
29
- * @param {!shaka.media.RegionTimeline} timeline
30
+ * @param {!shaka.media.RegionTimeline<T>} timeline
30
31
  * @param {boolean} startsPastZero
31
32
  */
32
33
  constructor(timeline, startsPastZero) {
33
34
  super();
34
35
 
35
- /** @private {shaka.media.RegionTimeline} */
36
+ /** @private {shaka.media.RegionTimeline<T>} */
36
37
  this.timeline_ = timeline;
37
38
 
38
39
  /**
@@ -50,8 +51,7 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
50
51
  * When the value here differs from what we calculate, it means we moved and
51
52
  * should fire an event.
52
53
  *
53
- * @private {!Map<shaka.extern.TimelineRegionInfo,
54
- * shaka.media.RegionObserver.RelativePosition_>}
54
+ * @private {!Map<T, shaka.media.RegionObserver.RelativePosition_>}
55
55
  */
56
56
  this.oldPosition_ = new Map();
57
57
 
@@ -109,7 +109,7 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
109
109
  this.eventManager_ = new shaka.util.EventManager();
110
110
 
111
111
  this.eventManager_.listen(this.timeline_, 'regionremove', (event) => {
112
- /** @type {shaka.extern.TimelineRegionInfo} */
112
+ /** @type {T} */
113
113
  const region = event['region'];
114
114
  this.oldPosition_.delete(region);
115
115
  });
@@ -162,7 +162,7 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
162
162
  * same parameters: region and seeking.
163
163
  *
164
164
  * @param {string} eventType
165
- * @param {shaka.extern.TimelineRegionInfo} region
165
+ * @param {T} region
166
166
  * @param {boolean} seeking
167
167
  * @private
168
168
  */
@@ -179,7 +179,8 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
179
179
  * at |seconds|. We treat the region's start and end times as inclusive
180
180
  * bounds.
181
181
  *
182
- * @param {shaka.extern.TimelineRegionInfo} region
182
+ * @template T
183
+ * @param {T} region
183
184
  * @param {number} seconds
184
185
  * @return {shaka.media.RegionObserver.RelativePosition_}
185
186
  * @private
@@ -218,7 +219,7 @@ shaka.media.RegionObserver.RelativePosition_ = {
218
219
  * region that the playhead is interacting with and whether or not the playhead
219
220
  * moving is part of a seek event.
220
221
  *
221
- * @typedef {function(shaka.extern.TimelineRegionInfo, boolean)}
222
+ * @typedef {function(?, boolean)}
222
223
  */
223
224
  shaka.media.RegionObserver.EventListener;
224
225
 
@@ -18,6 +18,7 @@ goog.require('shaka.util.Timer');
18
18
  * deleted, the 'regionremove' event will be fired.
19
19
  *
20
20
  * @implements {shaka.util.IReleasable}
21
+ * @template T
21
22
  * @final
22
23
  */
23
24
  shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
@@ -27,7 +28,7 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
27
28
  constructor(getSeekRange) {
28
29
  super();
29
30
 
30
- /** @private {!Set<shaka.extern.TimelineRegionInfo>} */
31
+ /** @private {!Set<T>} */
31
32
  this.regions_ = new Set();
32
33
 
33
34
  /** @private {!function():{start: number, end: number}} */
@@ -54,7 +55,7 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
54
55
  }
55
56
 
56
57
  /**
57
- * @param {shaka.extern.TimelineRegionInfo} region
58
+ * @param {T} region
58
59
  */
59
60
  addRegion(region) {
60
61
  const similarRegion = this.findSimilarRegion_(region);
@@ -97,18 +98,19 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
97
98
  * to be the same region. If no similar region can be found, |null| will be
98
99
  * returned.
99
100
  *
100
- * @param {shaka.extern.TimelineRegionInfo} region
101
- * @return {?shaka.extern.TimelineRegionInfo}
101
+ * @param {T} region
102
+ * @return {?T}
102
103
  * @private
103
104
  */
104
105
  findSimilarRegion_(region) {
106
+ const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.1;
105
107
  for (const existing of this.regions_) {
106
108
  // The same scheme ID and time range means that it is similar-enough to
107
109
  // be the same region.
108
110
  const isSimilar = existing.schemeIdUri == region.schemeIdUri &&
109
- existing.id == region.id &&
110
- existing.startTime == region.startTime &&
111
- existing.endTime == region.endTime;
111
+ existing.id == region.id &&
112
+ isDiffNegligible(existing.startTime, region.startTime) &&
113
+ isDiffNegligible(existing.endTime, region.endTime);
112
114
 
113
115
  if (isSimilar) {
114
116
  return existing;
@@ -123,7 +125,7 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
123
125
  * others to see what regions are in the timeline while not being able to
124
126
  * change the collection.
125
127
  *
126
- * @return {!Iterable<shaka.extern.TimelineRegionInfo>}
128
+ * @return {!Iterable<T>}
127
129
  */
128
130
  regions() {
129
131
  return this.regions_;
@@ -1368,12 +1368,17 @@ shaka.media.StreamingEngine = class {
1368
1368
  return null;
1369
1369
  }
1370
1370
 
1371
+ // Update updateIntervalSeconds according to our current playbackrate,
1372
+ // and always considering a minimum of 1.
1373
+ const updateIntervalSeconds = this.config_.updateIntervalSeconds /
1374
+ Math.max(1, Math.abs(this.playerInterface_.getPlaybackRate()));
1375
+
1371
1376
  if (!this.playerInterface_.mediaSourceEngine.isStreamingAllowed() &&
1372
1377
  mediaState.type != ContentType.TEXT) {
1373
1378
  // It is not allowed to add segments yet, so we schedule an update to
1374
1379
  // check again later. So any prediction we make now could be terribly
1375
1380
  // invalid soon.
1376
- return this.config_.updateIntervalSeconds / 2;
1381
+ return updateIntervalSeconds / 2;
1377
1382
  }
1378
1383
 
1379
1384
  const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
@@ -1445,9 +1450,8 @@ shaka.media.StreamingEngine = class {
1445
1450
  shaka.log.v2(logPrefix, 'buffering goal met');
1446
1451
 
1447
1452
  // Do not try to predict the next update. Just poll according to
1448
- // configuration (seconds). The playback rate can change at any time, so
1449
- // any prediction we make now could be terribly invalid soon.
1450
- return this.config_.updateIntervalSeconds / 2;
1453
+ // configuration (seconds).
1454
+ return updateIntervalSeconds / 2;
1451
1455
  }
1452
1456
 
1453
1457
  // Lack of segment iterator is the best indicator stream has changed.
@@ -1459,7 +1463,7 @@ shaka.media.StreamingEngine = class {
1459
1463
  // In any case just try again... if the manifest is incomplete or is not
1460
1464
  // being updated then we'll idle forever; otherwise, we'll end up getting
1461
1465
  // a SegmentReference eventually.
1462
- return this.config_.updateIntervalSeconds;
1466
+ return updateIntervalSeconds;
1463
1467
  }
1464
1468
  // Get media state adaptation and reset this value. By guarding it during
1465
1469
  // actual stream change we ensure it won't be cleaned by accident on regular
@@ -1498,7 +1502,7 @@ shaka.media.StreamingEngine = class {
1498
1502
  // For example, let video buffering catch up to audio buffering before
1499
1503
  // fetching another audio segment.
1500
1504
  shaka.log.v2(logPrefix, 'waiting for other streams to buffer');
1501
- return this.config_.updateIntervalSeconds;
1505
+ return updateIntervalSeconds;
1502
1506
  }
1503
1507
 
1504
1508
  if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
@@ -1821,7 +1825,19 @@ shaka.media.StreamingEngine = class {
1821
1825
  JSON.stringify(buffered));
1822
1826
 
1823
1827
  if (!mediaState.waitingToClearBuffer) {
1824
- this.playerInterface_.onSegmentAppended(reference, mediaState.stream);
1828
+ let otherState = null;
1829
+ if (mediaState.type === ContentType.VIDEO) {
1830
+ otherState = this.mediaStates_.get(ContentType.AUDIO);
1831
+ } else if (mediaState.type === ContentType.AUDIO) {
1832
+ otherState = this.mediaStates_.get(ContentType.VIDEO);
1833
+ }
1834
+ if (otherState && otherState.type == ContentType.AUDIO) {
1835
+ this.playerInterface_.onSegmentAppended(reference, mediaState.stream,
1836
+ otherState.stream.isAudioMuxedInVideo);
1837
+ } else {
1838
+ this.playerInterface_.onSegmentAppended(reference, mediaState.stream,
1839
+ mediaState.stream.codecs.includes(','));
1840
+ }
1825
1841
  }
1826
1842
 
1827
1843
  // Update right away.
@@ -2885,7 +2901,7 @@ shaka.media.StreamingEngine = class {
2885
2901
  * onError: function(!shaka.util.Error),
2886
2902
  * onEvent: function(!Event),
2887
2903
  * onSegmentAppended: function(!shaka.media.SegmentReference,
2888
- * !shaka.extern.Stream),
2904
+ * !shaka.extern.Stream, boolean),
2889
2905
  * onInitSegmentAppended: function(!number,!shaka.media.InitSegmentReference),
2890
2906
  * beforeAppendSegment: function(
2891
2907
  * shaka.util.ManifestParserUtils.ContentType,!BufferSource):Promise,
@@ -2910,7 +2926,7 @@ shaka.media.StreamingEngine = class {
2910
2926
  * @property {function(!Event)} onEvent
2911
2927
  * Called when an event occurs that should be sent to the app.
2912
2928
  * @property {function(!shaka.media.SegmentReference,
2913
- * !shaka.extern.Stream)} onSegmentAppended
2929
+ * !shaka.extern.Stream, boolean)} onSegmentAppended
2914
2930
  * Called after a segment is successfully appended to a MediaSource.
2915
2931
  * @property {function(!number,
2916
2932
  * !shaka.media.InitSegmentReference)} onInitSegmentAppended
package/lib/player.js CHANGED
@@ -54,6 +54,7 @@ goog.require('shaka.util.Functional');
54
54
  goog.require('shaka.util.IDestroyable');
55
55
  goog.require('shaka.util.LanguageUtils');
56
56
  goog.require('shaka.util.ManifestParserUtils');
57
+ goog.require('shaka.util.MapUtils');
57
58
  goog.require('shaka.util.MediaReadyState');
58
59
  goog.require('shaka.util.MimeUtils');
59
60
  goog.require('shaka.util.Mutex');
@@ -698,7 +699,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
698
699
  /** @private {shaka.media.BufferingObserver} */
699
700
  this.bufferObserver_ = null;
700
701
 
701
- /** @private {shaka.media.RegionTimeline} */
702
+ /**
703
+ * @private {shaka.media.RegionTimeline<
704
+ * shaka.extern.TimelineRegionInfo>}
705
+ */
702
706
  this.regionTimeline_ = null;
703
707
 
704
708
  /** @private {shaka.util.CmcdManager} */
@@ -3591,6 +3595,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3591
3595
 
3592
3596
  // Create the region observer. This will allow us to notify the app when we
3593
3597
  // move in and out of timeline regions.
3598
+ /** @type {!shaka.media.RegionObserver<shaka.extern.TimelineRegionInfo>} */
3594
3599
  const regionObserver = new shaka.media.RegionObserver(
3595
3600
  this.regionTimeline_, startsPastZero);
3596
3601
 
@@ -3808,10 +3813,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3808
3813
  netEngine: this.networkingEngine_,
3809
3814
  onError: (error) => this.onError_(error),
3810
3815
  onEvent: (event) => this.dispatchEvent(event),
3811
- onSegmentAppended: (reference, stream) => {
3816
+ onSegmentAppended: (reference, stream, isMuxed) => {
3812
3817
  this.onSegmentAppended_(
3813
- reference.startTime, reference.endTime, stream.type,
3814
- stream.codecs.includes(','));
3818
+ reference.startTime, reference.endTime, stream.type, isMuxed);
3815
3819
  },
3816
3820
  onInitSegmentAppended: (position, initSegment) => {
3817
3821
  const mediaQuality = initSegment.getMediaQuality();
@@ -4723,7 +4727,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4723
4727
  shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
4724
4728
  return;
4725
4729
  }
4726
- this.trickPlayEventManager_.removeAll();
4727
4730
 
4728
4731
  this.playRateController_.set(rate);
4729
4732
 
@@ -4732,26 +4735,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4732
4735
  this.streamingEngine_.setTrickPlay(
4733
4736
  useTrickPlayTrack && Math.abs(rate) > 1);
4734
4737
  }
4735
- if (this.isLive()) {
4736
- this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
4737
- const currentTime = this.video_.currentTime;
4738
- const seekRange = this.seekRange();
4739
- const safeSeekOffset = this.config_.streaming.safeSeekOffset;
4740
-
4741
- // Cancel trick play if we hit the beginning or end of the seekable
4742
- // (Sub-second accuracy not required here)
4743
- if (rate > 0) {
4744
- if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
4745
- this.cancelTrickPlay();
4746
- }
4747
- } else {
4748
- if (Math.floor(currentTime) <=
4749
- Math.floor(seekRange.start + safeSeekOffset)) {
4750
- this.cancelTrickPlay();
4751
- }
4752
- }
4753
- });
4754
- }
4738
+ this.setupTrickPlayEventListeners_(rate);
4755
4739
  }
4756
4740
 
4757
4741
  /**
@@ -5825,12 +5809,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5825
5809
  * is playing.
5826
5810
  */
5827
5811
  getLiveLatency() {
5828
- if (!this.video_) {
5812
+ if (!this.video_ || !this.video_.currentTime) {
5829
5813
  return null;
5830
5814
  }
5831
5815
  const now = this.getPresentationStartTimeAsDate().getTime() +
5832
5816
  this.video_.currentTime * 1000;
5833
- return Date.now() - now;
5817
+
5818
+ return Math.floor(Date.now() - now);
5834
5819
  }
5835
5820
 
5836
5821
  /**
@@ -6867,6 +6852,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6867
6852
  // The playback rate has changed. This could be us or someone else.
6868
6853
  // If this was us, setting the rate again will be a no-op.
6869
6854
  this.playRateController_.set(newRate);
6855
+
6856
+ if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
6857
+ this.abrManager_.playbackRateChanged(newRate);
6858
+ }
6859
+ this.setupTrickPlayEventListeners_(newRate);
6870
6860
  }
6871
6861
 
6872
6862
  const event = shaka.Player.makeEvent_(
@@ -6874,6 +6864,36 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6874
6864
  this.dispatchEvent(event);
6875
6865
  }
6876
6866
 
6867
+ /**
6868
+ * Configures all the necessary listeners when trick play is being performed.
6869
+ *
6870
+ * @param {number} rate
6871
+ * @private
6872
+ */
6873
+ setupTrickPlayEventListeners_(rate) {
6874
+ this.trickPlayEventManager_.removeAll();
6875
+ if (this.isLive()) {
6876
+ this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
6877
+ const currentTime = this.video_.currentTime;
6878
+ const seekRange = this.seekRange();
6879
+ const safeSeekOffset = this.config_.streaming.safeSeekOffset;
6880
+
6881
+ // Cancel trick play if we hit the beginning or end of the seekable
6882
+ // (Sub-second accuracy not required here)
6883
+ if (rate > 0) {
6884
+ if (Math.floor(currentTime) >= Math.floor(seekRange.end)) {
6885
+ this.cancelTrickPlay();
6886
+ }
6887
+ } else {
6888
+ if (Math.floor(currentTime) <=
6889
+ Math.floor(seekRange.start + safeSeekOffset)) {
6890
+ this.cancelTrickPlay();
6891
+ }
6892
+ }
6893
+ });
6894
+ }
6895
+ }
6896
+
6877
6897
  /**
6878
6898
  * Try updating the state history. If the player has not finished
6879
6899
  * initializing, this will be a no-op.
@@ -8042,6 +8062,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
8042
8062
 
8043
8063
  // Only filter tracks for keys if we have some key statuses to look at.
8044
8064
  if (keyIds.length) {
8065
+ const currentKeySystem = this.keySystem();
8066
+ const clearKeys = shaka.util.MapUtils.asMap(this.config_.drm.clearKeys);
8067
+
8045
8068
  for (const variant of this.manifest_.variants) {
8046
8069
  const streams = shaka.util.StreamUtils.getVariantStreams(variant);
8047
8070
 
@@ -8051,16 +8074,42 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
8051
8074
  // Only update if we have key IDs for the stream. If the keys aren't
8052
8075
  // all present, then the track should be restricted.
8053
8076
  if (stream.keyIds.size) {
8054
- variant.allowedByKeySystem = true;
8055
-
8056
- for (const keyId of stream.keyIds) {
8057
- const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
8058
- if (keyStatus || this.drmEngine_.hasManifestInitData()) {
8059
- variant.allowedByKeySystem = variant.allowedByKeySystem &&
8060
- !!keyStatus && !restrictedStatuses.includes(keyStatus);
8061
- }
8062
- }
8063
- }
8077
+ // If we are not using clearkeys, and the stream has drmInfos we
8078
+ // only want to check the keyIds of the keySystem we are using.
8079
+ // Other keySystems might have other keyIds that might not be
8080
+ // valid in this case. This can happen in HLS if the manifest
8081
+ // has Widevine with keyIds and PlayReady without keyIds and we are
8082
+ // using PlayReady.
8083
+ if (stream.drmInfos.length && !clearKeys.size) {
8084
+ for (const drmInfo of stream.drmInfos) {
8085
+ if (drmInfo.keyIds.size &&
8086
+ drmInfo.keySystem == currentKeySystem) {
8087
+ variant.allowedByKeySystem = true;
8088
+
8089
+ for (const keyId of drmInfo.keyIds) {
8090
+ const keyStatus =
8091
+ keyStatusMap[isGlobalStatus ? '00' : keyId];
8092
+ if (keyStatus || this.drmEngine_.hasManifestInitData()) {
8093
+ variant.allowedByKeySystem =
8094
+ variant.allowedByKeySystem &&
8095
+ !!keyStatus &&
8096
+ !restrictedStatuses.includes(keyStatus);
8097
+ } // if (keyStatus || this.drmEngine_.hasManifestInitData())
8098
+ } // for (const keyId of drmInfo.keyIds)
8099
+ } // if (drmInfo.keyIds.size && ...
8100
+ } // for (const drmInfo of stream.drmInfos
8101
+ } else {
8102
+ variant.allowedByKeySystem = true;
8103
+
8104
+ for (const keyId of stream.keyIds) {
8105
+ const keyStatus = keyStatusMap[isGlobalStatus ? '00' : keyId];
8106
+ if (keyStatus || this.drmEngine_.hasManifestInitData()) {
8107
+ variant.allowedByKeySystem = variant.allowedByKeySystem &&
8108
+ !!keyStatus && !restrictedStatuses.includes(keyStatus);
8109
+ }
8110
+ } // for (const keyId of stream.keyIds)
8111
+ } // if (stream.drmInfos.length && !clearKeys.size)
8112
+ } // if (stream.keyIds.size)
8064
8113
 
8065
8114
  if (originalAllowed != variant.allowedByKeySystem) {
8066
8115
  tracksChanged = true;
@@ -8386,7 +8435,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
8386
8435
  * @export
8387
8436
  */
8388
8437
  // eslint-disable-next-line no-useless-concat
8389
- shaka.Player.version = 'v4.13.1' + '-uncompiled'; // x-release-please-version
8438
+ shaka.Player.version = 'v4.13.2' + '-uncompiled'; // x-release-please-version
8390
8439
 
8391
8440
  // Initialize the deprecation system using the version string we just set
8392
8441
  // on the player.
package/lib/text/cue.js CHANGED
@@ -371,8 +371,12 @@ shaka.text.Cue = class {
371
371
  // performance optimization. We can avoid the more expensive recursive
372
372
  // checks if the top-level properties don't match.
373
373
  // See: https://github.com/shaka-project/shaka-player/issues/3018
374
- if (cue1.startTime != cue2.startTime || cue1.endTime != cue2.endTime ||
375
- cue1.payload != cue2.payload) {
374
+ if (cue1.payload != cue2.payload) {
375
+ return false;
376
+ }
377
+ const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.001;
378
+ if (!isDiffNegligible(cue1.startTime, cue2.startTime) ||
379
+ !isDiffNegligible(cue1.endTime, cue2.endTime)) {
376
380
  return false;
377
381
  }
378
382
  for (const k in cue1) {
@@ -55,6 +55,9 @@ shaka.text.UITextDisplayer = class {
55
55
  /** @private {?number} */
56
56
  this.aspectRatio_ = null;
57
57
 
58
+ /** @private {?shaka.extern.TextDisplayerConfiguration} */
59
+ this.config_ = null;
60
+
58
61
  /** @type {HTMLElement} */
59
62
  this.textContainer_ = shaka.util.Dom.createHTMLElement('div');
60
63
  this.textContainer_.classList.add('shaka-text-container');
@@ -77,7 +80,8 @@ shaka.text.UITextDisplayer = class {
77
80
  if (!this.video_.paused) {
78
81
  this.updateCaptions_();
79
82
  }
80
- }).tickEvery(/* seconds= */ 0.25);
83
+ });
84
+ this.configureCaptionsTimer_();
81
85
 
82
86
  /**
83
87
  * Maps cues to cue elements. Specifically points out the wrapper element of
@@ -101,6 +105,10 @@ shaka.text.UITextDisplayer = class {
101
105
  this.updateCaptions_(/* forceUpdate= */ true);
102
106
  });
103
107
 
108
+ this.eventManager_.listen(this.video_, 'ratechange', () => {
109
+ this.configureCaptionsTimer_();
110
+ });
111
+
104
112
  // From: https://html.spec.whatwg.org/multipage/media.html#dom-video-videowidth
105
113
  // Whenever the natural width or natural height of the video changes
106
114
  // (including, for example, because the selected video track was changed),
@@ -137,9 +145,8 @@ shaka.text.UITextDisplayer = class {
137
145
  * @export
138
146
  */
139
147
  configure(config) {
140
- if (this.captionsTimer_) {
141
- this.captionsTimer_.tickEvery(config.captionsUpdatePeriod);
142
- }
148
+ this.config_ = config;
149
+ this.configureCaptionsTimer_();
143
150
  }
144
151
 
145
152
 
@@ -186,6 +193,7 @@ shaka.text.UITextDisplayer = class {
186
193
  this.cues_ = [];
187
194
  if (this.captionsTimer_) {
188
195
  this.captionsTimer_.stop();
196
+ this.captionsTimer_ = null;
189
197
  }
190
198
 
191
199
  this.currentCuesMap_.clear();
@@ -265,6 +273,19 @@ shaka.text.UITextDisplayer = class {
265
273
  enableTextDisplayer() {
266
274
  }
267
275
 
276
+ /**
277
+ * @private
278
+ */
279
+ configureCaptionsTimer_() {
280
+ if (this.captionsTimer_) {
281
+ const captionsUpdatePeriod = this.config_ ?
282
+ this.config_.captionsUpdatePeriod : 0.25;
283
+ const updateTime = captionsUpdatePeriod /
284
+ Math.max(1, Math.abs(this.video_.playbackRate));
285
+ this.captionsTimer_.tickEvery(updateTime);
286
+ }
287
+ }
288
+
268
289
  /**
269
290
  * @private
270
291
  */
@@ -307,13 +307,15 @@ shaka.util.Platform = class {
307
307
  !shaka.util.Platform.isTizen() &&
308
308
  !shaka.util.Platform.isEOS() &&
309
309
  !shaka.util.Platform.isAPL() &&
310
+ !shaka.util.Platform.isAPP() &&
310
311
  !shaka.util.Platform.isVirginMedia() &&
311
312
  !shaka.util.Platform.isOrange() &&
312
313
  !shaka.util.Platform.isPS4() &&
313
314
  !shaka.util.Platform.isAmazonFireTV() &&
314
315
  !shaka.util.Platform.isComcastX1() &&
315
316
  !shaka.util.Platform.isZenterio() &&
316
- !shaka.util.Platform.isSkyQ();
317
+ !shaka.util.Platform.isSkyQ() &&
318
+ !shaka.util.Platform.isMultiChoice();
317
319
  }
318
320
 
319
321
  /**
@@ -396,6 +398,16 @@ shaka.util.Platform = class {
396
398
  return shaka.util.Platform.userAgentContains_('DT_STB_BCM');
397
399
  }
398
400
 
401
+ /**
402
+ * Check if the current platform is a MultiChoice STB.
403
+ * @return {boolean}
404
+ */
405
+ static isMultiChoice() {
406
+ // cspell: ignore MDMP
407
+ return shaka.util.Platform.userAgentContains_('MDMP1001S') ||
408
+ shaka.util.Platform.userAgentContains_('PS5525IMC');
409
+ }
410
+
399
411
  /**
400
412
  * Returns a major version number for Safari, or Safari-based iOS browsers.
401
413
  *
@@ -461,6 +473,14 @@ shaka.util.Platform = class {
461
473
  return shaka.util.Platform.userAgentContains_('PC=APL');
462
474
  }
463
475
 
476
+ /**
477
+ * Check if the current platform is an APPSTB set-top box.
478
+ * @return {boolean}
479
+ */
480
+ static isAPP() {
481
+ return shaka.util.Platform.userAgentContains_('PC=APPSTB');
482
+ }
483
+
464
484
  /**
465
485
  * Guesses if the platform is a mobile one (iOS or Android).
466
486
  *
@@ -568,7 +588,8 @@ shaka.util.Platform = class {
568
588
  Platform.isEOS() || Platform.isAPL() ||
569
589
  Platform.isVirginMedia() || Platform.isOrange() ||
570
590
  Platform.isComcastX1() || Platform.isChromecast() ||
571
- Platform.isHisense() || Platform.isZenterio()) {
591
+ Platform.isHisense() || Platform.isZenterio() ||
592
+ Platform.isAPP() || Platform.isMultiChoice()) {
572
593
  return true;
573
594
  }
574
595
  return false;
@@ -662,7 +683,7 @@ shaka.util.Platform = class {
662
683
  static supportsSequenceMode() {
663
684
  const Platform = shaka.util.Platform;
664
685
  if (Platform.isTizen3() || Platform.isTizen2() ||
665
- Platform.isWebOS3() || Platform.isPS4()) {
686
+ Platform.isWebOS3() || Platform.isPS4() || Platform.isPS5()) {
666
687
  return false;
667
688
  }
668
689
  return true;
@@ -1267,6 +1267,9 @@ shaka.util.StreamUtils = class {
1267
1267
  * @return {shaka.extern.Track}
1268
1268
  */
1269
1269
  static variantToTrack(variant) {
1270
+ const ManifestParserUtils = shaka.util.ManifestParserUtils;
1271
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
1272
+
1270
1273
  /** @type {?shaka.extern.Stream} */
1271
1274
  const audio = variant.audio;
1272
1275
  /** @type {?shaka.extern.Stream} */
@@ -1385,6 +1388,16 @@ shaka.util.StreamUtils = class {
1385
1388
  track.audioSamplingRate = video.audioSamplingRate;
1386
1389
  track.spatialAudio = video.spatialAudio;
1387
1390
  track.originalLanguage = video.originalLanguage;
1391
+ track.audioMimeType = videoMimeType;
1392
+ const allCodecs = videoCodec.split(',');
1393
+ try {
1394
+ track.videoCodec = ManifestParserUtils.guessCodecs(
1395
+ ContentType.VIDEO, allCodecs);
1396
+ track.audioCodec = ManifestParserUtils.guessCodecs(
1397
+ ContentType.AUDIO, allCodecs);
1398
+ } catch (e) {
1399
+ // Ignore this error.
1400
+ }
1388
1401
  }
1389
1402
  }
1390
1403
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.13.1",
4
+ "version": "4.13.2",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
package/project-words.txt CHANGED
@@ -160,6 +160,7 @@ linksource
160
160
  longname
161
161
 
162
162
  # 3rd party
163
+ APPSTB
163
164
  Bitcodin
164
165
  Brightcove
165
166
  Broadpeak