shaka-player 4.10.11 → 4.10.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.
@@ -105,14 +105,16 @@ shaka.cea.Mp4CeaParser = class {
105
105
  // These are the various boxes that signal a codec.
106
106
  .box('avc1', codecBoxParser)
107
107
  .box('avc3', codecBoxParser)
108
- .box('hev1', codecBoxParser)
109
- .box('hvc1', codecBoxParser)
110
108
  .box('dvav', codecBoxParser)
111
109
  .box('dva1', codecBoxParser)
110
+ .box('hev1', codecBoxParser)
111
+ .box('hvc1', codecBoxParser)
112
112
  .box('dvh1', codecBoxParser)
113
113
  .box('dvhe', codecBoxParser)
114
114
  .box('vvc1', codecBoxParser)
115
115
  .box('vvi1', codecBoxParser)
116
+ .box('dvc1', codecBoxParser)
117
+ .box('dvi1', codecBoxParser)
116
118
 
117
119
  // This signals an encrypted sample, which we can go inside of to find
118
120
  // the codec used.
@@ -406,18 +408,21 @@ shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = {
406
408
  // AVC
407
409
  'avc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
408
410
  'avc3': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
409
- // HEVC
410
- 'hev1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
411
- 'hvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
412
411
  // Dolby Vision based in AVC
413
412
  'dvav': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
414
413
  'dva1': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
414
+ // HEVC
415
+ 'hev1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
416
+ 'hvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
415
417
  // Dolby Vision based in HEVC
416
418
  'dvh1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
417
419
  'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
418
420
  // VVC
419
421
  'vvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H266,
420
422
  'vvi1': shaka.cea.Mp4CeaParser.BitstreamFormat.H266,
423
+ // Dolby Vision based in VVC
424
+ 'dvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H266,
425
+ 'dvi1': shaka.cea.Mp4CeaParser.BitstreamFormat.H266,
421
426
  };
422
427
 
423
428
  /**
@@ -11,14 +11,18 @@ goog.provide('shaka.config.CodecSwitchingStrategy');
11
11
  * @export
12
12
  */
13
13
  shaka.config.CodecSwitchingStrategy = {
14
- // Allow codec switching which will always involve reloading the
15
- // `MediaSource`.
16
- RELOAD: 'reload',
17
- // Allow codec switching; determine if `SourceBuffer.changeType` is available
18
- // and attempt to use this first, but fall back to reloading `MediaSource` if
19
- // not available.
20
- //
21
- // Note: Some devices that support `SourceBuffer.changeType` can become stuck
22
- // in a pause state.
23
- SMOOTH: 'smooth',
14
+ /**
15
+ * Allow codec switching which will always involve reloading
16
+ * the <code<MediaSource</code>.
17
+ */
18
+ 'RELOAD': 'reload',
19
+ /**
20
+ * Allow codec switching; determine if <code>SourceBuffer.changeType</code>
21
+ * is available and attempt to use this first, but fall back to reloading
22
+ * <code>MediaSource</code> if not available.
23
+ * <br>
24
+ * Note: Some devices that support <code>SourceBuffer.changeType</code> can
25
+ * become stuck in a pause state.
26
+ */
27
+ 'SMOOTH': 'smooth',
24
28
  };
@@ -30,6 +30,7 @@ goog.require('shaka.util.LanguageUtils');
30
30
  goog.require('shaka.util.ManifestParserUtils');
31
31
  goog.require('shaka.util.MimeUtils');
32
32
  goog.require('shaka.util.Networking');
33
+ goog.require('shaka.util.ObjectUtils');
33
34
  goog.require('shaka.util.OperationManager');
34
35
  goog.require('shaka.util.PeriodCombiner');
35
36
  goog.require('shaka.util.PlayerConfiguration');
@@ -93,12 +94,19 @@ shaka.dash.DashParser = class {
93
94
 
94
95
  /**
95
96
  * A map of IDs to Stream objects.
96
- * ID: Period@id,AdaptationSet@id,@Representation@id
97
- * e.g.: '1,5,23'
97
+ * ID: Period@id,Representation@id
98
+ * e.g.: '1,23'
98
99
  * @private {!Object.<string, !shaka.extern.Stream>}
99
100
  */
100
101
  this.streamMap_ = {};
101
102
 
103
+ /**
104
+ * A map of Period IDs to Stream Map IDs.
105
+ * Use to have direct access to streamMap key.
106
+ * @private {!Object.<string, !Array<string>>}
107
+ */
108
+ this.indexStreamMap_ = {};
109
+
102
110
  /**
103
111
  * A map of period ids to their durations
104
112
  * @private {!Object.<string, number>}
@@ -256,6 +264,7 @@ shaka.dash.DashParser = class {
256
264
  this.manifestUris_ = [];
257
265
  this.manifest_ = null;
258
266
  this.streamMap_ = {};
267
+ this.indexStreamMap_ = {};
259
268
  this.contextCache_.clear();
260
269
  this.manifestPatchContext_ = {
261
270
  mpdId: '',
@@ -653,7 +662,7 @@ shaka.dash.DashParser = class {
653
662
  }
654
663
 
655
664
  this.manifestPatchContext_.type = mpdType;
656
-
665
+ this.cleanStreamMap_();
657
666
  /** @type {!shaka.media.PresentationTimeline} */
658
667
  let presentationTimeline;
659
668
  if (this.manifest_) {
@@ -734,7 +743,8 @@ shaka.dash.DashParser = class {
734
743
  };
735
744
 
736
745
  this.gapCount_ = 0;
737
- const periodsAndDuration = this.parsePeriods_(context, getBaseUris, mpd);
746
+ const periodsAndDuration = this.parsePeriods_(
747
+ context, getBaseUris, mpd, /* newPeriod= */ false);
738
748
  const duration = periodsAndDuration.duration;
739
749
  const periods = periodsAndDuration.periods;
740
750
 
@@ -1013,7 +1023,7 @@ shaka.dash.DashParser = class {
1013
1023
  };
1014
1024
 
1015
1025
  const periodsAndDuration = this.parsePeriods_(context,
1016
- this.manifestPatchContext_.getBaseUris, periods);
1026
+ this.manifestPatchContext_.getBaseUris, periods, /* newPeriod= */ true);
1017
1027
 
1018
1028
  return periodsAndDuration.periods;
1019
1029
  }
@@ -1033,6 +1043,10 @@ shaka.dash.DashParser = class {
1033
1043
  this.contextCache_.delete(contextId);
1034
1044
  }
1035
1045
  }
