diff --git a/src/cursors.ts b/src/cursors.ts index d0232f3..b1adecb 100644 --- a/src/cursors.ts +++ b/src/cursors.ts @@ -14,7 +14,7 @@ import { assert, AsyncMutex, AsyncMutexLock, - CallSerializer2, + ForgivingCallSerializer, defer, isFirefox, last, @@ -255,7 +255,7 @@ export class PacketCursor { private _reader: PacketReader; private _options: PacketRetrievalOptions; private _nextIsFirst = true; - private _callSerializer = new CallSerializer2(); + private _callSerializer = new ForgivingCallSerializer(); constructor(track: T, options: PacketRetrievalOptions = {}) { if (!(track instanceof InputTrack)) { diff --git a/src/decode.ts b/src/decode.ts index 6d7b309..845d28b 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -20,7 +20,7 @@ import { } from './codec-data'; import { CustomAudioDecoder, customAudioDecoders, CustomVideoDecoder, customVideoDecoders } from './custom-coder'; import { - CallSerializer, + NaiveCallSerializer, Rotation, isChromium, toUint8Array, @@ -57,7 +57,7 @@ export class VideoDecoderWrapper extends DecoderWrapper { decoder: VideoDecoder | null = null; customDecoder: CustomVideoDecoder | null = null; - customDecoderCallSerializer = new CallSerializer(); + customDecoderCallSerializer = new NaiveCallSerializer(); customDecoderQueueSize = 0; customDecoderClosed = false; @@ -645,7 +645,7 @@ export class AudioDecoderWrapper extends DecoderWrapper { decoder: AudioDecoder | null = null; customDecoder: CustomAudioDecoder | null = null; - customDecoderCallSerializer = new CallSerializer(); + customDecoderCallSerializer = new NaiveCallSerializer(); customDecoderQueueSize = 0; customDecoderClosed = false; diff --git a/src/media-source.ts b/src/media-source.ts index b215f40..1ee3d06 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -23,7 +23,7 @@ import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } import { assert, assertNever, - CallSerializer, + NaiveCallSerializer, clamp, isFirefox, last, @@ -214,7 +214,7 @@ class VideoEncoderWrapper { private resizeCanvas: HTMLCanvasElement | OffscreenCanvas | null = null; private customEncoder: CustomVideoEncoder | null = null; - private customEncoderCallSerializer = new CallSerializer(); + private customEncoderCallSerializer = new NaiveCallSerializer(); private customEncoderQueueSize = 0; // Alpha stuff @@ -1342,7 +1342,7 @@ class AudioEncoderWrapper { private writeOutputValue: ((view: DataView, byteOffset: number, value: number) => void) | null = null; private customEncoder: CustomAudioEncoder | null = null; - private customEncoderCallSerializer = new CallSerializer(); + private customEncoderCallSerializer = new NaiveCallSerializer(); private customEncoderQueueSize = 0; private lastEndSampleIndex: number | null = null; diff --git a/src/misc.ts b/src/misc.ts index aef839c..7957be8 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -675,20 +675,6 @@ export const computeRationalApproximation = (x: number, maxDenominator: number) }; }; -export class CallSerializer { - currentPromise = Promise.resolve(); - errored = false; - - call(fn: () => Promise | void) { - return this.currentPromise = this.currentPromise - .then(fn) - .catch((error) => { - this.errored = true; - throw error; - }); - } -} - let isWebKitCache: boolean | null = null; export const isWebKit = () => { if (isWebKitCache !== null) { @@ -938,7 +924,30 @@ export class AsyncMutexLock implements Disposable { } } -export class CallSerializer2 { +/** + * A simple call serializer that works by chaining promises. When one callback throws, the serializer becomes bricked, + * meaning all future calls will also throw. + */ +export class NaiveCallSerializer { + currentPromise = Promise.resolve(); + errored = false; + + call(fn: () => Promise | void) { + return this.currentPromise = this.currentPromise + .then(fn) + .catch((error) => { + this.errored = true; + throw error; + }); + } +} + +/** + * A more complex call serializer implementation that works with optionally asynchronous functions. It is forgiving + * in the sense that when a call throws, the error is surfaced but subsequent calls will go through again. So, it + * recovers. + */ +export class ForgivingCallSerializer { private currentPromise: Promise | null = null; private queuedCalls = 0; diff --git a/test/node/call-serializer.test.ts b/test/node/forgiving-call-serializer.test.ts similarity index 87% rename from test/node/call-serializer.test.ts rename to test/node/forgiving-call-serializer.test.ts index 7ff94e6..40c9590 100644 --- a/test/node/call-serializer.test.ts +++ b/test/node/forgiving-call-serializer.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest'; -import { CallSerializer2 } from '../../src/misc.js'; +import { ForgivingCallSerializer } from '../../src/misc.js'; const executeDelayed = async (fn: () => T) => { await new Promise(resolve => setTimeout(resolve, 10)); @@ -8,7 +8,7 @@ const executeDelayed = async (fn: () => T) => { test('Call serialization and return values', async () => { const numbers: number[] = []; - const serializer = new CallSerializer2(); + const serializer = new ForgivingCallSerializer(); const first = serializer.call(() => numbers.push(1)); const second = serializer.call(() => executeDelayed(() => numbers.push(2))); @@ -25,7 +25,7 @@ test('Call serialization and return values', async () => { }); test('Synchronous return value', async () => { - const serializer = new CallSerializer2(); + const serializer = new ForgivingCallSerializer(); const first = serializer.call(() => {}); expect(first).not.toBeInstanceOf(Promise); @@ -42,7 +42,7 @@ test('Synchronous return value', async () => { }); test('Error handling', async () => { - const serializer = new CallSerializer2(); + const serializer = new ForgivingCallSerializer(); expect(() => serializer.call(() => { throw new Error('yo');