Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Codec switch reload - apply boundaries correctly #7700

Merged
merged 8 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 14 additions & 93 deletions lib/media/media_source_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,8 @@ shaka.media.MediaSourceEngine = class {
* @return {boolean}
*/
isStreamingAllowed() {
return this.streamingAllowed_ && !this.usingRemotePlayback_;
return this.streamingAllowed_ && !this.usingRemotePlayback_ &&
!this.reloadingMediaSource_;
}

/**
Expand Down Expand Up @@ -1859,11 +1860,13 @@ shaka.media.MediaSourceEngine = class {

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

// Enqueue a 'wait' operation onto each queue.
// This operation signals its readiness when it starts.
// When all wait operations are ready, the real operation takes place.
for (const contentType in this.sourceBuffers_) {
for (const contentType of contentTypes) {
const ready = new shaka.util.PublicPromise();
const operation = {
start: () => ready.resolve(),
Expand Down Expand Up @@ -1893,7 +1896,7 @@ shaka.media.MediaSourceEngine = class {
// assert at the end of destroy passes. In compiled mode, the queues
// are wiped in destroy.
if (goog.DEBUG) {
for (const contentType in this.sourceBuffers_) {
for (const contentType of contentTypes) {
if (this.queues_[contentType].length) {
goog.asserts.assert(
this.queues_[contentType].length == 1,
Expand All @@ -1910,7 +1913,7 @@ shaka.media.MediaSourceEngine = class {

if (goog.DEBUG) {
// If we did it correctly, nothing is updating.
for (const contentType in this.sourceBuffers_) {
for (const contentType of contentTypes) {
goog.asserts.assert(
this.sourceBuffers_[contentType].updating == false,
'SourceBuffers should not be updating after a blocking op!');
Expand All @@ -1930,7 +1933,7 @@ shaka.media.MediaSourceEngine = class {
null);
} finally {
// Unblock the queues.
for (const contentType in this.sourceBuffers_) {
for (const contentType of contentTypes) {
this.popFromQueue_(contentType);
}
}
Expand All @@ -1942,6 +1945,7 @@ shaka.media.MediaSourceEngine = class {
* @private
*/
popFromQueue_(contentType) {
goog.asserts.assert(this.queues_[contentType], 'Queue should exist');
// Remove the in-progress operation, which is now complete.
this.queues_[contentType].shift();
this.startOperation_(contentType);
Expand Down Expand Up @@ -2099,48 +2103,6 @@ shaka.media.MediaSourceEngine = class {
null);
}

/**
* Returns the source buffer parameters
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
* @return {?shaka.media.MediaSourceEngine.SourceBufferParams}
* @private
*/
getSourceBufferParams_(contentType) {
if (!this.sourceBuffers_[contentType]) {
return null;
}
return {
timestampOffset: this.sourceBuffers_[contentType].timestampOffset,
appendWindowStart: this.sourceBuffers_[contentType].appendWindowStart,
appendWindowEnd: this.sourceBuffers_[contentType].appendWindowEnd,
};
}

/**
* Restore source buffer parameters
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
* @param {?shaka.media.MediaSourceEngine.SourceBufferParams} params
* @private
*/
restoreSourceBufferParams_(contentType, params) {
if (!params) {
return;
}

if (!this.sourceBuffers_[contentType]) {
shaka.log.warning('Attempted to restore a non-existent source buffer');
return;
}

this.sourceBuffers_[contentType].timestampOffset =
params.timestampOffset;
// `end` needs to be set before `start`
this.sourceBuffers_[contentType].appendWindowEnd =
params.appendWindowEnd;
this.sourceBuffers_[contentType].appendWindowStart =
params.appendWindowStart;
}

/**
* Resets the MediaSource and re-adds source buffers due to codec mismatch
*
Expand All @@ -2149,7 +2111,6 @@ shaka.media.MediaSourceEngine = class {
* @private
*/
async reset_(streamsByType) {
const Functional = shaka.util.Functional;
const ContentType = shaka.util.ManifestParserUtils.ContentType;
this.reloadingMediaSource_ = true;
this.needSplitMuxedContent_ = false;
Expand All @@ -2171,36 +2132,11 @@ shaka.media.MediaSourceEngine = class {
try {
this.eventManager_.removeAll();

const cleanup = [];
for (const contentType in this.transmuxers_) {
cleanup.push(this.transmuxers_[contentType].destroy());
}
for (const contentType in this.queues_) {
// Make a local copy of the queue and the first item.
const q = this.queues_[contentType];
const inProgress = q[0];

// Drop everything else out of the original queue.
this.queues_[contentType] = q.slice(0, 1);

// We will wait for this item to complete/fail.
if (inProgress) {
cleanup.push(inProgress.p.catch(Functional.noop));
}

// The rest will be rejected silently if possible.
for (const item of q.slice(1)) {
item.p.reject(shaka.util.Destroyer.destroyedError());
}
}
for (const contentType in this.sourceBuffers_) {
const sourceBuffer = this.sourceBuffers_[contentType];
try {
this.mediaSource_.removeSourceBuffer(sourceBuffer);
} catch (e) {}
this.transmuxers_[contentType].destroy();
}
await Promise.all(cleanup);
this.transmuxers_ = {};
this.sourceBuffers_ = {};

const previousDuration = this.mediaSource_.duration;
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
Expand Down Expand Up @@ -2231,23 +2167,17 @@ shaka.media.MediaSourceEngine = class {
onSourceBufferAdded);

for (const contentType of streamsByType.keys()) {
const previousParams = this.getSourceBufferParams_(contentType);
const stream = streamsByType.get(contentType);
// eslint-disable-next-line no-await-in-loop
await this.initSourceBuffer_(contentType, stream, stream.codecs);
if (this.needSplitMuxedContent_) {
this.queues_[ContentType.AUDIO] = [];
this.queues_[ContentType.VIDEO] = [];
} else {
this.queues_[contentType] = [];
}

this.restoreSourceBufferParams_(contentType, previousParams);
}
const audio = streamsByType.get(ContentType.AUDIO);
if (audio && audio.isAudioMuxedInVideo) {
this.needSplitMuxedContent_ = true;
}
if (this.needSplitMuxedContent_ && !this.queues_[ContentType.AUDIO]) {
this.queues_[ContentType.AUDIO] = [];
}

// Fake a seek to catchup the playhead.
this.video_.currentTime = currentTime;
Expand Down Expand Up @@ -2540,12 +2470,3 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
* Called when an embedded 'emsg' box should trigger a manifest update.
*/
shaka.media.MediaSourceEngine.PlayerInterface;

/**
* @typedef {{
* timestampOffset: number,
* appendWindowStart: number,
* appendWindowEnd: number
* }}
*/
shaka.media.MediaSourceEngine.SourceBufferParams;
4 changes: 4 additions & 0 deletions lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2021,6 +2021,10 @@ shaka.media.StreamingEngine = class {
// Then clear our cache of the last init segment, since MSE will be
// reloaded and no init segment will be there post-reload.
otherState.lastInitSegmentReference = null;
// Clear cache of append window start and end, since they will need
// to be reapplied post-reload by streaming engine.
otherState.lastAppendWindowStart = null;
otherState.lastAppendWindowEnd = null;
tykus160 marked this conversation as resolved.
Show resolved Hide resolved
// Now force the existing buffer to be cleared. It is not necessary
// to perform the MSE clear operation, but this has the side-effect
// that our state for that stream will then match MSE's post-reload
Expand Down
Loading