Skip to content

Commit

Permalink
Error and switch on SourceBuffer append requests for non-existing tra…
Browse files Browse the repository at this point in the history
…cks (#5485)

* Error and switch on SourceBuffer append requests for non-existing tracks
(muxed "audiovideo" append over unmuxed "video" and "audio" SourceBuffer)
Related to #1510

* Handle queued appends for pending source buffers
  • Loading branch information
robwalch authored May 27, 2023
1 parent 75d9d2d commit 11b11de
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 46 deletions.
7 changes: 6 additions & 1 deletion src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,10 +715,15 @@ class AudioStreamController
this.state = State.IDLE;
}
break;
case ErrorDetails.BUFFER_APPEND_ERROR:
case ErrorDetails.BUFFER_FULL_ERROR:
if (!data.parent || data.parent !== 'audio') {
return;
}
if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
this.resetLoadingState();
return;
}
if (this.reduceLengthAndFlushBuffer(data)) {
this.bufferedTrack = null;
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
Expand Down Expand Up @@ -862,7 +867,7 @@ class AudioStreamController
this.hls.trigger(Events.BUFFER_APPENDING, segment);
}
// trigger handler right now
this.tick();
this.tickImmediate();
}

protected loadFragment(
Expand Down
18 changes: 6 additions & 12 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ export default class BaseStreamController
return data;
})
.then((data: FragLoadedData) => {
const { fragCurrent, hls, levels } = this;
const { levels } = this;
if (!levels) {
throw new Error('init load aborted, missing levels');
}
Expand All @@ -519,16 +519,6 @@ export default class BaseStreamController
frag.data = new Uint8Array(data.payload);
stats.parsing.start = stats.buffering.start = self.performance.now();
stats.parsing.end = stats.buffering.end = self.performance.now();

// Silence FRAG_BUFFERED event if fragCurrent is null
if (data.frag === fragCurrent) {
hls.trigger(Events.FRAG_BUFFERED, {
stats,
frag: fragCurrent,
part: null,
id: frag.type,
});
}
this.tick();
})
.catch((reason) => {
Expand Down Expand Up @@ -1522,13 +1512,17 @@ export default class BaseStreamController
this.resetFragmentErrors(filterType);
if (retryCount < retryConfig.maxNumRetry) {
// Network retry is skipped when level switch is preferred
if (!gapTagEncountered) {
if (
!gapTagEncountered &&
action !== NetworkErrorAction.RemoveAlternatePermanently
) {
errorAction.resolved = true;
}
} else {
logger.warn(
`${data.details} reached or exceeded max retry (${retryCount})`
);
return;
}
} else {
this.state = State.ERROR;
Expand Down
20 changes: 8 additions & 12 deletions src/controller/buffer-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ export default class BufferController implements ComponentAPI {
},
};

operationQueue.append(operation, type);
operationQueue.append(operation, type, !!this.pendingTracks[type]);
}

protected onBufferAppending(
Expand Down Expand Up @@ -412,10 +412,6 @@ export default class BufferController implements ComponentAPI {
},
onError: (err) => {
// in case any error occured while appending, put back segment in segments table
logger.error(
`[buffer-controller]: Error encountered while trying to append to the ${type} SourceBuffer`,
err
);
const event = {
type: ErrorTypes.MEDIA_ERROR,
parent: frag.type,
Expand Down Expand Up @@ -448,7 +444,7 @@ export default class BufferController implements ComponentAPI {
hls.trigger(Events.ERROR, event);
},
};
operationQueue.append(operation, type);
operationQueue.append(operation, type, !!this.pendingTracks[type]);
}

protected onBufferFlushing(
Expand Down Expand Up @@ -896,13 +892,13 @@ export default class BufferController implements ComponentAPI {

// This method must result in an updateend event; if append is not called, _onSBUpdateEnd must be called manually
private appendExecutor(data: Uint8Array, type: SourceBufferName) {
const { operationQueue, sourceBuffer } = this;
const sb = sourceBuffer[type];
const sb = this.sourceBuffer[type];
if (!sb) {
logger.warn(
`[buffer-controller]: Attempting to append to the ${type} SourceBuffer, but it does not exist`
);
operationQueue.shiftAndExecuteNext(type);
if (!this.pendingTracks[type]) {
throw new Error(
`Attempting to append to the ${type} SourceBuffer, but it does not exist`
);
}
return;
}

Expand Down
22 changes: 12 additions & 10 deletions src/controller/buffer-operation-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ export default class BufferOperationQueue {
this.buffers = sourceBufferReference;
}

public append(operation: BufferOperation, type: SourceBufferName) {
public append(
operation: BufferOperation,
type: SourceBufferName,
pending?: boolean
) {
const queue = this.queues[type];
queue.push(operation);
if (queue.length === 1 && this.buffers[type]) {
if (queue.length === 1 && !pending) {
this.executeNext(type);
}
}
Expand Down Expand Up @@ -49,25 +53,23 @@ export default class BufferOperationQueue {
}

public executeNext(type: SourceBufferName) {
const { buffers, queues } = this;
const sb = buffers[type];
const queue = queues[type];
const queue = this.queues[type];
if (queue.length) {
const operation: BufferOperation = queue[0];
try {
// Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations
// which do not end with this event must call _onSBUpdateEnd manually
operation.execute();
} catch (e) {
} catch (error) {
logger.warn(
'[buffer-operation-queue]: Unhandled exception executing the current operation'
`[buffer-operation-queue]: Exception executing "${type}" SourceBuffer operation: ${error}`
);
operation.onError(e);
operation.onError(error);

// Only shift the current operation off, otherwise the updateend handler will do this for us
const sb = this.buffers[type];
if (!sb?.updating) {
queue.shift();
this.executeNext(type);
this.shiftAndExecuteNext(type);
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/controller/error-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ export default class ErrorController implements NetworkComponentAPI {
this.penalizedRenditions = {};
}

startLoad(startPosition: number): void {
startLoad(startPosition: number): void {}

stopLoad(): void {
this.playlistError = 0;
}

stopLoad(): void {}

private getVariantLevelIndex(frag: Fragment | undefined): number {
return frag?.type === PlaylistLevelType.MAIN
? frag.level
Expand Down Expand Up @@ -210,14 +210,14 @@ export default class ErrorController implements NetworkComponentAPI {
return;
case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
case ErrorDetails.REMUX_ALLOC_ERROR:
case ErrorDetails.BUFFER_APPEND_ERROR:
data.errorAction = this.getLevelSwitchAction(
data,
data.level ?? hls.loadLevel
);
return;
case ErrorDetails.INTERNAL_EXCEPTION:
case ErrorDetails.BUFFER_APPENDING_ERROR:
case ErrorDetails.BUFFER_APPEND_ERROR:
case ErrorDetails.BUFFER_FULL_ERROR:
case ErrorDetails.LEVEL_SWITCH_ERROR:
case ErrorDetails.BUFFER_STALLED_ERROR:
Expand Down
4 changes: 2 additions & 2 deletions src/controller/level-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default class LevelController extends BasePlaylistController {
super.destroy();
}

public startLoad(): void {
public stopLoad(): void {
const levels = this._levels;

// clean up live level details to force reload them, and reset load errors
Expand All @@ -84,7 +84,7 @@ export default class LevelController extends BasePlaylistController {
level.fragmentError = 0;
});

super.startLoad();
super.stopLoad();
}

private resetLevels() {
Expand Down
7 changes: 6 additions & 1 deletion src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,10 +905,15 @@ export default class StreamController
this.state = State.IDLE;
}
break;
case ErrorDetails.BUFFER_APPEND_ERROR:
case ErrorDetails.BUFFER_FULL_ERROR:
if (!data.parent || data.parent !== 'main') {
return;
}
if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
this.resetLoadingState();
return;
}
if (this.reduceLengthAndFlushBuffer(data)) {
this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
}
Expand Down Expand Up @@ -1292,7 +1297,7 @@ export default class StreamController
}
});
// trigger handler right now
this.tick();
this.tickImmediate();
}

public getMainFwdBufferInfo(): BufferInfo | null {
Expand Down
22 changes: 18 additions & 4 deletions tests/unit/controller/buffer-controller-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,15 @@ describe('BufferController', function () {

it('should cycle the SourceBuffer operation queue if the sourceBuffer does not exist while appending', function () {
const queueAppendSpy = sandbox.spy(operationQueue, 'append');
const frag = new Fragment(PlaylistLevelType.MAIN, '');
const chunkMeta = new ChunkMetadata(0, 0, 0, 0);
queueNames.forEach((name, i) => {
bufferController.sourceBuffer = {};
bufferController.onBufferAppending(Events.BUFFER_APPENDING, {
type: name,
data: new Uint8Array(),
frag: new Fragment(PlaylistLevelType.MAIN, ''),
chunkMeta: new ChunkMetadata(0, 0, 0, 0),
frag,
chunkMeta,
});

expect(
Expand All @@ -243,8 +245,20 @@ describe('BufferController', function () {
'The queue should have been cycled'
).to.have.callCount(i + 1);
});
expect(triggerSpy, 'No event should have been triggered').to.have.not.been
.called;
expect(
triggerSpy,
'Buffer append error event should have been triggered'
).to.have.been.calledWith(Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.BUFFER_APPEND_ERROR,
parent: 'main',
frag,
part: undefined,
chunkMeta,
error: triggerSpy.getCall(0).lastArg.error,
err: triggerSpy.getCall(0).lastArg.error,
fatal: false,
});
});
});

Expand Down

0 comments on commit 11b11de

Please sign in to comment.