Skip to content

Commit 1d28ee2

Browse files
authored
fix(Transmux): Fall back to main thread when the transmux worker fails (#10370)
This PR makes a transmux worker crash or load failure fall back to main-thread transmuxing instead of failing playback with error 3018. Previously only the timeout path fell back - a worker error rejected the in-flight request. relates to #10347
1 parent 25b244e commit 1d28ee2

2 files changed

Lines changed: 34 additions & 27 deletions

File tree

lib/transmuxer/transmuxer_proxy.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,13 @@ shaka.transmuxer.TransmuxerProxy = class {
245245

246246
const response = await promise;
247247

248-
// If the worker did not respond in time, warn and fall back to
249-
// main-thread transmuxing so playback can continue instead of failing.
250-
if (timedOut) {
248+
// A null response means the worker either did not respond in time
249+
// (timedOut) or failed/was terminated (terminateWorker_ resolves pending
250+
// requests with null). In both cases fall back to main-thread transmuxing
251+
// so playback continues instead of failing.
252+
if (timedOut || response === null) {
251253
shaka.log.alwaysWarn(
252-
'Worker transmux timed out; falling back to ' +
254+
'Worker transmux unavailable; falling back to ' +
253255
'main-thread transmuxing');
254256
return this.innerTransmuxer_.transmux(
255257
data, stream, reference, duration, contentType);
@@ -398,22 +400,23 @@ shaka.transmuxer.TransmuxerProxy.getOrCreateWorker_ = (workerUrlOverride) => {
398400

399401

400402
/**
401-
* Marks all active instances as failed, rejects their pending requests, and
402-
* shuts down the shared worker.
403-
* @param {string} message Error message for rejected promises.
403+
* Marks all active instances as failed, resolves their pending requests so the
404+
* callers fall back to main-thread transmuxing, and shuts down the shared
405+
* worker.
406+
* @param {string} message Reason the worker was terminated, for logging.
404407
* @private
405408
*/
406409
shaka.transmuxer.TransmuxerProxy.terminateWorker_ = (message) => {
407410
const TransmuxerProxy = shaka.transmuxer.TransmuxerProxy;
411+
shaka.log.warning(
412+
'Transmuxer worker terminated, falling back to main thread:', message);
408413
for (const instance of TransmuxerProxy.activeInstances_.values()) {
409414
instance.workerFailed_ = true;
410415
for (const pending of instance.pendingRequests_.values()) {
411416
pending.timer.stop();
412-
pending.reject(new shaka.util.Error(
413-
shaka.util.Error.Severity.CRITICAL,
414-
shaka.util.Error.Category.MEDIA,
415-
shaka.util.Error.Code.TRANSMUXING_FAILED,
416-
message));
417+
// Resolve (not reject) with null so the awaiting transmux() call falls
418+
// back to the main thread instead of surfacing a fatal error.
419+
pending.resolve(null);
417420
}
418421
instance.pendingRequests_.clear();
419422
}

test/transmuxer/transmuxer_proxy_unit.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -490,27 +490,31 @@ describe('TransmuxerProxy', () => {
490490
});
491491

492492
describe('worker global error event', () => {
493-
it('rejects the pending request', async () => {
493+
it('falls back to main thread for the in-flight request', async () => {
494494
const p = transmuxer.transmux(fakeData, fakeStream, null, 10, 'video');
495495

496-
const assertion = expectAsync(p).toBeRejectedWith(
497-
jasmine.objectContaining({
498-
code: shaka.util.Error.Code.TRANSMUXING_FAILED,
499-
}));
500-
501-
capturedErrorHandler(new Event('error'));
502-
await assertion;
503-
});
504-
505-
it('falls back to main thread after worker error', async () => {
506-
const p = transmuxer.transmux(fakeData, fakeStream, null, 10, 'video');
507-
const rejection = expectAsync(p).toBeRejected();
496+
// The worker crashes / fails to load; its global error event fires
497+
// while a transmux is in flight.
508498
capturedErrorHandler(new Event('error'));
509-
await rejection;
510499

511-
await transmuxer.transmux(fakeData, fakeStream, null, 10, 'video');
500+
// The pending call resolves via the inner transmuxer instead of
501+
// rejecting, so playback can continue.
502+
const result = await p;
512503
expect(mockInner.transmux).toHaveBeenCalled();
504+
expect(result).toEqual(jasmine.any(Uint8Array));
513505
});
506+
507+
it('continues falling back to main thread after a worker error',
508+
async () => {
509+
const p = transmuxer.transmux(
510+
fakeData, fakeStream, null, 10, 'video');
511+
capturedErrorHandler(new Event('error'));
512+
await p;
513+
514+
// A subsequent call goes straight to the inner transmuxer.
515+
await transmuxer.transmux(fakeData, fakeStream, null, 10, 'video');
516+
expect(mockInner.transmux).toHaveBeenCalledTimes(2);
517+
});
514518
});
515519

516520
it('surfaces init error with correct reqId on first transmux', async () => {

0 commit comments

Comments
 (0)