remotion-dev/remotion · error · Error
Cannot append audio after the time stretcher was finalized.
Error message
Cannot append audio after the time stretcher was finalized.
What it means
The streaming pitch shifter buffers input audio and can only accept data while the time stretcher is still active. Once finalize() has been called the internal buffers are closed for writing, so append() throws this error to prevent corrupting the stretched output. It is a lifecycle/state guard.
Solutions
- Ensure all append() calls happen before calling finalize()
- Restructure the audio pipeline so finalize() is only called after the last chunk is appended
- Create a new StreamingTimeStretcher instance for any additional audio after finalization
Example fix
// before stretcher.append(lastChunk); stretcher.finalize(); stretcher.append(lateChunk); // throws // after stretcher.append(lastChunk); stretcher.append(lateChunk); stretcher.finalize();
Defensive patterns
Strategy: type-guard
Validate before calling
function canAppend(s: {readonly finalized: boolean}): boolean {
return !s.finalized;
} Type guard
function isAppendable(s: StreamingTimeStretcher): boolean {
return !s.finalized;
} Try / catch
try {
stretcher.append(audio);
} catch (e) {
if ((e as Error).message.includes('finalized')) {
throw new Error('Audio chunk arrived after finalize(); check pipeline ordering', {cause: e});
}
throw e;
} Prevention
- Enforce ordering: append all chunks, then finalize, in a single async sequence (await each append before finalize)
- Avoid calling finalize in multiple places (end handler + cleanup); centralize it
- If late chunks are possible, buffer them and append before finalizing, or use a new stretcher instance
When it happens
Trigger: Calling append(planarAudio) on a StreamingTimeStretcher instance after finalize() has already been invoked on the same instance; e.g. pushing late audio chunks in a pipeline that finalized early.
Common situations: Streaming pipelines where the finish/finalize step runs before all audio chunks have been delivered (race condition or wrong ordering in async chunk processing); reusing a finalized stretcher for a second stream.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- The time stretcher has already been finalized.
- Cannot append audio after the time stretcher was finalized.
- Cannot change the behavior for pre-mounting audio tags…
- Changing the AudioContext sample rate dynamically is not…
- `_experimentalKeepAudioContextAlive` cannot be changed…
AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18).
Data as JSON: /api/errors/e89fc47e26a5168a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/media/src/audio/streaming-pitch-shifter.ts:127
sampleRate: number;
factor: number;
}) {
this.numberOfChannels = numberOfChannels;
this.factor = factor;
this.hopSize = Math.max(
32,
Math.round((REFERENCE_HOP_SIZE * sampleRate) / REFERENCE_SAMPLE_RATE),
);
this.windowSize = this.hopSize * 2;
this.searchRadius = this.hopSize;
this.analysisHop = this.hopSize / factor;
this.input = makePlanarAudio(numberOfChannels, 65_536);
this.output = makePlanarAudio(numberOfChannels, 65_536);
}
public append(audio: PlanarAudio) {
if (this.finalized) {
throw new Error(
'Cannot append audio after the time stretcher was finalized.',
);
}
const {length} = audio[0];
this.input = ensurePlanarCapacity({
buffers: this.input,
requiredLength: this.inputLength + length,
});
for (let channel = 0; channel < this.numberOfChannels; channel++) {
this.input[channel].set(audio[channel], this.inputLength);
}
this.inputLength += length;
this.totalInputFrames += length;
this.process();
return this.drainFinalizedOutput();View on GitHub (pinned to 46a3a6bf13)