Phase 21.2: back-pressure to bound the unplayed decoded region
Shared scheduler fill signal (forward water-marks + hard byte cap) pauses the C# read loop above high-water and, for Opus, stops the demux/decode feed so WebCodecs queues stay near-empty. Routes through the existing cancellation discipline; releases the latch on clear/seek.
This commit is contained in:
@@ -448,6 +448,65 @@ test('OpusStreamDecoder.totalDuration is null when the sidecar carries no positi
|
||||
assertEqual(decoder.totalDuration, null, 'no positive duration -> null');
|
||||
});
|
||||
|
||||
// --- Phase 21.2b: Opus decode-ahead back-pressure (the stash-while-full half) ------------------
|
||||
//
|
||||
// When the shared scheduler is full, push() must NOT demux/decode ahead — it stashes the raw bytes
|
||||
// and returns nothing, so the WebCodecs decode queue and decodedQueue stay near-empty (OQ7). The
|
||||
// stash-while-full branch returns BEFORE ensureConfigured(), so it is testable without WebCodecs
|
||||
// (no AudioDecoder is constructed). The drain-on-resume path needs the real WebCodecs decoder and
|
||||
// stays browser-verified; here we pin the bound itself and the lifecycle resets.
|
||||
|
||||
// Access the private stash for white-box assertions (same idiom the scheduler tests use).
|
||||
function stashLength(decoder: OpusStreamDecoder): number {
|
||||
return (decoder as unknown as { pendingBytes: Uint8Array[] }).pendingBytes.length;
|
||||
}
|
||||
|
||||
// The stash-while-full branch returns synchronously at the top of push() (before any real await),
|
||||
// so the stash is observable immediately without awaiting the returned promise — keeping these
|
||||
// tests inside the synchronous inline harness (which does not await test bodies).
|
||||
test('push stashes bytes and decodes nothing while the scheduler is full (no decode-ahead)', () => {
|
||||
const sidecar = sidecarFrom({
|
||||
setupHeader: [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64],
|
||||
totalByteLength: 500_000, totalDuration: 100, preSkip: 312,
|
||||
points: [{ granule: 312, byteOffset: 4096 }],
|
||||
});
|
||||
// Scheduler reports "full" → push must short-circuit before touching WebCodecs.
|
||||
const decoder = new OpusStreamDecoder(stubContextManager, sidecar, () => true);
|
||||
|
||||
void decoder.push(new Uint8Array([1, 2, 3]));
|
||||
void decoder.push(new Uint8Array([4, 5]));
|
||||
|
||||
assertEqual(stashLength(decoder), 2, 'both chunks stashed in arrival order');
|
||||
assertEqual(decoder.ready, false, 'decoder not even configured while throttled');
|
||||
});
|
||||
|
||||
test('reinitializeForRangeContinuation drops the pre-seek stash (C6 — no stale feed across reset)', () => {
|
||||
const sidecar = sidecarFrom({
|
||||
setupHeader: [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64],
|
||||
totalByteLength: 500_000, totalDuration: 100, preSkip: 312,
|
||||
points: [{ granule: 312, byteOffset: 4096 }],
|
||||
});
|
||||
const decoder = new OpusStreamDecoder(stubContextManager, sidecar, () => true);
|
||||
void decoder.push(new Uint8Array([1, 2, 3])); // stash one chunk while full
|
||||
assertEqual(stashLength(decoder), 1, 'one chunk stashed pre-seek');
|
||||
|
||||
decoder.reinitializeForRangeContinuation(0, 5); // a seek
|
||||
assertEqual(stashLength(decoder), 0, 'pre-seek stash dropped on range-continuation');
|
||||
});
|
||||
|
||||
test('dispose clears the stash', () => {
|
||||
const sidecar = sidecarFrom({
|
||||
setupHeader: [0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64],
|
||||
totalByteLength: 500_000, totalDuration: 100, preSkip: 312,
|
||||
points: [{ granule: 312, byteOffset: 4096 }],
|
||||
});
|
||||
const decoder = new OpusStreamDecoder(stubContextManager, sidecar, () => true);
|
||||
void decoder.push(new Uint8Array([9]));
|
||||
assertEqual(stashLength(decoder), 1, 'stashed');
|
||||
decoder.dispose();
|
||||
assertEqual(stashLength(decoder), 0, 'stash cleared on dispose');
|
||||
});
|
||||
|
||||
function concat(arrs: Uint8Array[]): Uint8Array {
|
||||
let len = 0;
|
||||
for (const a of arrs) len += a.length;
|
||||
|
||||
Reference in New Issue
Block a user