Skip to content

Commit 2ad1eff

Browse files
authored
feat(preload): Wait for prefetches when preloading (#7533)
Previously, the PreloadManager would consider a preload "finished" after a few major files like the manifest had been preloaded. It would start prefetching some segments, but wouldn't wait on it to notify the developer. This PR changes the PreloadManager so that PreloadManager.waitForFinish won't return until the prefetched segments have finished loading. Because of that, this also better surfaces errors thrown during segment prefetching, when preloading. Issue #7520
1 parent 6ab6a8f commit 2ad1eff

File tree

3 files changed

+43
-19
lines changed

3 files changed

+43
-19
lines changed

lib/media/preload_manager.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,10 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
607607
* Performs a final filtering of the manifest, and chooses the initial
608608
* variant.
609609
*
610+
* @return {!Promise}
610611
* @private
611612
*/
612-
chooseInitialVariantInner_() {
613+
async chooseInitialVariantInner_() {
613614
goog.asserts.assert(
614615
this.manifest_, 'The manifest should already be parsed.');
615616

@@ -655,13 +656,15 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
655656
this.abrManager_.setVariants(Array.from(adaptationSet.values()));
656657
const variant = this.abrManager_.chooseVariant();
657658
if (variant) {
659+
const promises = [];
658660
this.prefetchedVariant_ = variant;
659661
if (variant.video) {
660-
this.makePrefetchForStream_(variant.video, isLive);
662+
promises.push(this.prefetchStream_(variant.video, isLive));
661663
}
662664
if (variant.audio) {
663-
this.makePrefetchForStream_(variant.audio, isLive);
665+
promises.push(this.prefetchStream_(variant.audio, isLive));
664666
}
667+
await Promise.all(promises);
665668
}
666669
}
667670
}
@@ -672,7 +675,7 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
672675
* @return {!Promise}
673676
* @private
674677
*/
675-
async makePrefetchForStream_(stream, isLive) {
678+
async prefetchStream_(stream, isLive) {
676679
// Use the prefetch limit from the config if this is set, otherwise use 2.
677680
const prefetchLimit = this.config_.streaming.segmentPrefetchLimit || 2;
678681
const prefetch = new shaka.media.SegmentPrefetch(
@@ -700,13 +703,14 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
700703
if (isLive) {
701704
// Preload only the init segment for Live
702705
if (prefetchSegment.initSegmentReference) {
703-
prefetch.prefetchInitSegment(prefetchSegment.initSegmentReference);
706+
await prefetch.prefetchInitSegment(
707+
prefetchSegment.initSegmentReference);
704708
}
705709
} else {
706710
// Preload a segment, too... either the first segment, or the segment
707711
// that corresponds with this.startTime_, as appropriate.
708712
// Note: this method also preload the init segment
709-
prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
713+
await prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
710714
}
711715
}
712716
}

lib/media/segment_prefetch.js

+32-13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ goog.require('shaka.media.InitSegmentReference');
1212
goog.require('shaka.media.SegmentIterator');
1313
goog.require('shaka.media.SegmentReference');
1414
goog.require('shaka.net.NetworkingEngine');
15+
goog.require('shaka.util.Error');
1516
goog.require('shaka.util.Uint8ArrayUtils');
1617

1718

@@ -74,6 +75,7 @@ shaka.media.SegmentPrefetch = class {
7475
*
7576
* @param {number} currTime
7677
* @param {boolean=} skipFirst
78+
* @return {!Promise}
7779
* @public
7880
*/
7981
prefetchSegmentsByTime(currTime, skipFirst = false) {
@@ -83,19 +85,20 @@ shaka.media.SegmentPrefetch = class {
8385
const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_);
8486
if (!this.stream_.segmentIndex) {
8587
shaka.log.debug(logPrefix, 'missing segmentIndex');
86-
return;
88+
return Promise.resolve();
8789
}
8890
if (!this.iterator_) {
8991
this.iterator_ = this.stream_.segmentIndex.getIteratorForTime(
9092
currTime, /* allowNonIndepedent= */ true, this.reverse_);
9193
}
9294
if (!this.iterator_) {
9395
shaka.log.debug(logPrefix, 'missing iterator');
94-
return;
96+
return Promise.resolve();
9597
}
9698
if (skipFirst) {
9799
this.iterator_.next();
98100
}
101+
const promises = [];
99102
while (this.segmentPrefetchMap_.size < this.prefetchLimit_) {
100103
const reference = this.iterator_.next().value;
101104
if (!reference) {
@@ -115,22 +118,26 @@ shaka.media.SegmentPrefetch = class {
115118
prefetchAllowed = false;
116119
}
117120
if (prefetchAllowed && reference.initSegmentReference) {
118-
this.prefetchInitSegment(reference.initSegmentReference);
121+
promises.push(this.prefetchInitSegment(
122+
reference.initSegmentReference));
119123
}
120124
if (prefetchAllowed && !this.segmentPrefetchMap_.has(reference)) {
121125
const segmentPrefetchOperation =
122126
new shaka.media.SegmentPrefetchOperation(this.fetchDispatcher_);
123-
segmentPrefetchOperation.dispatchFetch(reference, this.stream_);
127+
promises.push(segmentPrefetchOperation.dispatchFetch(
128+
reference, this.stream_));
124129
this.segmentPrefetchMap_.set(reference, segmentPrefetchOperation);
125130
}
126131
}
127132
this.clearInitSegments_();
133+
return Promise.all(promises);
128134
}
129135

130136
/**
131137
* Fetch init segment.
132138
*
133139
* @param {!shaka.media.InitSegmentReference} initSegmentReference
140+
* @return {!Promise}
134141
*/
135142
prefetchInitSegment(initSegmentReference) {
136143
goog.asserts.assert(this.prefetchLimit_ > 0,
@@ -139,11 +146,11 @@ shaka.media.SegmentPrefetch = class {
139146
const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_);
140147
if (!this.stream_.segmentIndex) {
141148
shaka.log.debug(logPrefix, 'missing segmentIndex');
142-
return;
149+
return Promise.resolve();
143150
}
144151

145152
if (initSegmentReference.getSegmentData()) {
146-
return;
153+
return Promise.resolve();
147154
}
148155

149156
// init segments are ignored from the prefetch limit
@@ -152,14 +159,16 @@ shaka.media.SegmentPrefetch = class {
152159
return shaka.media.InitSegmentReference.equal(
153160
reference, initSegmentReference);
154161
});
155-
if (!someReference) {
156-
const segmentPrefetchOperation =
157-
new shaka.media.SegmentPrefetchOperation(this.fetchDispatcher_);
158-
segmentPrefetchOperation.dispatchFetch(
159-
initSegmentReference, this.stream_);
160-
this.initSegmentPrefetchMap_.set(
161-
initSegmentReference, segmentPrefetchOperation);
162+
if (someReference) {
163+
return Promise.resolve();
162164
}
165+
const segmentPrefetchOperation = new shaka.media.SegmentPrefetchOperation(
166+
this.fetchDispatcher_);
167+
const promise = segmentPrefetchOperation.dispatchFetch(
168+
initSegmentReference, this.stream_);
169+
this.initSegmentPrefetchMap_.set(
170+
initSegmentReference, segmentPrefetchOperation);
171+
return promise;
163172
}
164173

165174
/**
@@ -415,6 +424,7 @@ shaka.media.SegmentPrefetchOperation = class {
415424
* @param {!(shaka.media.SegmentReference|shaka.media.InitSegmentReference)}
416425
* reference
417426
* @param {!shaka.extern.Stream} stream
427+
* @return {!Promise}
418428
* @public
419429
*/
420430
dispatchFetch(reference, stream) {
@@ -433,6 +443,15 @@ shaka.media.SegmentPrefetchOperation = class {
433443
buffered = new Uint8Array(0);
434444
}
435445
});
446+
return this.operation_.promise.catch((e) => {
447+
// Ignore OPERATION_ABORTED errors.
448+
if (e instanceof shaka.util.Error &&
449+
e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
450+
return Promise.resolve();
451+
}
452+
// Continue to surface other errors.
453+
return Promise.reject(e);
454+
});
436455
}
437456

438457
/**

test/test/util/fake_segment_prefetch.js

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ shaka.test.FakeSegmentPrefetch = class {
6363
this.prefetchPosTime_ = reference.startTime;
6464
reference = iterator.next().value;
6565
}
66+
return Promise.resolve();
6667
}
6768

6869
/** @override */

0 commit comments

Comments
 (0)