Skip to content

Commit 8dd9d20

Browse files
authored
fix: Codec switch reload - apply boundaries correctly (#7700)
Fixes #7595
1 parent 6e382ce commit 8dd9d20

File tree

2 files changed

+32
-97
lines changed

2 files changed

+32
-97
lines changed

lib/media/media_source_engine.js

+23-96
Original file line numberDiff line numberDiff line change
@@ -466,12 +466,12 @@ shaka.media.MediaSourceEngine = class {
466466
cleanup.push(this.textEngine_.destroy());
467467
}
468468

469+
await Promise.all(cleanup);
470+
469471
for (const contentType in this.transmuxers_) {
470-
cleanup.push(this.transmuxers_[contentType].destroy());
472+
this.transmuxers_[contentType].destroy();
471473
}
472474

473-
474-
await Promise.all(cleanup);
475475
if (this.eventManager_) {
476476
this.eventManager_.release();
477477
this.eventManager_ = null;
@@ -692,7 +692,8 @@ shaka.media.MediaSourceEngine = class {
692692
* @return {boolean}
693693
*/
694694
isStreamingAllowed() {
695-
return this.streamingAllowed_ && !this.usingRemotePlayback_;
695+
return this.streamingAllowed_ && !this.usingRemotePlayback_ &&
696+
!this.reloadingMediaSource_;
696697
}
697698

698699
/**
@@ -1859,11 +1860,13 @@ shaka.media.MediaSourceEngine = class {
18591860

18601861
/** @type {!Array.<!shaka.util.PublicPromise>} */
18611862
const allWaiters = [];
1863+
/** @type {!Array.<!shaka.util.ManifestParserUtils.ContentType>} */
1864+
const contentTypes = Object.keys(this.sourceBuffers_);
18621865

18631866
// Enqueue a 'wait' operation onto each queue.
18641867
// This operation signals its readiness when it starts.
18651868
// When all wait operations are ready, the real operation takes place.
1866-
for (const contentType in this.sourceBuffers_) {
1869+
for (const contentType of contentTypes) {
18671870
const ready = new shaka.util.PublicPromise();
18681871
const operation = {
18691872
start: () => ready.resolve(),
@@ -1893,7 +1896,7 @@ shaka.media.MediaSourceEngine = class {
18931896
// assert at the end of destroy passes. In compiled mode, the queues
18941897
// are wiped in destroy.
18951898
if (goog.DEBUG) {
1896-
for (const contentType in this.sourceBuffers_) {
1899+
for (const contentType of contentTypes) {
18971900
if (this.queues_[contentType].length) {
18981901
goog.asserts.assert(
18991902
this.queues_[contentType].length == 1,
@@ -1910,7 +1913,7 @@ shaka.media.MediaSourceEngine = class {
19101913

19111914
if (goog.DEBUG) {
19121915
// If we did it correctly, nothing is updating.
1913-
for (const contentType in this.sourceBuffers_) {
1916+
for (const contentType of contentTypes) {
19141917
goog.asserts.assert(
19151918
this.sourceBuffers_[contentType].updating == false,
19161919
'SourceBuffers should not be updating after a blocking op!');
@@ -1930,7 +1933,7 @@ shaka.media.MediaSourceEngine = class {
19301933
null);
19311934
} finally {
19321935
// Unblock the queues.
1933-
for (const contentType in this.sourceBuffers_) {
1936+
for (const contentType of contentTypes) {
19341937
this.popFromQueue_(contentType);
19351938
}
19361939
}
@@ -1942,6 +1945,7 @@ shaka.media.MediaSourceEngine = class {
19421945
* @private
19431946
*/
19441947
popFromQueue_(contentType) {
1948+
goog.asserts.assert(this.queues_[contentType], 'Queue should exist');
19451949
// Remove the in-progress operation, which is now complete.
19461950
this.queues_[contentType].shift();
19471951
this.startOperation_(contentType);
@@ -2099,48 +2103,6 @@ shaka.media.MediaSourceEngine = class {
20992103
null);
21002104
}
21012105

2102-
/**
2103-
* Returns the source buffer parameters
2104-
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
2105-
* @return {?shaka.media.MediaSourceEngine.SourceBufferParams}
2106-
* @private
2107-
*/
2108-
getSourceBufferParams_(contentType) {
2109-
if (!this.sourceBuffers_[contentType]) {
2110-
return null;
2111-
}
2112-
return {
2113-
timestampOffset: this.sourceBuffers_[contentType].timestampOffset,
2114-
appendWindowStart: this.sourceBuffers_[contentType].appendWindowStart,
2115-
appendWindowEnd: this.sourceBuffers_[contentType].appendWindowEnd,
2116-
};
2117-
}
2118-
2119-
/**
2120-
* Restore source buffer parameters
2121-
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
2122-
* @param {?shaka.media.MediaSourceEngine.SourceBufferParams} params
2123-
* @private
2124-
*/
2125-
restoreSourceBufferParams_(contentType, params) {
2126-
if (!params) {
2127-
return;
2128-
}
2129-
2130-
if (!this.sourceBuffers_[contentType]) {
2131-
shaka.log.warning('Attempted to restore a non-existent source buffer');
2132-
return;
2133-
}
2134-
2135-
this.sourceBuffers_[contentType].timestampOffset =
2136-
params.timestampOffset;
2137-
// `end` needs to be set before `start`
2138-
this.sourceBuffers_[contentType].appendWindowEnd =
2139-
params.appendWindowEnd;
2140-
this.sourceBuffers_[contentType].appendWindowStart =
2141-
params.appendWindowStart;
2142-
}
2143-
21442106
/**
21452107
* Resets the MediaSource and re-adds source buffers due to codec mismatch
21462108
*
@@ -2149,7 +2111,6 @@ shaka.media.MediaSourceEngine = class {
21492111
* @private
21502112
*/
21512113
async reset_(streamsByType) {
2152-
const Functional = shaka.util.Functional;
21532114
const ContentType = shaka.util.ManifestParserUtils.ContentType;
21542115
this.reloadingMediaSource_ = true;
21552116
this.needSplitMuxedContent_ = false;
@@ -2171,36 +2132,19 @@ shaka.media.MediaSourceEngine = class {
21712132
try {
21722133
this.eventManager_.removeAll();
21732134

2174-
const cleanup = [];
21752135
for (const contentType in this.transmuxers_) {
2176-
cleanup.push(this.transmuxers_[contentType].destroy());
2177-
}
2178-
for (const contentType in this.queues_) {
2179-
// Make a local copy of the queue and the first item.
2180-
const q = this.queues_[contentType];
2181-
const inProgress = q[0];
2182-
2183-
// Drop everything else out of the original queue.
2184-
this.queues_[contentType] = q.slice(0, 1);
2185-
2186-
// We will wait for this item to complete/fail.
2187-
if (inProgress) {
2188-
cleanup.push(inProgress.p.catch(Functional.noop));
2189-
}
2190-
2191-
// The rest will be rejected silently if possible.
2192-
for (const item of q.slice(1)) {
2193-
item.p.reject(shaka.util.Destroyer.destroyedError());
2194-
}
2136+
this.transmuxers_[contentType].destroy();
21952137
}
21962138
for (const contentType in this.sourceBuffers_) {
21972139
const sourceBuffer = this.sourceBuffers_[contentType];
21982140
try {
21992141
this.mediaSource_.removeSourceBuffer(sourceBuffer);
2200-
} catch (e) {}
2142+
} catch (e) {
2143+
shaka.log.debug('Exception on removeSourceBuffer', e);
2144+
}
22012145
}
2202-
await Promise.all(cleanup);
22032146
this.transmuxers_ = {};
2147+
this.sourceBuffers_ = {};
22042148

22052149
const previousDuration = this.mediaSource_.duration;
22062150
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
@@ -2231,23 +2175,17 @@ shaka.media.MediaSourceEngine = class {
22312175
onSourceBufferAdded);
22322176

22332177
for (const contentType of streamsByType.keys()) {
2234-
const previousParams = this.getSourceBufferParams_(contentType);
22352178
const stream = streamsByType.get(contentType);
22362179
// eslint-disable-next-line no-await-in-loop
22372180
await this.initSourceBuffer_(contentType, stream, stream.codecs);
2238-
if (this.needSplitMuxedContent_) {
2239-
this.queues_[ContentType.AUDIO] = [];
2240-
this.queues_[ContentType.VIDEO] = [];
2241-
} else {
2242-
this.queues_[contentType] = [];
2243-
}
2244-
2245-
this.restoreSourceBufferParams_(contentType, previousParams);
22462181
}
22472182
const audio = streamsByType.get(ContentType.AUDIO);
22482183
if (audio && audio.isAudioMuxedInVideo) {
22492184
this.needSplitMuxedContent_ = true;
22502185
}
2186+
if (this.needSplitMuxedContent_ && !this.queues_[ContentType.AUDIO]) {
2187+
this.queues_[ContentType.AUDIO] = [];
2188+
}
22512189

22522190
// Fake a seek to catchup the playhead.
22532191
this.video_.currentTime = currentTime;
@@ -2393,13 +2331,12 @@ shaka.media.MediaSourceEngine = class {
23932331
* Returns true if it's necessary codec switch to load the new stream.
23942332
*
23952333
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
2396-
* @param {shaka.extern.Stream} stream
23972334
* @param {string} refMimeType
23982335
* @param {string} refCodecs
23992336
* @return {boolean}
24002337
* @private
24012338
*/
2402-
isCodecSwitchNecessary_(contentType, stream, refMimeType, refCodecs) {
2339+
isCodecSwitchNecessary_(contentType, refMimeType, refCodecs) {
24032340
if (contentType == shaka.util.ManifestParserUtils.ContentType.TEXT) {
24042341
return false;
24052342
}
@@ -2443,13 +2380,12 @@ shaka.media.MediaSourceEngine = class {
24432380
* new stream.
24442381
*
24452382
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
2446-
* @param {shaka.extern.Stream} stream
24472383
* @param {string} mimeType
24482384
* @param {string} codecs
24492385
* @return {boolean}
24502386
*/
2451-
isResetMediaSourceNecessary(contentType, stream, mimeType, codecs) {
2452-
if (!this.isCodecSwitchNecessary_(contentType, stream, mimeType, codecs)) {
2387+
isResetMediaSourceNecessary(contentType, mimeType, codecs) {
2388+
if (!this.isCodecSwitchNecessary_(contentType, mimeType, codecs)) {
24532389
return false;
24542390
}
24552391

@@ -2540,12 +2476,3 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
25402476
* Called when an embedded 'emsg' box should trigger a manifest update.
25412477
*/
25422478
shaka.media.MediaSourceEngine.PlayerInterface;
2543-
2544-
/**
2545-
* @typedef {{
2546-
* timestampOffset: number,
2547-
* appendWindowStart: number,
2548-
* appendWindowEnd: number
2549-
* }}
2550-
*/
2551-
shaka.media.MediaSourceEngine.SourceBufferParams;

lib/media/streaming_engine.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -2007,7 +2007,7 @@ shaka.media.StreamingEngine = class {
20072007
const isResetMediaSourceNecessary =
20082008
mediaState.lastCodecs && mediaState.lastMimeType &&
20092009
this.playerInterface_.mediaSourceEngine.isResetMediaSourceNecessary(
2010-
mediaState.type, mediaState.stream, mimeType, fullCodecs);
2010+
mediaState.type, mimeType, fullCodecs);
20112011
if (isResetMediaSourceNecessary) {
20122012
let otherState = null;
20132013
if (mediaState.type === ContentType.VIDEO) {
@@ -2021,6 +2021,10 @@ shaka.media.StreamingEngine = class {
20212021
// Then clear our cache of the last init segment, since MSE will be
20222022
// reloaded and no init segment will be there post-reload.
20232023
otherState.lastInitSegmentReference = null;
2024+
// Clear cache of append window start and end, since they will need
2025+
// to be reapplied post-reload by streaming engine.
2026+
otherState.lastAppendWindowStart = null;
2027+
otherState.lastAppendWindowEnd = null;
20242028
// Now force the existing buffer to be cleared. It is not necessary
20252029
// to perform the MSE clear operation, but this has the side-effect
20262030
// that our state for that stream will then match MSE's post-reload
@@ -2725,6 +2729,8 @@ shaka.media.StreamingEngine = class {
27252729
const audioMediaState = this.mediaStates_.get(ContentType.AUDIO);
27262730
if (audioMediaState) {
27272731
audioMediaState.lastInitSegmentReference = null;
2732+
audioMediaState.lastAppendWindowStart = null;
2733+
audioMediaState.lastAppendWindowEnd = null;
27282734
if (clearBuffer) {
27292735
this.forceClearBuffer_(audioMediaState);
27302736
}
@@ -2736,6 +2742,8 @@ shaka.media.StreamingEngine = class {
27362742
const videoMediaState = this.mediaStates_.get(ContentType.VIDEO);
27372743
if (videoMediaState) {
27382744
videoMediaState.lastInitSegmentReference = null;
2745+
videoMediaState.lastAppendWindowStart = null;
2746+
videoMediaState.lastAppendWindowEnd = null;
27392747
if (clearBuffer) {
27402748
this.forceClearBuffer_(videoMediaState);
27412749
}

0 commit comments

Comments
 (0)