Merge Opus 48kHz up-front AudioContext init (root-cause fix) into streaming-overhaul
This commit is contained in:
@@ -16,7 +16,7 @@ import { WavFormatDecoder } from './WavFormatDecoder.js';
|
||||
import { Mp3FormatDecoder } from './Mp3FormatDecoder.js';
|
||||
import { FlacFormatDecoder } from './FlacFormatDecoder.js';
|
||||
import { OpusStreamDecoder } from './OpusStreamDecoder.js';
|
||||
import { OpusSeekData, parseSidecar, resolveOpusByteOffset, OpusSeekResolution } from './OpusSidecar.js';
|
||||
import { OpusSeekData, parseSidecar, resolveOpusByteOffset, OpusSeekResolution, OPUS_SAMPLE_RATE } from './OpusSidecar.js';
|
||||
|
||||
export interface AudioResult {
|
||||
success: boolean;
|
||||
@@ -118,7 +118,7 @@ export class AudioPlayer {
|
||||
|
||||
// ==================== Streaming ====================
|
||||
|
||||
initializeStreaming(totalStreamLength: number, contentType: string): AudioResult {
|
||||
async initializeStreaming(totalStreamLength: number, contentType: string): Promise<AudioResult> {
|
||||
try {
|
||||
// Full cleanup before starting new stream
|
||||
this.stopProgressTracking();
|
||||
@@ -137,6 +137,19 @@ export class AudioPlayer {
|
||||
// selects Opus when the sidecar parsed, so the null branch is defensive.
|
||||
if (this.isOpusContentType(contentType) && this.pendingOpusSidecar) {
|
||||
this.activeOpusSidecar = this.pendingOpusSidecar;
|
||||
|
||||
// Align the AudioContext to 48 kHz NOW, before any Opus bytes flow — the format is
|
||||
// already resolved (C# resolves Opus + injects the sidecar before this call), so the
|
||||
// target rate is known up front. Done here, the decoder's own lazy
|
||||
// recreateWithSampleRate(48000) in ensureConfigured hits its sampleRate-equal early
|
||||
// return and is a no-op; the live graph is never close()'d and rebuilt mid-decode (the
|
||||
// teardown that double-decoded the stream and OOM'd the tab with HW accel off). The
|
||||
// recreate seam itself stays — it is the WAV path's mechanism for non-44.1 sources and
|
||||
// remains the defensive backstop here.
|
||||
if (this.contextManager.sampleRate !== OPUS_SAMPLE_RATE) {
|
||||
await this.contextManager.recreateWithSampleRate(OPUS_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
// Pass the shared back-pressure signal (21.2b): the Opus decoder stops demuxing/
|
||||
// decoding new packets while the scheduler is full, so the WebCodecs decode queue
|
||||
// and decodedQueue do not balloon behind a throttled socket (OQ7). Same signal the
|
||||
@@ -148,7 +161,9 @@ export class AudioPlayer {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// Non-Opus (or Opus-without-sidecar): the existing StreamDecoder path, unchanged.
|
||||
// Non-Opus (or Opus-without-sidecar): the existing StreamDecoder path, unchanged. The
|
||||
// context sample rate is untouched here, so the WAV/lossless path is byte-for-byte
|
||||
// unaffected by the Opus up-front alignment above.
|
||||
const formatDecoder = this.createFormatDecoder(contentType);
|
||||
this.streamDecoder.initialize(totalStreamLength, formatDecoder);
|
||||
return { success: true };
|
||||
|
||||
@@ -123,7 +123,11 @@ export class OpusStreamDecoder implements IStreamingDecoder {
|
||||
// Copy the OpusHead into a standalone buffer — the sidecar subarray is a view we keep.
|
||||
this.opusHeadDescription = opusHead.slice();
|
||||
|
||||
// Opus decodes at 48 kHz; align the context so no resample is needed.
|
||||
// Opus decodes at 48 kHz; align the context so no resample is needed. AudioPlayer.initializeStreaming
|
||||
// already aligned it to 48 kHz up front (the format is resolved before any bytes flow), so in the
|
||||
// common path this is an early-return no-op — the live graph is NOT close()'d and rebuilt mid-decode.
|
||||
// Kept as the defensive backstop for any path that reaches a configured decoder on a non-48 kHz
|
||||
// context (the same recreate seam the WAV path uses for non-44.1 sources).
|
||||
if (this.contextManager.sampleRate !== OPUS_SAMPLE_RATE) {
|
||||
await this.contextManager.recreateWithSampleRate(OPUS_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ const DeepDrftAudio = {
|
||||
}
|
||||
},
|
||||
|
||||
initializeStreaming: (playerId: string, totalStreamLength: number, contentType: string): AudioResult => {
|
||||
initializeStreaming: async (playerId: string, totalStreamLength: number, contentType: string): Promise<AudioResult> => {
|
||||
const player = audioPlayers.get(playerId);
|
||||
if (!player) return { success: false, error: 'Player not found' };
|
||||
return player.initializeStreaming(totalStreamLength, contentType);
|
||||
|
||||
Reference in New Issue
Block a user