Replace broken per-segment Opus decode with WebCodecs AudioDecoder streaming pipeline

This commit is contained in:
daniel-c-harvey
2026-06-23 17:42:06 -04:00
parent d0118997b6
commit 7f3fb74126
13 changed files with 1270 additions and 649 deletions
@@ -123,6 +123,39 @@ export function presentationTimeSeconds(granulePosition: number, preSkip: number
return Math.max(0, (granulePosition - preSkip) / OPUS_SAMPLE_RATE);
}
/**
* Resolve a seek time (seconds) to a file-absolute, page-start byte offset via the precomputed index —
* the accurate VBR-safe transfer function (§3.4a A/C). Binary-searches for the largest entry whose
* presentation time is <= `positionSeconds` and returns its exact page-start byte offset. NOT
* interpolation, NOT byteRate math. With an empty index it degrades to the start of audio (the offset
* of the first audio page == the setup-header length, since the server emits [setup pages][audio pages]).
*
* This is the single source of truth for Opus seek-offset math, shared by the seek-beyond-buffer path
* (AudioPlayer) and any byte-offset resolver. The Range fetch from this offset lands the decoder
* Ogg-sync-aligned because every indexed offset is a real page start.
*/
export function resolveOpusByteOffset(sidecar: OpusSeekData, positionSeconds: number): number {
const points = sidecar.points;
if (points.length === 0) {
return sidecar.setupHeaderBytes.length;
}
let lo = 0;
let hi = points.length - 1;
let best = 0;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
const t = presentationTimeSeconds(points[mid].granulePosition, sidecar.preSkip);
if (t <= positionSeconds) {
best = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return points[best].byteOffset;
}
function toUint8Array(input: Uint8Array | ArrayBuffer | ArrayBufferView): Uint8Array {
if (input instanceof Uint8Array) return input;
if (input instanceof ArrayBuffer) return new Uint8Array(input);