mastra-ai/mastra · error · Error

writeMjpegAviFile: at least one frame is required

Error message

writeMjpegAviFile: at least one frame is required

What it means

writeMjpegAviFile refuses to mux an AVI from an empty frame list. A zero-frame MJPEG AVI would contain an empty video stream, so the writer throws before creating any file instead of producing a corrupt/empty video.

Source

Thrown at packages/core/src/browser/recording/mjpeg-avi.ts:72

const MAX_U32 = 0xffffffff;
const MAX_I16 = 0x7fff;

function fourcc(s: string): Buffer {
  if (s.length !== 4) {
    throw new Error(`fourcc must be 4 ASCII characters, got "${s}"`);
  }
  return Buffer.from(s, 'ascii');
}

/**
 * Encode a list of MJPEG frames as an AVI 1.0 file.
 *
 * The file is written incrementally to disk so we don't have to hold the whole
 * AVI in memory — large recordings can be hundreds of MB.
 */
export function writeMjpegAviFile(filePath: string, frames: readonly MjpegFrame[], opts: MjpegAviOptions): void {
  if (frames.length === 0) {
    throw new Error('writeMjpegAviFile: at least one frame is required');
  }
  if (opts.width <= 0 || opts.height <= 0 || !Number.isInteger(opts.width) || !Number.isInteger(opts.height)) {
    throw new Error(`writeMjpegAviFile: invalid dimensions ${opts.width}x${opts.height}`);
  }
  if (opts.width > MAX_I16 || opts.height > MAX_I16) {
    throw new Error(`writeMjpegAviFile: dimensions exceed AVI header bounds: ${opts.width}x${opts.height}`);
  }
  for (let i = 0; i < frames.length; i++) {
    assertJpegFrame(frames[i]!.bytes, i);
  }

  mkdirSync(dirname(filePath), { recursive: true });

  // Frame rate: derived from the elapsed time between the first and last
  // captured frames. AVI's main header stores a single dwMicroSecPerFrame, so
  // playback is even-paced; that matches MJPEG's typical usage.
  const elapsedMs =
    frames.length > 1 ? Math.max(1, frames[frames.length - 1]!.timestampMs - frames[0]!.timestampMs) : 1000;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure recording runs long enough for at least one frame to be captured before stopping
  2. Check state.frames.length > 0 before calling writeMjpegAviFile / encodeFramesAsAvi
  3. Verify screen-capture permissions so frames are actually captured
  4. If a zero-frame recording is legitimate, skip muxing at the caller and report an empty recording instead

Example fix

// before
writeMjpegAviFile(outPath, frames, opts);
// after
if (frames.length === 0) {
  throw new Error('cannot write AVI: no frames were captured');
}
writeMjpegAviFile(outPath, frames, opts);
Defensive patterns

Strategy: validation

Validate before calling

function canMux(frames) {
  return Array.isArray(frames) && frames.length > 0;
}
if (!canMux(frames)) throw new Error('refusing to mux: no frames');

Type guard

function hasFrames(frames) {
  return Array.isArray(frames) && frames.length > 0;
}

Try / catch

try {
  writeMjpegAviFile(outPath, frames, opts);
} catch (e) {
  if (e.message.includes('at least one frame is required')) {
    // treat as empty recording: skip or log
  } else throw e;
}

Prevention

When it happens

Trigger: Calling writeMjpegAviFile(filePath, [], opts) directly, or indirectly via encodeFramesAsAvi when RecordingState.frames is empty — e.g. stopping browser_record before any screenshot interval captured a frame.

Common situations: Stopping a recording immediately after start (faster than the capture interval); screen capture permission denied so no frames were grabbed; browser tab closed before first frame; frames cleared by an earlier error path.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/24333645a0aaea27. Report an issue: GitHub.