remotion-dev/remotion · error
The mediaParserController() was used in multiple parseMedia(
Error message
The mediaParserController() was used in multiple parseMedia() calls. Create a separate controller for each call.
What it means
Thrown inside attachSeekingHintResolution when parseMedia tries to register its seeking-hints resolver on a controller that already has one. Because each controller is single-use, a second registration means the same controller was handed to a second parseMedia() call. The guard prevents the two parses from racing on one resolver.
Source
Thrown at packages/media-parser/src/controller/media-parser-controller.ts:89
return seekingHintResolution();
};
const simulateSeek = (seekInSeconds: number) => {
if (!simulateSeekResolution) {
throw new Error(
'The mediaParserController() was not yet used in a parseMedia() call',
);
}
return simulateSeekResolution(seekInSeconds);
};
const attachSeekingHintResolution = (
callback: () => Promise<SeekingHints | null>,
) => {
if (seekingHintResolution) {
throw new Error(
'The mediaParserController() was used in multiple parseMedia() calls. Create a separate controller for each call.',
);
}
seekingHintResolution = callback;
};
const attachSimulateSeekResolution = (
callback: (seekInSeconds: number) => Promise<SeekResolution>,
) => {
if (simulateSeekResolution) {
throw new Error(
'The mediaParserController() was used in multiple parseMedia() calls. Create a separate controller for each call.',
);
}
simulateSeekResolution = callback;
};View on GitHub (pinned to 78fe4bb3fd)
Solutions
- Create a fresh mediaParserController() for every parseMedia() invocation; store it in component/request scope, not module scope.
- If you need coordinated abort across multiple parses, share an AbortSignal via a single parent controller and create separate child controllers for each parseMedia call.
- Abort and discard the old controller before starting a new parse: controller.abort() then make a new one.
Example fix
// before
const controller = mediaParserController();
await parseMedia({src: a, controller});
await parseMedia({src: b, controller}); // throws
// after
const ctrlA = mediaParserController();
await parseMedia({src: a, controller: ctrlA});
const ctrlB = mediaParserController();
await parseMedia({src: b, controller: ctrlB}); Defensive patterns
Strategy: validation
Validate before calling
const makeRun = (src) => {
const controller = mediaParserController();
parseMedia({src, controller, fields: {duration: true}});
return controller;
};
const c1 = makeRun(a); // separate controller per run
const c2 = makeRun(b); Type guard
null
Try / catch
null
Prevention
- Instantiate mediaParserController inside the same scope as parseMedia.
- Never share a controller across concurrent or sequential parseMedia calls.
- On source change, abort and recreate the controller.
When it happens
Trigger: Passing the same controller instance to a second parseMedia() call while (or after) the first is still using it. Running parseMedia in a loop with a controller declared outside the loop.
Common situations: Building a player that re-parses when the source changes but reuses the controller variable. Concurrent parseMedia calls (e.g. video + separate audio) sharing one controller for shared abort/seek control. Hot-reload or React strict-mode double-invoke reusing a controller from module scope.
Related errors
- The mediaParserController() was not yet used in a parseMedia
- bundle() was called without arguments
- bundle() no longer supports the legacy positional arguments.
- bundle() was called without the `entryPoint` option
- No canvas recording was available to open.
AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12).
Data as JSON: /api/errors/54159385b3f3d6c2.
Report an issue: GitHub.