chatwoot/chatwoot · error · Error

No Opus frames found in WebM input

Error message

No Opus frames found in WebM input

What it means

remuxWebmToOgg parses the WebM container (EBML tracks + SimpleBlocks) to extract Opus packets and repack them into an OGG/Opus stream. The guard fires when parsing succeeded structurally but zero Opus frames were collected — the input had no usable audio track payload. It fires only after the OggS magic-bytes short-circuit, so it is specifically about a WebM file whose audio content is absent or unrecognized.

Source

Thrown at app/javascript/dashboard/components/widgets/WootWriter/utils/webmOpusToOgg.js:386

 */
export async function remuxWebmToOgg(webmBlob) {
  const buffer = await webmBlob.arrayBuffer();
  const bytes = new Uint8Array(buffer);

  // Already OGG? Return unchanged.
  if (
    bytes.length >= 4 &&
    bytes[0] === 0x4f &&
    bytes[1] === 0x67 &&
    bytes[2] === 0x67 &&
    bytes[3] === 0x53
  ) {
    return webmBlob;
  }

  const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);
  if (frames.length === 0) {
    throw new Error('No Opus frames found in WebM input');
  }

  // Extract pre-skip from the WebM CodecPrivate (which IS the OpusHead)
  let preSkip = 312;
  if (codecPrivate && codecPrivate.length >= 12) {
    const magic = new TextDecoder().decode(codecPrivate.slice(0, 8));
    if (magic === 'OpusHead') {
      preSkip = new DataView(
        codecPrivate.buffer,
        codecPrivate.byteOffset,
        codecPrivate.length
      ).getUint16(10, true);
    }
  }

  const serial = (Math.random() * 0x100000000) >>> 0;
  let pageSeq = 0;
  const pages = [];

View on GitHub (pinned to ed230f9bc0)

Solutions

  1. Guard before remuxing: skip conversion for blobs under a minimum size (e.g. a few KB) and treat them as empty recordings in the UI
  2. Ensure the MediaRecorder is created with an explicitly supported Opus mimeType: MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
  3. Verify the recorder flow collects chunks via recorder.ondataavailable with timeslice or on stop, and that stop() completed before converting
  4. Log bytes.length and the parsed track list when the error fires to distinguish empty input from codec mismatch
  5. If input is header-only due to interruption, surface a 'recording too short' message instead of a raw error

Example fix

// before
const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);
if (frames.length === 0) {
  throw new Error('No Opus frames found in WebM input');
}

// after (caller-side guard + clearer message)
if (webmBlob.size < 1024) {
  throw new Error('Recording is empty or too short to convert');
}
const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);
if (frames.length === 0) {
  throw new Error('No Opus frames found — input has no decodable Opus audio track');
}
Defensive patterns

Strategy: validation

Validate before calling

const MIN_REMUX_BYTES = 1024; // header-only WebM is smaller than this
if (webmBlob.size < MIN_REMUX_BYTES) {
  throw new Error('Recording is empty or too short to convert');
}
const supportsOpus = MediaRecorder.isTypeSupported('audio/webm;codecs=opus');
if (!supportsOpus) {
  /* choose a different output format up front instead of remuxing later */
}

Type guard

const isNonEmptyWebm = blob =>
  blob instanceof Blob && blob.size > 1024 && /webm/i.test(blob.type);

Try / catch

try {
  const ogg = await remuxWebmToOgg(webmBlob);
} catch (error) {
  if (error.message.includes('No Opus frames')) {
    // Treat as empty recording: prompt the user instead of surfacing a codec error
    return showEmptyRecordingNotice();
  }
  throw error;
}

Prevention

When it happens

Trigger: A recording blob with size > 0 but no data blocks: user hit stop before ondataavailable delivered audio; a MediaRecorder created without an Opus audio codec (e.g. video-only webm or a vp8/vp9-only mimeType) so no block maps to the Opus track; a truncated WebM from an interrupted recording where only the header survived; a corrupted cluster segment the parser skips.

Common situations: Instant tap-and-release on the voice-note button in the WootWriter recorder; browser-specific MediaRecorder quirks where mimeType 'audio/webm;codecs=opus' was requested but not honored; memory pressure on mobile causing the recorder to drop all chunks; feeding a pre-existing OGG-less or header-only file into convertAudio's 'audio/ogg' path because its MIME was reported as webm.

Related errors


AI-assisted analysis of chatwoot/chatwoot@ed230f9bc0 (2026-08-21). Data as JSON: /api/errors/3f23721538432e82. Report an issue: GitHub.