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.
- package/dist/deps.js +1 -1
- package/dist/locales.js +35 -35
- package/dist/shaka-player.compiled.debug.js +364 -360
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +1190 -1189
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.debug.js +334 -330
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +1420 -1419
- package/dist/shaka-player.ui.map +3 -3
- package/externs/shaka/player.js +2 -2
- package/lib/cea/mp4_cea_parser.js +67 -31
- package/lib/dash/mpd_utils.js +3 -3
- package/lib/hls/hls_parser.js +66 -59
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/preload_manager.js +1 -1
- package/lib/media/segment_index.js +14 -5
- package/lib/media/segment_prefetch.js +37 -25
- package/lib/media/segment_utils.js +8 -1
- package/lib/media/streaming_engine.js +28 -14
- package/lib/player.js +1 -1
- package/lib/util/manifest_parser_utils.js +1 -0
- package/lib/util/mp4_box_parsers.js +36 -0
- package/package.json +1 -1
package/externs/shaka/player.js
CHANGED
|
@@ -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 {
|
|
593
|
-
* <b>DEPRECATED</b>: Use
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
let
|
|
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
|
-
//
|
|
180
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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());
|
package/lib/dash/mpd_utils.js
CHANGED
|
@@ -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
|
-
|
|
171
|
-
|
|
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;
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -466,7 +466,7 @@ shaka.hls.HlsParser = class {
|
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
const {segments, bandwidth} = this.createSegments_(
|
|
469
|
-
playlist,
|
|
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
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
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
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
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,
|
|
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 {
|
|
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,
|
|
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 =
|
|
3372
|
-
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,
|
|
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(
|
|
3944
|
-
|
|
3945
|
-
|
|
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
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
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(
|
|
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
|
-
|
|
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);
|
|
@@ -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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
99
|
-
}
|
|
100
|
-
if (!reference) {
|
|
101
|
-
return;
|
|
97
|
+
this.iterator_.next();
|
|
102
98
|
}
|
|
103
|
-
while (this.segmentPrefetchMap_.size < this.prefetchLimit_
|
|
104
|
-
|
|
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
|
-
|
|
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;
|