diff --git a/lib/media/segment_prefetch.js b/lib/media/segment_prefetch.js index e52567b432..1c64607bd7 100644 --- a/lib/media/segment_prefetch.js +++ b/lib/media/segment_prefetch.js @@ -40,8 +40,9 @@ shaka.media.SegmentPrefetch = class { this.fetchDispatcher_ = fetchDispatcher; /** - * @private {!Map.} + * @private {!Map.< + * !(shaka.media.SegmentReference|shaka.media.InitSegmentReference), + * !shaka.media.SegmentPrefetchOperation>} */ this.segmentPrefetchMap_ = new Map(); } @@ -50,9 +51,10 @@ shaka.media.SegmentPrefetch = class { * Fetch next segments ahead of current segment. * * @param {(!shaka.media.SegmentReference)} startReference + * @param {boolean=} skipFirst * @public */ - prefetchSegments(startReference) { + prefetchSegments(startReference, skipFirst = false) { goog.asserts.assert(this.prefetchLimit_ > 0, 'SegmentPrefetch can not be used when prefetchLimit <= 0.'); @@ -68,6 +70,14 @@ shaka.media.SegmentPrefetch = class { return; } let reference = startReference; + if (skipFirst) { + reference = iterator.next().value; + if (reference && + reference.startTime == startReference.startTime && + reference.endTime == startReference.endTime) { + reference = null; + } + } while (this.segmentPrefetchMap_.size < this.prefetchLimit_ && reference != null) { // By default doesn't prefech preload partial segments when using @@ -91,9 +101,38 @@ shaka.media.SegmentPrefetch = class { } } + /** + * Fetch init segment. + * + * @param {!shaka.media.InitSegmentReference} initSegmentReference + * @public + */ + prefetchInitSegment(initSegmentReference) { + goog.asserts.assert(this.prefetchLimit_ > 0, + 'SegmentPrefetch can not be used when prefetchLimit <= 0.'); + + const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_); + if (!this.stream_.segmentIndex) { + shaka.log.info(logPrefix, 'missing segmentIndex'); + return; + } + + if (this.segmentPrefetchMap_.size < this.prefetchLimit_) { + if (!this.segmentPrefetchMap_.has(initSegmentReference)) { + const segmentPrefetchOperation = + new shaka.media.SegmentPrefetchOperation(this.fetchDispatcher_); + segmentPrefetchOperation.dispatchFetch( + initSegmentReference, this.stream_); + this.segmentPrefetchMap_.set( + initSegmentReference, segmentPrefetchOperation); + } + } + } + /** * Get the result of prefetched segment if already exists. - * @param {(!shaka.media.SegmentReference)} reference + * @param {!(shaka.media.SegmentReference|shaka.media.InitSegmentReference)} + * reference * @param {?function(BufferSource):!Promise=} streamDataCallback * @return {?shaka.net.NetworkingEngine.PendingRequest} op * @public @@ -101,8 +140,6 @@ shaka.media.SegmentPrefetch = class { getPrefetchedSegment(reference, streamDataCallback) { goog.asserts.assert(this.prefetchLimit_ > 0, 'SegmentPrefetch can not be used when prefetchLimit <= 0.'); - goog.asserts.assert(reference instanceof shaka.media.SegmentReference, - 'getPrefetchedSegment is only used for shaka.media.SegmentReference.'); const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_); @@ -112,16 +149,30 @@ shaka.media.SegmentPrefetch = class { segmentPrefetchOperation.setStreamDataCallback(streamDataCallback); } this.segmentPrefetchMap_.delete(reference); - shaka.log.debug( - logPrefix, - 'reused prefetched segment at time:', reference.startTime, - 'mapSize', this.segmentPrefetchMap_.size); + if (reference instanceof shaka.media.SegmentReference) { + shaka.log.debug( + logPrefix, + 'reused prefetched segment at time:', reference.startTime, + 'mapSize', this.segmentPrefetchMap_.size); + } else { + shaka.log.debug( + logPrefix, + 'reused prefetched init segment at time, mapSize', + this.segmentPrefetchMap_.size); + } return segmentPrefetchOperation.getOperation(); } else { - shaka.log.debug( - logPrefix, - 'missed segment at time:', reference.startTime, - 'mapSize', this.segmentPrefetchMap_.size); + if (reference instanceof shaka.media.SegmentReference) { + shaka.log.debug( + logPrefix, + 'reused prefetched segment at time:', reference.startTime, + 'mapSize', this.segmentPrefetchMap_.size); + } else { + shaka.log.debug( + logPrefix, + 'reused prefetched init segment at time, mapSize', + this.segmentPrefetchMap_.size); + } return null; } } @@ -177,7 +228,8 @@ shaka.media.SegmentPrefetch = class { /** * Remove a segment from prefetch map and abort it. - * @param {(!shaka.media.SegmentReference)} reference + * @param {!(shaka.media.SegmentReference|shaka.media.InitSegmentReference)} + * reference * @private */ abortPrefetchedSegment_(reference) { @@ -186,9 +238,13 @@ shaka.media.SegmentPrefetch = class { this.segmentPrefetchMap_.delete(reference); if (segmentPrefetchOperation) { segmentPrefetchOperation.abort(); - shaka.log.info( - logPrefix, - 'pop and abort prefetched segment at time:', reference.startTime); + if (reference instanceof shaka.media.SegmentReference) { + shaka.log.info( + logPrefix, + 'pop and abort prefetched segment at time:', reference.startTime); + } else { + shaka.log.info(logPrefix, 'pop and abort prefetched init segment'); + } } } @@ -224,8 +280,8 @@ shaka.media.SegmentPrefetchOperation = class { /** * Fetch a segments * - * @param {!shaka.media.SegmentReference} - * reference + * @param {!(shaka.media.SegmentReference|shaka.media.InitSegmentReference)} + * reference * @param {!shaka.extern.Stream} stream * @public */ diff --git a/lib/media/streaming_engine.js b/lib/media/streaming_engine.js index 47802a74d0..0f39b047da 100644 --- a/lib/media/streaming_engine.js +++ b/lib/media/streaming_engine.js @@ -1171,6 +1171,12 @@ shaka.media.StreamingEngine = class { } if (mediaState.segmentPrefetch && mediaState.segmentIterator) { + const initSegmentReference = reference.initSegmentReference; + if (initSegmentReference && (!mediaState.lastSegmentReference || + !shaka.media.InitSegmentReference.equal( + initSegmentReference, mediaState.lastInitSegmentReference))) { + mediaState.segmentPrefetch.prefetchInitSegment(initSegmentReference); + } mediaState.segmentPrefetch.prefetchSegments(reference); } @@ -1383,7 +1389,8 @@ shaka.media.StreamingEngine = class { /* isChunkedData= */ true); if (mediaState.segmentPrefetch && mediaState.segmentIterator) { - mediaState.segmentPrefetch.prefetchSegments(reference); + mediaState.segmentPrefetch.prefetchSegments( + reference, /* skipFirst= */ true); } } }; @@ -1417,7 +1424,8 @@ shaka.media.StreamingEngine = class { } if (mediaState.segmentPrefetch && mediaState.segmentIterator) { - mediaState.segmentPrefetch.prefetchSegments(reference); + mediaState.segmentPrefetch.prefetchSegments( + reference, /* skipFirst= */ true); } } else { if (this.config_.lowLatencyMode && !isReadableStreamSupported) { @@ -1780,8 +1788,7 @@ shaka.media.StreamingEngine = class { shaka.log.v1(logPrefix, 'fetching init segment'); const fetchInit = - this.fetch_(mediaState, reference.initSegmentReference, - /* streamDataCallback= */ undefined, /* isInit= */ true); + this.fetch_(mediaState, reference.initSegmentReference); const append = async () => { try { const initSegment = await fetchInit; @@ -2185,13 +2192,12 @@ shaka.media.StreamingEngine = class { * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)} * reference * @param {?function(BufferSource):!Promise=} streamDataCallback - * @param {boolean=} isInit * * @return {!Promise.} * @private * @suppress {strictMissingProperties} */ - async fetch_(mediaState, reference, streamDataCallback, isInit) { + async fetch_(mediaState, reference, streamDataCallback) { if (reference instanceof shaka.media.InitSegmentReference) { const segmentData = reference.getSegmentData(); if (segmentData) { @@ -2199,17 +2205,13 @@ shaka.media.StreamingEngine = class { } } let op = null; - if ( - mediaState.segmentPrefetch && - reference instanceof shaka.media.SegmentReference - ) { + if (mediaState.segmentPrefetch) { op = mediaState.segmentPrefetch.getPrefetchedSegment( reference, streamDataCallback); } if (!op) { op = this.dispatchFetch_( - reference, mediaState.stream, streamDataCallback, isInit, - ); + reference, mediaState.stream, streamDataCallback); } mediaState.operation = op; @@ -2231,18 +2233,17 @@ shaka.media.StreamingEngine = class { * @param {(!shaka.media.InitSegmentReference|!shaka.media.SegmentReference)} * reference * @param {?function(BufferSource):!Promise=} streamDataCallback - * @param {boolean=} isInit * * @return {!shaka.net.NetworkingEngine.PendingRequest} * @private */ - dispatchFetch_(reference, stream, streamDataCallback, isInit) { + dispatchFetch_(reference, stream, streamDataCallback) { const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT; - const type = isInit ? - shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT : - shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT; const segment = reference instanceof shaka.media.SegmentReference ? reference : undefined; + const type = segment ? + shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT : + shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT; const request = shaka.util.Networking.createSegmentRequest( reference.getUris(), reference.startByte, diff --git a/test/test/util/fake_segment_prefetch.js b/test/test/util/fake_segment_prefetch.js index c7ada36947..53324db17b 100644 --- a/test/test/util/fake_segment_prefetch.js +++ b/test/test/util/fake_segment_prefetch.js @@ -78,4 +78,7 @@ shaka.test.FakeSegmentPrefetch = class { } return null; } + + /** @override */ + prefetchInitSegment(reference) {} };