shaka-player 4.9.8 → 4.9.10

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.
@@ -589,8 +589,8 @@ shaka.extern.MetadataFrame;
589
589
  * The presentation time (in seconds) that the region should end.
590
590
  * @property {string} id
591
591
  * Specifies an identifier for this instance of the region.
592
- * @property {Elment} eventElement
593
- * <b>DEPRECATED</b>: Use eventElement instead.
592
+ * @property {Element} eventElement
593
+ * <b>DEPRECATED</b>: Use eventNode instead.
594
594
  * The XML element that defines the Event.
595
595
  * @property {?shaka.extern.xml.Node} eventNode
596
596
  * The XML element that defines the Event.
@@ -164,53 +164,62 @@ shaka.cea.Mp4CeaParser = class {
164
164
  /** @type {!Array<!shaka.extern.ICeaParser.CaptionPacket>} **/
165
165
  const captionPackets = [];
166
166
 
167
- // Fields that are found in MOOF boxes
168
- let defaultSampleDuration = this.defaultSampleDuration_;
169
- let defaultSampleSize = this.defaultSampleSize_;
170
167
  let moofOffset = 0;
171
- /** @type {!Array<shaka.util.ParsedTRUNBox>} */
172
- let parsedTRUNs = [];
173
- let baseMediaDecodeTime = null;
174
- let timescale = shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE;
168
+
169
+ /** @type {!Array<!shaka.cea.Mp4CeaParser.ParsedTRAF>} */
170
+ let parsedTRAFs = [];
175
171
 
176
172
  new Mp4Parser()
177
173
  .box('moof', (box) => {
178
174
  moofOffset = box.start;
179
- // trun box parsing is reset on each moof.
180
- parsedTRUNs = [];
175
+ // traf box parsing is reset on each moof.
176
+ parsedTRAFs = [];
177
+ Mp4Parser.children(box);
178
+ })
179
+ .box('traf', (box) => {
180
+ parsedTRAFs.push({
181
+ baseMediaDecodeTime: null,
182
+ defaultSampleDuration: this.defaultSampleDuration_,
183
+ defaultSampleSize: this.defaultSampleSize_,
184
+ parsedTRUNs: [],
185
+ timescale: shaka.cea.CeaUtils.DEFAULT_TIMESCALE_VALUE,
186
+ });
181
187
  Mp4Parser.children(box);
182
188
  })
183
- .box('traf', Mp4Parser.children)
184
189
  .fullBox('trun', (box) => {
185
190
  goog.asserts.assert(
186
- box.version != null && box.flags!=null,
191
+ box.version != null && box.flags != null,
187
192
  'TRUN is a full box and should have a valid version & flags.');
188
193
 
194
+ const lastTRAF = parsedTRAFs[parsedTRAFs.length - 1];
195
+
189
196
  const parsedTRUN = shaka.util.Mp4BoxParsers.parseTRUN(
190
197
  box.reader, box.version, box.flags);
191
- parsedTRUNs.push(parsedTRUN);
198
+ lastTRAF.parsedTRUNs.push(parsedTRUN);
192
199
  })
193
200
  .fullBox('tfhd', (box) => {
194
201
  goog.asserts.assert(
195
202
  box.flags != null,
196
203
  'TFHD is a full box and should have valid flags.');
197
204
 
205
+ const lastTRAF = parsedTRAFs[parsedTRAFs.length - 1];
206
+
198
207
  const parsedTFHD = shaka.util.Mp4BoxParsers.parseTFHD(
199
208
  box.reader, box.flags);
200
209
 
201
210
  // If specified, defaultSampleDuration and defaultSampleSize
202
211
  // override the ones specified in the TREX box
203
- defaultSampleDuration = parsedTFHD.defaultSampleDuration ||
212
+ lastTRAF.defaultSampleDuration = parsedTFHD.defaultSampleDuration ||
204
213
  this.defaultSampleDuration_;
205
214
 
206
- defaultSampleSize = parsedTFHD.defaultSampleSize ||
215
+ lastTRAF.defaultSampleSize = parsedTFHD.defaultSampleSize ||
207
216
  this.defaultSampleSize_;
208
217
 
209
218
  const trackId = parsedTFHD.trackId;
210
219
 
211
220
  // Get the timescale from the track Id
212
221
  if (this.trackIdToTimescale_.has(trackId)) {
213
- timescale = this.trackIdToTimescale_.get(trackId);
222
+ lastTRAF.timescale = this.trackIdToTimescale_.get(trackId);
214
223
  }
215
224
  })
216
225
  .fullBox('tfdt', (box) => {
@@ -218,27 +227,37 @@ shaka.cea.Mp4CeaParser = class {
218
227
  box.version != null,
219
228
  'TFDT is a full box and should have a valid version.');
220
229
 
230
+ const lastTRAF = parsedTRAFs[parsedTRAFs.length - 1];
231
+
221
232
  const parsedTFDT = shaka.util.Mp4BoxParsers.parseTFDTInaccurate(
222
233
  box.reader, box.version);
223
234
 
224
- baseMediaDecodeTime = parsedTFDT.baseMediaDecodeTime;
235
+ lastTRAF.baseMediaDecodeTime = parsedTFDT.baseMediaDecodeTime;
225
236
  })
226
237
  .box('mdat', (box) => {
227
- if (baseMediaDecodeTime === null) {
228
- // This field should have been populated by the Base Media Decode
229
- // Time in the tfdt box.
230
- shaka.log.alwaysWarn(
231
- 'Unable to find base media decode time for CEA captions!');
232
- throw new shaka.util.Error(
233
- shaka.util.Error.Severity.CRITICAL,
234
- shaka.util.Error.Category.TEXT,
235
- shaka.util.Error.Code.INVALID_MP4_CEA);
236
- }
237
-
238
238
  const offset = moofOffset - box.start - 8;
239
- this.parseMdat_(box.reader, baseMediaDecodeTime, timescale,
240
- defaultSampleDuration, defaultSampleSize, offset, parsedTRUNs,
241
- captionPackets);
239
+ const initialPosition = box.reader.getPosition();
240
+ for (const parsedTRAF of parsedTRAFs) {
241
+ if (parsedTRAF.baseMediaDecodeTime === null) {
242
+ // This field should have been populated by the Base Media Decode
243
+ // Time in the tfdt box.
244
+ shaka.log.alwaysWarn(
245
+ 'Unable to find base media decode time for CEA captions!');
246
+ throw new shaka.util.Error(
247
+ shaka.util.Error.Severity.CRITICAL,
248
+ shaka.util.Error.Category.TEXT,
249
+ shaka.util.Error.Code.INVALID_MP4_CEA);
250
+ }
251
+ box.reader.seek(initialPosition);
252
+ this.parseMdat_(box.reader,
253
+ parsedTRAF.baseMediaDecodeTime,
254
+ parsedTRAF.timescale,
255
+ parsedTRAF.defaultSampleDuration,
256
+ parsedTRAF.defaultSampleSize,
257
+ offset,
258
+ parsedTRAF.parsedTRUNs,
259
+ captionPackets);
260
+ }
242
261
  })
243
262
  .parse(mediaSegment, /* partialOkay= */ false);
244
263
 
@@ -315,7 +334,7 @@ shaka.cea.Mp4CeaParser = class {
315
334
  timeOffset = sampleData[sampleIndex].sampleCompositionTimeOffset || 0;
316
335
  }
317
336
 
318
- const pts = (time + timeOffset)/timescale;
337
+ const pts = (time + timeOffset) / timescale;
319
338
  for (const packet of this.seiProcessor_
320
339
  .process(reader.readBytes(naluSize - naluHeaderSize))) {
321
340
  captionPackets.push({
@@ -384,5 +403,22 @@ shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = {
384
403
  'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
385
404
  };
386
405
 
406
+ /**
407
+ * @typedef {{
408
+ * baseMediaDecodeTime: ?number,
409
+ * defaultSampleDuration: number,
410
+ * defaultSampleSize: number,
411
+ * parsedTRUNs: !Array<shaka.util.ParsedTRUNBox>,
412
+ * timescale: number
413
+ * }}
414
+ *
415
+ * @property {?number} baseMediaDecodeTime
416
+ * @property {number} defaultSampleDuration
417
+ * @property {number} defaultSampleSize
418
+ * @property {!Array<shaka.util.ParsedTRUNBox>} parsedTRUNs
419
+ * @property {?number} timescale
420
+ */
421
+ shaka.cea.Mp4CeaParser.ParsedTRAF;
422
+
387
423
  shaka.media.ClosedCaptionParser.registerParser('video/mp4',
388
424
  () => new shaka.cea.Mp4CeaParser());
@@ -166,9 +166,9 @@ shaka.dash.MpdUtils = class {
166
166
 
167
167
  if (!d) {
168
168
  shaka.log.warning(
169
- '"S" element must have a duration:',
170
- 'ignoring the remaining "S" elements.', timePoint);
171
- return timeline;
169
+ '"S" element must have a duration: ignoring this element.',
170
+ timePoint);
171
+ continue;
172
172
  }
173
173
 
174
174
  let startTime = t != null ? t : lastEndTime;
@@ -466,7 +466,7 @@ shaka.hls.HlsParser = class {
466
466
  }
467
467
 
468
468
  const {segments, bandwidth} = this.createSegments_(
469
- playlist, stream, mediaSequenceToStartTime, mediaVariables,
469
+ playlist, mediaSequenceToStartTime, mediaVariables,
470
470
  streamInfo.getUris, streamInfo.type);
471
471
  if (bandwidth) {
472
472
  stream.bandwidth = bandwidth;
@@ -2302,6 +2302,13 @@ shaka.hls.HlsParser = class {
2302
2302
  /** @type {!shaka.hls.Playlist} */
2303
2303
  const playlist = this.manifestTextParser_.parsePlaylist(response.data);
2304
2304
 
2305
+ if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
2306
+ throw new shaka.util.Error(
2307
+ shaka.util.Error.Severity.CRITICAL,
2308
+ shaka.util.Error.Category.MANIFEST,
2309
+ shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
2310
+ }
2311
+
2305
2312
  /** @type {!Array.<!shaka.hls.Tag>} */
2306
2313
  const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
2307
2314
  'EXT-X-DEFINE');
@@ -2319,24 +2326,32 @@ shaka.hls.HlsParser = class {
2319
2326
  this.config_.hls.disableCodecGuessing) ||
2320
2327
  (this.needsClosedCaptionsDetection_ && type == ContentType.VIDEO &&
2321
2328
  !this.config_.hls.disableClosedCaptionsDetection)) {
2322
- this.needsClosedCaptionsDetection_ = false;
2323
-
2324
- const basicInfo = await this.getMediaPlaylistBasicInfo_(
2325
- playlist, getUris, mediaVariables);
2326
-
2327
- goog.asserts.assert(
2328
- type === basicInfo.type, 'Media types should match!');
2329
-
2330
- if (basicInfo.closedCaptions.size && (!closedCaptions ||
2331
- closedCaptions.size != basicInfo.closedCaptions.size)) {
2332
- closedCaptions = basicInfo.closedCaptions;
2333
- closedCaptionsUpdated = true;
2329
+ let canRequestBasicInfo = playlist.segments.length > 0;
2330
+ if (canRequestBasicInfo) {
2331
+ const segment = playlist.segments[0];
2332
+ if (shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXT-X-GAP')) {
2333
+ canRequestBasicInfo = false;
2334
+ }
2334
2335
  }
2336
+ if (canRequestBasicInfo) {
2337
+ this.needsClosedCaptionsDetection_ = false;
2338
+ const basicInfo = await this.getMediaPlaylistBasicInfo_(
2339
+ playlist, getUris, mediaVariables);
2340
+
2341
+ goog.asserts.assert(
2342
+ type === basicInfo.type, 'Media types should match!');
2343
+
2344
+ if (basicInfo.closedCaptions.size && (!closedCaptions ||
2345
+ closedCaptions.size != basicInfo.closedCaptions.size)) {
2346
+ closedCaptions = basicInfo.closedCaptions;
2347
+ closedCaptionsUpdated = true;
2348
+ }
2335
2349
 
2336
- if (!this.codecInfoInManifest_ &&
2337
- this.config_.hls.disableCodecGuessing) {
2338
- mimeType = basicInfo.mimeType;
2339
- codecs = basicInfo.codecs;
2350
+ if (!this.codecInfoInManifest_ &&
2351
+ this.config_.hls.disableCodecGuessing) {
2352
+ mimeType = basicInfo.mimeType;
2353
+ codecs = basicInfo.codecs;
2354
+ }
2340
2355
  }
2341
2356
  }
2342
2357
 
@@ -2623,15 +2638,6 @@ shaka.hls.HlsParser = class {
2623
2638
  getUris, responseUri, codecs, type, languageValue, primary, name,
2624
2639
  channelsCount, closedCaptions, characteristics, forced, sampleRate,
2625
2640
  spatialAudio, mimeType = undefined) {
2626
- if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
2627
- // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
2628
- // playlists.
2629
- throw new shaka.util.Error(
2630
- shaka.util.Error.Severity.CRITICAL,
2631
- shaka.util.Error.Category.MANIFEST,
2632
- shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
2633
- }
2634
-
2635
2641
  goog.asserts.assert(playlist.segments != null,
2636
2642
  'Media playlist should have segments!');
2637
2643
 
@@ -2641,9 +2647,14 @@ shaka.hls.HlsParser = class {
2641
2647
  this.determineLastTargetDuration_(playlist);
2642
2648
  }
2643
2649
 
2650
+ const mediaSequenceToStartTime = this.isLive_() ?
2651
+ this.mediaSequenceToStartTimeByType_.get(type) : new Map();
2652
+
2653
+ const {segments, bandwidth} = this.createSegments_(
2654
+ playlist, mediaSequenceToStartTime, variables, getUris, type);
2655
+
2644
2656
  if (!mimeType) {
2645
- mimeType = await this.guessMimeType_(type, codecs, playlist,
2646
- variables, getUris);
2657
+ mimeType = await this.guessMimeType_(type, codecs, segments);
2647
2658
  }
2648
2659
 
2649
2660
  const {drmInfos, keyIds, encrypted, aesEncrypted} =
@@ -2663,16 +2674,10 @@ shaka.hls.HlsParser = class {
2663
2674
  stream.drmInfos = drmInfos;
2664
2675
  stream.keyIds = keyIds;
2665
2676
  stream.mimeType = mimeType;
2666
- this.setFullTypeForStream_(stream);
2667
-
2668
- const mediaSequenceToStartTime = this.isLive_() ?
2669
- this.mediaSequenceToStartTimeByType_.get(type) : new Map();
2670
-
2671
- const {segments, bandwidth} = this.createSegments_(
2672
- playlist, stream, mediaSequenceToStartTime, variables, getUris, type);
2673
2677
  if (bandwidth) {
2674
2678
  stream.bandwidth = bandwidth;
2675
2679
  }
2680
+ this.setFullTypeForStream_(stream);
2676
2681
 
2677
2682
  // This new calculation is necessary for Low Latency streams.
2678
2683
  if (this.isLive_()) {
@@ -3303,7 +3308,7 @@ shaka.hls.HlsParser = class {
3303
3308
  * @param {number} startTime
3304
3309
  * @param {!Map.<string, string>} variables
3305
3310
  * @param {!shaka.hls.Playlist} playlist
3306
- * @param {shaka.extern.Stream} stream
3311
+ * @param {string} type
3307
3312
  * @param {function():!Array.<string>} getUris
3308
3313
  * @param {shaka.extern.aesKey=} aesKey
3309
3314
  * @return {shaka.media.SegmentReference}
@@ -3311,7 +3316,7 @@ shaka.hls.HlsParser = class {
3311
3316
  */
3312
3317
  createSegmentReference_(
3313
3318
  initSegmentReference, previousReference, hlsSegment, startTime,
3314
- variables, playlist, stream, getUris, aesKey) {
3319
+ variables, playlist, type, getUris, aesKey) {
3315
3320
  const tags = hlsSegment.tags;
3316
3321
  const extinfTag =
3317
3322
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
@@ -3368,8 +3373,8 @@ shaka.hls.HlsParser = class {
3368
3373
  let isPreloadSegment = false;
3369
3374
 
3370
3375
  if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
3371
- const byterangeOptimizationSupport = (stream.mimeType == 'video/mp4' ||
3372
- stream.mimeType == 'audio/mp4') && window.ReadableStream &&
3376
+ const byterangeOptimizationSupport =
3377
+ initSegmentReference && window.ReadableStream &&
3373
3378
  this.config_.hls.allowLowLatencyByteRangeOptimization;
3374
3379
 
3375
3380
  let partialSyncTime = syncTime;
@@ -3528,7 +3533,7 @@ shaka.hls.HlsParser = class {
3528
3533
 
3529
3534
  let tilesLayout = '';
3530
3535
  let tileDuration = null;
3531
- if (stream.type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
3536
+ if (type == shaka.util.ManifestParserUtils.ContentType.IMAGE) {
3532
3537
  // By default in HLS the tilesLayout is 1x1
3533
3538
  tilesLayout = '1x1';
3534
3539
  const tilesTag =
@@ -3622,7 +3627,6 @@ shaka.hls.HlsParser = class {
3622
3627
  * playlist.
3623
3628
  *
3624
3629
  * @param {!shaka.hls.Playlist} playlist
3625
- * @param {shaka.extern.Stream} stream
3626
3630
  * @param {!Map.<number, number>} mediaSequenceToStartTime
3627
3631
  * @param {!Map.<string, string>} variables
3628
3632
  * @param {function():!Array.<string>} getUris
@@ -3631,7 +3635,7 @@ shaka.hls.HlsParser = class {
3631
3635
  * bandwidth: (number|undefined)}}
3632
3636
  * @private
3633
3637
  */
3634
- createSegments_(playlist, stream, mediaSequenceToStartTime, variables,
3638
+ createSegments_(playlist, mediaSequenceToStartTime, variables,
3635
3639
  getUris, type) {
3636
3640
  /** @type {Array.<!shaka.hls.Segment>} */
3637
3641
  const hlsSegments = playlist.segments;
@@ -3723,7 +3727,7 @@ shaka.hls.HlsParser = class {
3723
3727
  startTime,
3724
3728
  variables,
3725
3729
  playlist,
3726
- stream,
3730
+ type,
3727
3731
  getUris,
3728
3732
  aesKey);
3729
3733
 
@@ -3922,34 +3926,37 @@ shaka.hls.HlsParser = class {
3922
3926
  }
3923
3927
 
3924
3928
  /**
3925
- * Attempts to guess stream's mime type based on content type, URI, and
3926
- * contents of the playlist.
3929
+ * Attempts to guess stream's mime type.
3927
3930
  *
3928
3931
  * @param {string} contentType
3929
3932
  * @param {string} codecs
3930
- * @param {!shaka.hls.Playlist} playlist
3931
- * @param {!Map.<string, string>} variables
3932
- * @param {function():!Array.<string>} getUris
3933
+ * @param {!Array.<!shaka.media.SegmentReference>} segments
3933
3934
  * @return {!Promise.<string>}
3934
3935
  * @private
3935
3936
  */
3936
- async guessMimeType_(contentType, codecs, playlist, variables, getUris) {
3937
+ async guessMimeType_(contentType, codecs, segments) {
3937
3938
  const HlsParser = shaka.hls.HlsParser;
3938
3939
  const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
3939
3940
 
3940
3941
  // If you wait long enough, requesting the first segment can fail
3941
3942
  // because it has fallen off the left edge of DVR, so to be safer,
3942
3943
  // let's request the middle segment.
3943
- goog.asserts.assert(playlist.segments.length,
3944
- 'Playlist should have segments!');
3945
- const middleSegmentIdx = Math.trunc((playlist.segments.length - 1) / 2);
3944
+ goog.asserts.assert(segments.length, 'Should have segments!');
3945
+ let segmentIndex = Math.trunc((segments.length - 1) / 2);
3946
+ let segment = segments[segmentIndex];
3947
+ while (segment.status == shaka.media.SegmentReference.Status.MISSING &&
3948
+ segmentIndex < segments.length) {
3949
+ segmentIndex ++;
3950
+ segment = segments[segmentIndex];
3951
+ }
3946
3952
 
3947
- const middleSegmentUris = shaka.hls.Utils.constructSegmentUris(
3948
- getUris(),
3949
- playlist.segments[middleSegmentIdx].verbatimSegmentUri,
3950
- variables);
3953
+ if (segment.status == shaka.media.SegmentReference.Status.MISSING) {
3954
+ return this.guessMimeTypeFallback_(contentType);
3955
+ }
3956
+
3957
+ const segmentUris = segment.getUris();
3951
3958
 
3952
- const parsedUri = new goog.Uri(middleSegmentUris[0]);
3959
+ const parsedUri = new goog.Uri(segmentUris[0]);
3953
3960
  const extension = parsedUri.getPath().split('.').pop();
3954
3961
  const map = HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_[contentType];
3955
3962
 
@@ -3974,7 +3981,7 @@ shaka.hls.HlsParser = class {
3974
3981
  let contentMimeType;
3975
3982
  const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
3976
3983
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
3977
- middleSegmentUris, this.config_.retryParameters);
3984
+ segmentUris, this.config_.retryParameters);
3978
3985
  try {
3979
3986
  headRequest.method = 'HEAD';
3980
3987
  const response = await this.makeNetworkRequest_(
@@ -3995,7 +4002,7 @@ shaka.hls.HlsParser = class {
3995
4002
 
3996
4003
  if (contentMimeType) {
3997
4004
  // Split the MIME type in case the server sent additional parameters.
3998
- return contentMimeType.split(';')[0];
4005
+ return contentMimeType.toLowerCase().split(';')[0];
3999
4006
  }
4000
4007
 
4001
4008
  return this.guessMimeTypeFallback_(contentType);
@@ -312,4 +312,5 @@ shaka.hls.ManifestTextParser.SEGMENT_TAGS = [
312
312
  'EXT-X-KEY',
313
313
  'EXT-X-DATERANGE',
314
314
  'EXT-X-MAP',
315
+ 'EXT-X-GAP',
315
316
  ];
@@ -664,7 +664,7 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
664
664
  return shaka.media.StreamingEngine.dispatchFetch(
665
665
  reference, stream, streamDataCallback || null,
666
666
  this.config_.streaming.retryParameters, this.networkingEngine_);
667
- });
667
+ }, /* reverse= */ false);
668
668
  this.segmentPrefetchById_.set(stream.id, prefetch);
669
669
 
670
670
  // Start prefetching a bit.
@@ -444,12 +444,17 @@ shaka.media.SegmentIndex = class {
444
444
  let index = this.find(time);
445
445
  if (index == null) {
446
446
  return null;
447
- } else {
447
+ }
448
+
449
+ const ref = this.get(index);
450
+
451
+ // Adjust index to point to previous or next index (if reversed), so first
452
+ // next() call will traverse in proper direction.
453
+ if (!reverse) {
448
454
  index--;
455
+ } else {
456
+ index++;
449
457
  }
450
- // +1 so we can get the element we'll eventually point to so we can see if
451
- // we need to use a partial segment index.
452
- const ref = this.get(index + 1);
453
458
 
454
459
  let partialSegmentIndex = -1;
455
460
  if (ref && ref.hasPartialSegments()) {
@@ -471,7 +476,11 @@ shaka.media.SegmentIndex = class {
471
476
  }
472
477
  // Call to next() should move the partial segment, not the full
473
478
  // segment.
474
- index++;
479
+ if (reverse) {
480
+ index--;
481
+ } else {
482
+ index++;
483
+ }
475
484
  partialSegmentIndex = i - 1;
476
485
  break;
477
486
  }
@@ -9,6 +9,7 @@ goog.provide('shaka.media.SegmentPrefetch');
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
11
  goog.require('shaka.media.InitSegmentReference');
12
+ goog.require('shaka.media.SegmentIterator');
12
13
  goog.require('shaka.media.SegmentReference');
13
14
  goog.require('shaka.net.NetworkingEngine');
14
15
  goog.require('shaka.util.Uint8ArrayUtils');
@@ -25,17 +26,15 @@ shaka.media.SegmentPrefetch = class {
25
26
  * @param {number} prefetchLimit
26
27
  * @param {shaka.extern.Stream} stream
27
28
  * @param {shaka.media.SegmentPrefetch.FetchDispatcher} fetchDispatcher
29
+ * @param {boolean} reverse
28
30
  */
29
- constructor(prefetchLimit, stream, fetchDispatcher) {
31
+ constructor(prefetchLimit, stream, fetchDispatcher, reverse) {
30
32
  /** @private {number} */
31
33
  this.prefetchLimit_ = prefetchLimit;
32
34
 
33
35
  /** @private {shaka.extern.Stream} */
34
36
  this.stream_ = stream;
35
37
 
36
- /** @private {number} */
37
- this.prefetchPosTime_ = 0;
38
-
39
38
  /** @private {shaka.media.SegmentPrefetch.FetchDispatcher} */
40
39
  this.fetchDispatcher_ = fetchDispatcher;
41
40
 
@@ -52,6 +51,12 @@ shaka.media.SegmentPrefetch = class {
52
51
  * !shaka.media.SegmentPrefetchOperation>}
53
52
  */
54
53
  this.initSegmentPrefetchMap_ = new Map();
54
+
55
+ /** @private {?shaka.media.SegmentIterator} */
56
+ this.iterator_ = null;
57
+
58
+ /** @private {boolean} */
59
+ this.reverse_ = reverse;
55
60
  }
56
61
 
57
62
  /**
@@ -64,13 +69,6 @@ shaka.media.SegmentPrefetch = class {
64
69
  }
65
70
  }
66
71
 
67
- /**
68
- * @return {number}
69
- */
70
- getLastKnownPosition() {
71
- return this.prefetchPosTime_;
72
- }
73
-
74
72
  /**
75
73
  * Fetch next segments ahead of current time.
76
74
  *
@@ -87,21 +85,22 @@ shaka.media.SegmentPrefetch = class {
87
85
  shaka.log.debug(logPrefix, 'missing segmentIndex');
88
86
  return;
89
87
  }
90
- const maxTime = Math.max(currTime, this.prefetchPosTime_);
91
- const iterator = this.stream_.segmentIndex.getIteratorForTime(
92
- maxTime, /* allowNonIndepedent= */ true);
93
- if (!iterator) {
88
+ if (!this.iterator_) {
89
+ this.iterator_ = this.stream_.segmentIndex.getIteratorForTime(
90
+ currTime, /* allowNonIndepedent= */ true, this.reverse_);
91
+ }
92
+ if (!this.iterator_) {
93
+ shaka.log.debug(logPrefix, 'missing iterator');
94
94
  return;
95
95
  }
96
- let reference = iterator.next().value;
97
96
  if (skipFirst) {
98
- reference = iterator.next().value;
99
- }
100
- if (!reference) {
101
- return;
97
+ this.iterator_.next();
102
98
  }
103
- while (this.segmentPrefetchMap_.size < this.prefetchLimit_ &&
104
- reference != null) {
99
+ while (this.segmentPrefetchMap_.size < this.prefetchLimit_) {
100
+ const reference = this.iterator_.next().value;
101
+ if (!reference) {
102
+ break;
103
+ }
105
104
  // By default doesn't prefech preload partial segments when using
106
105
  // byterange
107
106
  let prefetchAllowed = true;
@@ -121,12 +120,10 @@ shaka.media.SegmentPrefetch = class {
121
120
  segmentPrefetchOperation.dispatchFetch(reference, this.stream_);
122
121
  this.segmentPrefetchMap_.set(reference, segmentPrefetchOperation);
123
122
  }
124
- this.prefetchPosTime_ = reference.startTime;
125
123
  if (this.stream_.fastSwitching && reference.isPartial() &&
126
124
  reference.isLastPartial()) {
127
125
  break;
128
126
  }
129
- reference = iterator.next().value;
130
127
  }
131
128
  this.clearInitSegments_();
132
129
  }
@@ -226,6 +223,11 @@ shaka.media.SegmentPrefetch = class {
226
223
  }
227
224
  }
228
225
 
226
+ /** */
227
+ resetPosition() {
228
+ this.iterator_ = null;
229
+ }
230
+
229
231
  /**
230
232
  * Clear all segment data.
231
233
  * @public
@@ -233,9 +235,9 @@ shaka.media.SegmentPrefetch = class {
233
235
  clearAll() {
234
236
  this.clearMap_(this.segmentPrefetchMap_);
235
237
  this.clearMap_(this.initSegmentPrefetchMap_);
238
+ this.resetPosition();
236
239
  const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_);
237
240
  shaka.log.debug(logPrefix, 'cleared all');
238
- this.prefetchPosTime_ = 0;
239
241
  }
240
242
 
241
243
  /**
@@ -262,6 +264,16 @@ shaka.media.SegmentPrefetch = class {
262
264
  }
263
265
  }
264
266
 
267
+ /**
268
+ * @param {boolean} reverse
269
+ */
270
+ setReverse(reverse) {
271
+ this.reverse_ = reverse;
272
+ if (this.iterator_) {
273
+ this.iterator_.setReverse(reverse);
274
+ }
275
+ }
276
+
265
277
  /**
266
278
  * Remove all init segments that don't have associated segments in
267
279
  * the segment prefetch map.
@@ -140,7 +140,7 @@ shaka.media.SegmentUtils = class {
140
140
  const SegmentUtils = shaka.media.SegmentUtils;
141
141
 
142
142
  const audioCodecs = [];
143
- const videoCodecs = [];
143
+ let videoCodecs = [];
144
144
 
145
145
  let hasAudio = false;
146
146
  let hasVideo = false;
@@ -360,6 +360,13 @@ shaka.media.SegmentUtils = class {
360
360
  })
361
361
 
362
362
  .box('colr', (box) => {
363
+ videoCodecs = videoCodecs.map((codec) => {
364
+ if (codec.startsWith('av01.')) {
365
+ return shaka.util.Mp4BoxParsers.updateAV1CodecWithCOLRBox(
366
+ codec, box.reader);
367
+ }
368
+ return codec;
369
+ });
363
370
  const {videoRange, colorGamut} =
364
371
  shaka.util.Mp4BoxParsers.parseCOLR(box.reader);
365
372
  realVideoRange = videoRange;