remotion-dev/remotion · error · Error

windowInSeconds cannot be changed dynamically

Error message

windowInSeconds cannot be changed dynamically

What it means

Thrown synchronously during render by useWindowedAudioData() when windowInSeconds differs from the value captured on first render (line 75-77). The hook freezes the initial windowInSeconds into state because refetching and re-windowing already-loaded waveform data is not supported, so changing it after mount is treated as a programming error.

Source

Thrown at packages/media-utils/src/use-windowed-audio-data.ts:76

}

export const useWindowedAudioData = ({
	src,
	frame,
	fps,
	windowInSeconds,
	channelIndex = 0,
	requestInit,
}: UseWindowedAudioDataOptions): UseWindowedAudioDataReturnValue => {
	const isMounted = useRef(true);
	const [audioUtils, setAudioUtils] = useState<AudioUtils | null>(null);
	const [waveFormMap, setWaveformMap] = useState({} as WaveformMap);
	const requests = useRef<Record<string, AbortController | null>>({});
	const [initialWindowInSeconds] = useState(windowInSeconds);
	const [initialRequestInit] = useState(requestInit);

	if (windowInSeconds !== initialWindowInSeconds) {
		throw new Error('windowInSeconds cannot be changed dynamically');
	}

	useEffect(() => {
		isMounted.current = true;

		return () => {
			isMounted.current = false;

			Object.values(requests.current).forEach((controller) => {
				if (controller) {
					controller.abort();
				}
			});
			requests.current = {};

			setWaveformMap({});

			if (audioUtils) {

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Make windowInSeconds a constant or a value that is stable for the lifetime of the component (e.g. a literal or a value memoized with useState initializer).
  2. If you genuinely need a new window size, change the `key` of the component using useWindowedAudioData so React remounts it with the fresh initial value rather than updating the prop.
  3. Compute windowInSeconds outside the render cycle (e.g. from a static config) so it cannot drift between renders.
  4. If the value comes from async metadata, render a placeholder until the metadata resolves and pass the final windowInSeconds as the initial value on first mount.

Example fix

// before (windowInSeconds changes -> throws)
const {audioData} = useWindowedAudioData({
  src,
  frame,
  fps,
  windowInSeconds: duration / 10, // duration arrives later, changes value
});

// after (remount with key when the intended window changes)
const windowInSeconds = duration ? duration / 10 : 1;
return (
  <Waveform key={windowInSeconds} src={src} windowInSeconds={windowInSeconds} ... />
);
Defensive patterns

Strategy: validation

Validate before calling

// Freeze windowInSeconds for the lifetime of the component using useState initializer
const [windowInSeconds] = useState(() => computeWindow(fps));
// or derive it once from props and never change it; remount via key if it must change.

Type guard

// Ensures a value is stable across renders via ref comparison
function useStable<T>(value: T): T {
  const ref = useRef(value);
  if (ref.current !== value) {
    // surface the change loudly in dev instead of letting useWindowedAudioData throw
    throw new Error(
      `windowInSeconds changed from ${ref.current} to ${value}; remount the component via key instead.`,
    );
  }
  return ref.current;
}

Try / catch

// Render-time throw cannot be caught with try/catch in the same component.
// Wrap the subtree in an ErrorBoundary and remount with a fresh key on intent change:
// <ErrorBoundary fallback={<Retry/>}>
//   <Waveform key={windowInSeconds} windowInSeconds={windowInSeconds} ... />
// </ErrorBoundary>

Prevention

When it happens

Trigger: Passing a windowInSeconds value derived from fps, durationInSeconds, or any state that changes after mount; computing it inline from a prop that updates; passing a different number on re-render due to floating-point or rounding differences.

Common situations: Tying windowInSeconds to viewport size or fps that changes responsively; deriving it from a fetched video metadata value that arrives after first paint; passing a value from a slider/slider-like control; two siblings rendering the hook with different window sizes that swap in.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/eba11852b36c0ac9. Report an issue: GitHub.