{"record":{"id":"8d525eaac7974343","repo":"remotion-dev/remotion","slug":"the-time-stretcher-has-already-been-finalized-streaming","errorCode":null,"errorMessage":"The time stretcher has already been finalized.","messagePattern":"The time stretcher has already been finalized\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/media/src/audio/streaming-pitch-shifter.ts","lineNumber":321,"sourceCode":"\t\tthis.outputLength -= finalizedLength;\n\t\tthis.synthesisPosition -= finalizedLength;\n\n\t\tconst inputFramesToDiscard = Math.max(\n\t\t\t0,\n\t\t\tMath.floor(this.analysisPosition) - this.searchRadius,\n\t\t);\n\t\tfor (let channel = 0; channel < this.numberOfChannels; channel++) {\n\t\t\tthis.input[channel].copyWithin(0, inputFramesToDiscard, this.inputLength);\n\t\t}\n\n\t\tthis.inputLength -= inputFramesToDiscard;\n\t\tthis.analysisPosition -= inputFramesToDiscard;\n\t\treturn result;\n\t}\n\n\tpublic finalize() {\n\t\tif (this.finalized) {\n\t\t\tthrow new Error('The time stretcher has already been finalized.');\n\t\t}\n\n\t\tthis.finalized = true;\n\t\tconst targetLength = Math.round(this.totalInputFrames * this.factor);\n\t\tconst padding = makePlanarAudio(\n\t\t\tthis.numberOfChannels,\n\t\t\tthis.windowSize + this.searchRadius * 2,\n\t\t);\n\t\tthis.input = ensurePlanarCapacity({\n\t\t\tbuffers: this.input,\n\t\t\trequiredLength: this.inputLength + padding[0].length,\n\t\t});\n\t\tfor (let channel = 0; channel < this.numberOfChannels; channel++) {\n\t\t\tthis.input[channel].set(padding[channel], this.inputLength);\n\t\t}\n\n\t\tthis.inputLength += padding[0].length;\n\t\tthis.process();","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/remotion-dev/remotion/blob/46a3a6bf13ca367376ff18bbb06dae2e87e0d068/packages/media/src/audio/streaming-pitch-shifter.ts#L303-L339","documentation":"finalize() flushes the time stretcher and produces the final stretched output; it must run exactly once per instance. The instance tracks a `finalized` flag, and calling finalize() a second time throws this error because there is nothing left to flush and re-finalizing would produce duplicate/incorrect output.","triggerScenarios":"Calling finalize() twice on the same StreamingTimeStretcher instance, e.g. finalize triggered both by an explicit call and by a stream 'end' handler, or a retry path that calls finalize again after an earlier error.","commonSituations":"Event-driven audio pipelines where both the end-of-stream callback and cleanup code call finalize; wrapping finalize in retry logic without idempotency; accidental duplicate shutdown hooks.","solutions":["Call finalize() only once per instance; guard the call with an `if (!stretcher.finalized)` check in shared cleanup code","Deduplicate shutdown paths so end-of-stream and cleanup logic do not both finalize","Create a fresh StreamingTimeStretcher if you need to process another stream"],"exampleFix":"// before\nstream.on('end', () => stretcher.finalize());\ncleanup(() => stretcher.finalize()); // throws on second call\n// after\nstream.on('end', () => {\n  if (!stretcher.finalized) stretcher.finalize();\n});","handlingStrategy":"type-guard","validationCode":"function canFinalize(s: {readonly finalized: boolean}): boolean {\n  return !s.finalized;\n}","typeGuard":"function isFinalizable(s: StreamingTimeStretcher): boolean {\n  return !s.finalized;\n}","tryCatchPattern":"try {\n  stretcher.finalize();\n} catch (e) {\n  if ((e as Error).message.includes('already been finalized')) return; // idempotent shutdown\n  throw e;\n}","preventionTips":["Wrap finalize in a small helper that checks the finalized flag first to make shutdown idempotent","Ensure only one code path is responsible for finalizing a given stretcher","Do not put finalize inside retry loops without an already-finalized check"],"tags":["audio","state","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"46a3a6bf13ca367376ff18bbb06dae2e87e0d068","analyzedAt":"2026-09-18T00:12:20.256Z","contentChangedAt":"2026-09-18T00:12:20.256Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}