1046
+ const newPeriods = this.lastManifestUpdatePeriodIds_.filter((pID) => {
1047
+ return pID !== periodId;
1048
+ });
1049
+ this.lastManifestUpdatePeriodIds_ = newPeriods;
1036
1050
  }
1037
1051
 
1038
1052
  /**
@@ -1274,6 +1288,7 @@ shaka.dash.DashParser = class {
1274
1288
  * @param {shaka.dash.DashParser.Context} context
1275
1289
  * @param {function():!Array.<string>} getBaseUris
1276
1290
  * @param {!shaka.extern.xml.Node} mpd
1291
+ * @param {!boolean} newPeriod
1277
1292
  * @return {{
1278
1293
  * periods: !Array.<shaka.extern.Period>,
1279
1294
  * duration: ?number,
@@ -1281,7 +1296,7 @@ shaka.dash.DashParser = class {
1281
1296
  * }}
1282
1297
  * @private
1283
1298
  */
1284
- parsePeriods_(context, getBaseUris, mpd) {
1299
+ parsePeriods_(context, getBaseUris, mpd, newPeriod) {
1285
1300
  const TXml = shaka.util.TXml;
1286
1301
  let presentationDuration = context.mediaPresentationDuration;
1287
1302
 
@@ -1413,8 +1428,18 @@ shaka.dash.DashParser = class {
1413
1428
  prevEnd = start + periodDuration;
1414
1429
  } // end of period parsing loop
1415
1430
 
1416
- // Replace previous seen periods with the current one.
1417
- this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
1431
+ if (newPeriod) {
1432
+ // append new period from the patch manifest
1433
+ for (const el of periods) {
1434
+ const periodID = el.id;
1435
+ if (!this.lastManifestUpdatePeriodIds_.includes(periodID)) {
1436
+ this.lastManifestUpdatePeriodIds_.push(periodID);
1437
+ }
1438
+ }
1439
+ } else {
1440
+ // Replace previous seen periods with the current one.
1441
+ this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
1442
+ }
1418
1443
 
1419
1444
  if (presentationDuration != null) {
1420
1445
  if (prevEnd != null) {
@@ -1442,6 +1467,26 @@ shaka.dash.DashParser = class {
1442
1467
  }
1443
1468
  }
1444
1469
 
1470
+ /**
1471
+ * Clean StreamMap Object to remove reference of deleted Stream Object
1472
+ * @private
1473
+ */
1474
+ cleanStreamMap_() {
1475
+ const oldPeriodIds = Object.keys(this.indexStreamMap_);
1476
+ if (oldPeriodIds.length === this.lastManifestUpdatePeriodIds_.length) {
1477
+ return;
1478
+ }
1479
+ const diffPeriodsIDs = oldPeriodIds.filter((pId) => {
1480
+ return !this.lastManifestUpdatePeriodIds_.includes(pId);
1481
+ });
1482
+ for (const pId of diffPeriodsIDs) {
1483
+ for (const contextId of this.indexStreamMap_[pId]) {
1484
+ delete this.streamMap_[contextId];
1485
+ }
1486
+ delete this.indexStreamMap_[pId];
1487
+ }
1488
+ }
1489
+
1445
1490
  /**
1446
1491
  * Parses a Period XML element. Unlike the other parse methods, this is not
1447
1492
  * given the Node; it is given a PeriodInfo structure. Also, partial parsing
@@ -1458,6 +1503,7 @@ shaka.dash.DashParser = class {
1458
1503
  const TXml = shaka.util.TXml;
1459
1504
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
1460
1505
 
1506
+ goog.asserts.assert(periodInfo.node, 'periodInfo.node should exist');
1461
1507
  context.period = this.createFrame_(periodInfo.node, null, getBaseUris);
1462
1508
  context.periodInfo = periodInfo;
1463
1509
  context.period.availabilityTimeOffset = context.availabilityTimeOffset;
@@ -2221,7 +2267,12 @@ shaka.dash.DashParser = class {
2221
2267
  };
2222
2268
 
2223
2269
  if (contextId && context.dynamic && !this.streamMap_[contextId]) {
2270
+ const periodId = context.period.id || '';
2271
+ if (!this.indexStreamMap_[periodId]) {
2272
+ this.indexStreamMap_[periodId] = [];
2273
+ }
2224
2274
  this.streamMap_[contextId] = stream;
2275
+ this.indexStreamMap_[periodId].push(contextId);
2225
2276
  }
2226
2277
 
2227
2278
  return stream;
@@ -2235,45 +2286,29 @@ shaka.dash.DashParser = class {
2235
2286
  * @private
2236
2287
  */
2237
2288
  cloneContext_(context) {
2238
- const contextClone = /** @type {!shaka.dash.DashParser.Context} */({});
2239
-
2240
- for (const k of Object.keys(context)) {
2241
- if (['period', 'adaptationSet', 'representation'].includes(k)) {
2242
- /** @type {shaka.dash.DashParser.InheritanceFrame} */
2243
- const frameRef = context[k];
2244
- contextClone[k] = {
2245
- segmentBase: null,
2246
- segmentList: null,
2247
- segmentTemplate: frameRef.segmentTemplate,
2248
- getBaseUris: frameRef.getBaseUris,
2249
- width: frameRef.width,
2250
- height: frameRef.height,
2251
- contentType: frameRef.contentType,
2252
- mimeType: frameRef.mimeType,
2253
- language: frameRef.language,
2254
- codecs: frameRef.codecs,
2255
- frameRate: frameRef.frameRate,
2256
- pixelAspectRatio: frameRef.pixelAspectRatio,
2257
- emsgSchemeIdUris: frameRef.emsgSchemeIdUris,
2258
- id: frameRef.id,
2259
- position: frameRef.position,
2260
- numChannels: frameRef.numChannels,
2261
- audioSamplingRate: frameRef.audioSamplingRate,
2262
- availabilityTimeOffset: frameRef.availabilityTimeOffset,
2263
- initialization: frameRef.initialization,
2264
- };
2265
- } else if (k == 'periodInfo') {
2266
- /** @type {shaka.dash.DashParser.PeriodInfo} */
2267
- const frameRef = context[k];
2268
- contextClone[k] = {
2269
- start: frameRef.start,
2270
- duration: frameRef.duration,
2271
- node: null,
2272
- isLastPeriod: frameRef.isLastPeriod,
2273
- };
2274
- } else {
2275
- contextClone[k] = context[k];
2289
+ /**
2290
+ * @param {?shaka.dash.DashParser.InheritanceFrame} frame
2291
+ * @return {?shaka.dash.DashParser.InheritanceFrame}
2292
+ */
2293
+ const cloneFrame = (frame) => {
2294
+ if (!frame) {
2295
+ return null;
2276
2296
  }
2297
+ const clone = shaka.util.ObjectUtils.shallowCloneObject(frame);
2298
+ clone.segmentBase = null;
2299
+ clone.segmentList = null;
2300
+ clone.segmentTemplate = shaka.util.TXml.cloneNode(clone.segmentTemplate);
2301
+ return clone;
2302
+ };
2303
+ const contextClone = shaka.util.ObjectUtils.shallowCloneObject(context);
2304
+ contextClone.period = cloneFrame(contextClone.period);
2305
+ contextClone.adaptationSet = cloneFrame(contextClone.adaptationSet);
2306
+ contextClone.representation = cloneFrame(contextClone.representation);
2307
+
2308
+ if (contextClone.periodInfo) {
2309
+ contextClone.periodInfo =
2310
+ shaka.util.ObjectUtils.shallowCloneObject(contextClone.periodInfo);
2311
+ contextClone.periodInfo.node = null;
2277
2312
  }
2278
2313
 
2279
2314
  return contextClone;
@@ -3137,7 +3172,7 @@ shaka.dash.DashParser.Context;
3137
3172
  * @typedef {{
3138
3173
  * start: number,
3139
3174
  * duration: ?number,
3140
- * node: !shaka.extern.xml.Node,
3175
+ * node: ?shaka.extern.xml.Node,
3141
3176
  * isLastPeriod: boolean
3142
3177
  * }}
3143
3178
  *
@@ -3149,7 +3184,7 @@ shaka.dash.DashParser.Context;
3149
3184
  * @property {?number} duration
3150
3185
  * The duration of the period; or null if the duration is not given. This
3151
3186
  * will be non-null for all periods except the last.
3152
- * @property {!shaka.extern.xml.Node} node
3187
+ * @property {?shaka.extern.xml.Node} node
3153
3188
  * The XML Node for the Period.
3154
3189
  * @property {boolean} isLastPeriod
3155
3190
  * Whether this Period is the last one in the manifest.
@@ -736,6 +736,9 @@ shaka.hls.HlsParser = class {
736
736
  // also start 6 seconds apart in presentation time.
737
737
 
738
738
  const segment0 = segmentIndex.earliestReference();
739
+ if (!segment0) {
740
+ continue;
741
+ }
739
742
  if (segment0.syncTime == null) {
740
743
  shaka.log.alwaysError('Missing EXT-X-PROGRAM-DATE-TIME for stream',
741
744
  streamInfo.getUris(),
@@ -95,6 +95,18 @@ shaka.media.ContentWorkarounds = class {
95
95
  newType: ContentWorkarounds.BOX_TYPE_ENCV_,
96
96
  });
97
97
  })
98
+ .fullBox('dvc1', (box) => {
99
+ boxesToModify.push({
100
+ box,
101
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
102
+ });
103
+ })
104
+ .fullBox('dvi1', (box) => {
105
+ boxesToModify.push({
106
+ box,
107
+ newType: ContentWorkarounds.BOX_TYPE_ENCV_,
108
+ });
109
+ })
98
110
  .fullBox('hev1', (box) => {
99
111
  boxesToModify.push({
100
112
  box,
@@ -493,9 +493,13 @@ shaka.media.SegmentUtils = class {
493
493
  return codecs;
494
494
  }
495
495
  const dolbyVision = codecs.find((codec) => {
496
- return codec.startsWith('dvh1.') ||
496
+ return codec.startsWith('dvav.') ||
497
+ codec.startsWith('dva1.') ||
498
+ codec.startsWith('dvh1.') ||
497
499
  codec.startsWith('dvhe.') ||
498
- codec.startsWith('dav1.');
500
+ codec.startsWith('dav1.') ||
501
+ codec.startsWith('dvc1.') ||
502
+ codec.startsWith('dvi1.');
499
503
  });
500
504
  if (!dolbyVision) {
501
505
  return codecs;
@@ -15,10 +15,9 @@ goog.require('shaka.log');
15
15
  goog.require('shaka.media.InitSegmentReference');
16
16
  goog.require('shaka.media.ManifestParser');
17
17
  goog.require('shaka.media.MediaSourceEngine');
18
- goog.require('shaka.media.SegmentIndex');
19
18
  goog.require('shaka.media.SegmentIterator');
20
- goog.require('shaka.media.SegmentPrefetch');
21
19
  goog.require('shaka.media.SegmentReference');
20
+ goog.require('shaka.media.SegmentPrefetch');
22
21
  goog.require('shaka.media.SegmentUtils');
23
22
  goog.require('shaka.net.Backoff');
24
23
  goog.require('shaka.net.NetworkingEngine');
@@ -75,16 +74,9 @@ shaka.media.StreamingEngine = class {
75
74
  this.config_ = null;
76
75
 
77
76
  /**
78
- * Retains deferred SegmentIndex objects for streams which were switched
79
- * away from during an ongoing fetchAndAppend_().
80
- * They are released and cleared from this map when the next onUpdate_() for
81
- * a stream's content type occurs.
82
- * This is because at that point, it is guaranteed no logic is actively
83
- * using information for these old streams.
84
- *
85
- * Lastly, the operations are stored in arrays in the off chance multiple
86
- * ABR switches happen during an ongoing fetchAndAppend_().
87
- * @private {!Map.<string, !Array.<?shaka.media.SegmentIndex>>}
77
+ * Retains a reference to the function used to close SegmentIndex objects
78
+ * for streams which were switched away from during an ongoing update_().
79
+ * @private {!Map.<string, !function()>}
88
80
  */
89
81
  this.deferredCloseSegmentIndex_ = new Map();
90
82
 
@@ -176,7 +168,6 @@ shaka.media.StreamingEngine = class {
176
168
  state.segmentPrefetch.clearAll();
177
169
  state.segmentPrefetch = null;
178
170
  }
179
- this.handleDeferredCloseSegmentIndexes_(state);
180
171
  }
181
172
  for (const prefetch of this.audioPrefetchMap_.values()) {
182
173
  prefetch.clearAll();
@@ -186,7 +177,6 @@ shaka.media.StreamingEngine = class {
186
177
 
187
178
  this.mediaStates_.clear();
188
179
  this.audioPrefetchMap_.clear();
189
- this.deferredCloseSegmentIndex_.clear();
190
180
 
191
181
  this.playerInterface_ = null;
192
182
  this.manifest_ = null;
@@ -530,14 +520,9 @@ shaka.media.StreamingEngine = class {
530
520
  handleDeferredCloseSegmentIndexes_(mediaState) {
531
521
  for (const [key, value] of this.deferredCloseSegmentIndex_.entries()) {
532
522
  const streamId = /** @type {string} */ (key);
533
- const segmentIndexArray =
534
- /** @type {!Array.<?shaka.media.SegmentIndex>} */ (value);
523
+ const closeSegmentIndex = /** @type {!function()} */ (value);
535
524
  if (streamId.includes(mediaState.type)) {
536
- for (const segmentIndex of segmentIndexArray) {
537
- if (segmentIndex) {
538
- segmentIndex.release();
539
- }
540
- }
525
+ closeSegmentIndex();
541
526
  this.deferredCloseSegmentIndex_.delete(streamId);
542
527
  }
543
528
  }
@@ -612,26 +597,23 @@ shaka.media.StreamingEngine = class {
612
597
  fullMimeType, this.manifest_.sequenceMode, stream.external);
613
598
  }
614
599
 
615
- // Releases the segmentIndex of the old stream if safe to do so.
600
+ // Releases the segmentIndex of the old stream.
616
601
  // Do not close segment indexes we are prefetching.
617
602
  if (!this.audioPrefetchMap_.has(mediaState.stream)) {
618
603
  if (mediaState.stream.closeSegmentIndex) {
619
604
  if (mediaState.performingUpdate) {
620
605
  const oldStreamTag =
621
606
  shaka.media.StreamingEngine.logPrefix_(mediaState);
622
- // The ongoing update is still using the old stream's segment
623
- // reference information.
624
- // If we close the old stream now, the update will not complete
625
- // correctly.
626
- // onUpdate_() will resume the closeSegmentIndex() operation for the
627
- // old stream when the update has finished.
628
607
  if (!this.deferredCloseSegmentIndex_.has(oldStreamTag)) {
608
+ // The ongoing update is still using the old stream's segment
609
+ // reference information.
610
+ // If we close the old stream now, the update will not complete
611
+ // correctly.
612
+ // The next onUpdate_() for this content type will resume the
613
+ // closeSegmentIndex() operation for the old stream once the ongoing
614
+ // update has finished, then immediately create a new segment index.
629
615
  this.deferredCloseSegmentIndex_.set(
630
- oldStreamTag, [mediaState.stream.segmentIndex]);
631
- } else {
632
- // We switched from stream A -> B -> back to A.
633
- this.deferredCloseSegmentIndex_.get(oldStreamTag).push(
634
- mediaState.stream.segmentIndex);
616
+ oldStreamTag, mediaState.stream.closeSegmentIndex);
635
617
  }
636
618
  } else {
637
619
  mediaState.stream.closeSegmentIndex();
@@ -643,8 +625,8 @@ shaka.media.StreamingEngine = class {
643
625
  mediaState.segmentIterator = null;
644
626
  mediaState.adaptation = !!adaptation;
645
627
 
646
- const newStreamTag = shaka.media.StreamingEngine.logPrefix_(mediaState);
647
- shaka.log.debug('switch: switching to Stream ' + newStreamTag);
628
+ const streamTag = shaka.media.StreamingEngine.logPrefix_(mediaState);
629
+ shaka.log.debug('switch: switching to Stream ' + streamTag);
648
630
 
649
631
  if (clearBuffer) {
650
632
  if (mediaState.clearingBuffer) {
@@ -1228,6 +1210,11 @@ shaka.media.StreamingEngine = class {
1228
1210
  return;
1229
1211
  }
1230
1212
 
1213
+ // If stream switches happened during the previous update_() for this
1214
+ // content type, close out the old streams that were switched away from.
1215
+ // Even if we had switched away from the active stream 'A' during the
1216
+ // update_(), e.g. (A -> B -> A), closing 'A' is permissible here since we
1217
+ // will immediately re-create it in the logic below.
1231
1218
  this.handleDeferredCloseSegmentIndexes_(mediaState);
1232
1219
 
1233
1220
  // Make sure the segment index exists. If not, create the segment index.
@@ -1546,7 +1533,8 @@ shaka.media.StreamingEngine = class {
1546
1533
  // If there's positive drift then we need to adjust the lookup time, and
1547
1534
  // may wind up requesting the previous segment to be safe.
1548
1535
  // inaccurateManifestTolerance should be 0 for low latency streaming.
1549
- const inaccurateTolerance = this.config_.inaccurateManifestTolerance;
1536
+ const inaccurateTolerance = this.manifest_.sequenceMode ?
1537
+ 0 : this.config_.inaccurateManifestTolerance;
1550
1538
  const lookupTime = Math.max(presentationTime - inaccurateTolerance, 0);
1551
1539
 
1552
1540
  shaka.log.v1(logPrefix, 'looking up segment',
@@ -2086,6 +2074,8 @@ shaka.media.StreamingEngine = class {
2086
2074
  .box('dva1', Mp4Parser.visualSampleEntry)
2087
2075
  .box('dvh1', Mp4Parser.visualSampleEntry)
2088
2076
  .box('dvhe', Mp4Parser.visualSampleEntry)
2077
+ .box('dvc1', Mp4Parser.visualSampleEntry)
2078
+ .box('dvi1', Mp4Parser.visualSampleEntry)
2089
2079
  .box('vexu', Mp4Parser.children)
2090
2080
  .box('proj', Mp4Parser.children)
2091
2081
  .fullBox('prji', (box) => {
@@ -2810,6 +2800,10 @@ shaka.media.StreamingEngine = class {
2810
2800
  this.destroyer_.ensureNotDestroyed();
2811
2801
  // Try to recover from network errors
2812
2802
  if (error.category === shaka.util.Error.Category.NETWORK) {
2803
+ if (mediaState.restoreStreamAfterTrickPlay) {
2804
+ this.setTrickPlay(/* on= */ false);
2805
+ return;
2806
+ }
2813
2807
  const maxDisabledTime = this.getDisabledTime_(error);
2814
2808
  error.handled = this.playerInterface_.disableStream(
2815
2809
  mediaState.stream, maxDisabledTime);
package/lib/player.js CHANGED
@@ -4454,9 +4454,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4454
4454
  * beginning or end of the seekable range for the content.
4455
4455
  *
4456
4456
  * @param {number} rate
4457
+ * @param {boolean=} useTrickPlayTrack
4457
4458
  * @export
4458
4459
  */
4459
- trickPlay(rate) {
4460
+ trickPlay(rate, useTrickPlayTrack = true) {
4460
4461
  // A playbackRate of 0 is used internally when we are in a buffering state,
4461
4462
  // and doesn't make sense for trick play. If you set a rate of 0 for trick
4462
4463
  // play, we will reject it and issue a warning. If it happens during a
@@ -4478,7 +4479,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4478
4479
 
4479
4480
  if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
4480
4481
  this.abrManager_.playbackRateChanged(rate);
4481
- this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
4482
+ this.streamingEngine_.setTrickPlay(
4483
+ useTrickPlayTrack && Math.abs(rate) > 1);
4482
4484
  }
4483
4485
  if (this.isLive()) {
4484
4486
  this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
@@ -5561,6 +5563,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
5561
5563
  }
5562
5564
 
5563
5565
  if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
5566
+ this.stats_.addBytesDownloaded(NaN);
5564
5567
  this.stats_.setResolution(
5565
5568
  /* width= */ element.videoWidth || NaN,
5566
5569
  /* height= */ element.videoHeight || NaN);
@@ -6502,7 +6505,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6502
6505
  shaka.log.debug('Buffer fullness ratio (' + bufferFullness + ') ' +
6503
6506
  'is less than the vodDynamicPlaybackRateBufferRatio (' +
6504
6507
  bufferThreshold + '). Updating playbackRate to ' + minPlaybackRate);
6505
- this.trickPlay(minPlaybackRate);
6508
+ this.trickPlay(minPlaybackRate, /* useTrickPlayTrack= */ false);
6506
6509
  }
6507
6510
  } else if (bufferFullness == 1) {
6508
6511
  if (playbackRate !== this.playRateController_.getDefaultRate()) {
@@ -6629,7 +6632,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6629
6632
  timeSinceLastRebuffer + 's) ' +
6630
6633
  'is less than the live sync panicThreshold (' + panicThreshold +
6631
6634
  's). Updating playbackRate to ' + minPlaybackRate);
6632
- this.trickPlay(minPlaybackRate);
6635
+ this.trickPlay(minPlaybackRate, /* useTrickPlayTrack= */ false);
6633
6636
  }
6634
6637
  } else if (maxLatency && maxPlaybackRate &&
6635
6638
  (latency - offset) > maxLatency) {
@@ -6637,7 +6640,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6637
6640
  shaka.log.debug('Latency (' + latency + 's) is greater than ' +
6638
6641
  'live sync maxLatency (' + maxLatency + 's). ' +
6639
6642
  'Updating playbackRate to ' + maxPlaybackRate);
6640
- this.trickPlay(maxPlaybackRate);
6643
+ this.trickPlay(maxPlaybackRate, /* useTrickPlayTrack= */ false);
6641
6644
  }
6642
6645
  this.targetLatencyReached_ = null;
6643
6646
  } else if (minLatency && minPlaybackRate &&
@@ -6646,7 +6649,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
6646
6649
  shaka.log.debug('Latency (' + latency + 's) is smaller than ' +
6647
6650
  'live sync minLatency (' + minLatency + 's). ' +
6648
6651
  'Updating playbackRate to ' + minPlaybackRate);
6649
- this.trickPlay(minPlaybackRate);
6652
+ this.trickPlay(minPlaybackRate, /* useTrickPlayTrack= */ false);
6650
6653
  }
6651
6654
  this.targetLatencyReached_ = null;
6652
6655
  } else if (playbackRate !== this.playRateController_.getDefaultRate()) {
@@ -7257,19 +7260,32 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
7257
7260
  if (hasAltStream) {
7258
7261
  let didDisableStream = false;
7259
7262
 
7263
+ let isTrickModeVideo = false;
7264
+
7260
7265
  for (const variant of this.manifest_.variants) {
7261
7266
  const candidate = variant[stream.type];
7262
7267
 
7263
- if (candidate && candidate.id === stream.id) {
7268
+ if (!candidate) {
7269
+ continue;
7270
+ }
7271
+
7272
+ if (candidate.id === stream.id) {
7264
7273
  variant.disabledUntilTime = (Date.now() / 1000) + disableTime;
7265
7274
  didDisableStream = true;
7266
7275
 
7267
7276
  shaka.log.v2(
7268
7277
  'Disabled stream ' + stream.type + ':' + stream.id +
7269
7278
  ' for ' + disableTime + ' seconds...');
7279
+ } else if (candidate.trickModeVideo &&
7280
+ candidate.trickModeVideo.id == stream.id) {
7281
+ isTrickModeVideo = true;
7270
7282
  }
7271
7283
  }
7272
7284
 
7285
+ if (!didDisableStream && isTrickModeVideo) {
7286
+ return false;
7287
+ }
7288
+
7273
7289
  goog.asserts.assert(didDisableStream, 'Must have disabled stream');
7274
7290
 
7275
7291
  this.checkVariantsTimer_.tickEvery(1);
@@ -7832,7 +7848,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
7832
7848
  * @export
7833
7849
  */
7834
7850
  // eslint-disable-next-line no-useless-concat
7835
- shaka.Player.version = 'v4.10.11' + '-uncompiled'; // x-release-please-version
7851
+ shaka.Player.version = 'v4.10.13' + '-uncompiled'; // x-release-please-version
7836
7852
 
7837
7853
  // Initialize the deprecation system using the version string we just set
7838
7854
  // on the player.
@@ -172,6 +172,9 @@ shaka.util.MimeUtils = class {
172
172
  case base === 'hvc1':
173
173
  case base === 'hev1':
174
174
  return 'hevc'; // H265
175
+ case base === 'vvc1':
176
+ case base === 'vvi1':
177
+ return 'vvc'; // H266
175
178
  case base === 'dvh1':
176
179
  case base === 'dvhe':
177
180
  return 'dovi-hevc'; // Dolby Vision based in HEVC
@@ -180,6 +183,9 @@ shaka.util.MimeUtils = class {
180
183
  return 'dovi-avc'; // Dolby Vision based in AVC
181
184
  case base === 'dav1':
182
185
  return 'dovi-av1'; // Dolby Vision based in AV1
186
+ case base === 'dvc1':
187
+ case base === 'dvi1':
188
+ return 'dovi-vvc'; // Dolby Vision based in VVC
183
189
  }
184
190
  return base;
185
191
  }