decolua/9router · error

Bing TTS returned empty audio

Error message

Bing TTS returned empty audio

What it means

After Bing's tfettts endpoint responds 200, edgeTts.js reads the body and requires at least 1024 bytes before treating it as valid MP3 audio; a smaller payload throws "Bing TTS returned empty audio". This guards against responses that look successful but contain no usable audio (error pages, empty bodies, stub responses from the undocumented endpoint).

Source

Thrown at open-sse/handlers/ttsProviders/edgeTts.js:86

  async synthesize(text, model) {
    const voiceId = model || "vi-VN-HoaiMyNeural";
    let token = await getToken();
    let res = await ttsRequest(text, voiceId, token);

    // 429/403: invalidate cache and retry once
    if (res.status === 429 || res.status === 403) {
      cache.token = null;
      cache.tokenTime = 0;
      token = await getToken();
      res = await ttsRequest(text, voiceId, token);
    }

    if (!res.ok) {
      const body = await res.text().catch(() => "");
      throw new Error(`Bing TTS failed: ${res.status}${body ? " - " + body : ""}`);
    }
    const buf = await res.arrayBuffer();
    if (buf.byteLength < 1024) throw new Error("Bing TTS returned empty audio");
    return { base64: Buffer.from(buf).toString("base64"), format: "mp3" };
  },
};

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Verify the voice id is real by fetching the voice list (fetchEdgeTtsVoices) and picking one of its `ShortName` values instead of a hand-typed id.
  2. Check that the text argument is non-empty and non-whitespace before calling synthesize; log the text length when this fires.
  3. Retry the request — this is often transient; the module refreshes its Bing token every ~5 minutes, so a later attempt may use a fresh token.
  4. Inspect the actual response: temporarily log res body/headers on this path; if Bing consistently returns HTML instead of audio, its endpoint changed and this provider needs updating or replacing.
  5. Fall back to another TTS provider for affected requests.

Example fix

// before
text = userInput.trim();
await edgeTts.synthesize(text, "my-custom-voice");
// after: validate inputs against the real voice list
const voices = await fetchEdgeTtsVoices();
const voice = "my-custom-voice";
if (!text?.trim()) throw new Error("TTS text is empty");
if (!voices.some(v => v.ShortName === voice)) throw new Error(`Unknown voice: ${voice}`);
await edgeTts.synthesize(text.trim(), voice);
Defensive patterns

Strategy: validation

Validate before calling

import { fetchEdgeTtsVoices } from "open-sse/handlers/ttsProviders/edgeTts.js";
if (!text?.trim()) throw new Error("TTS text is empty");
const voices = await fetchEdgeTtsVoices();
if (!voices.some(v => v.ShortName === voiceId)) throw new Error(`Unknown Edge voice: ${voiceId}`);

Type guard

function isValidEdgeVoice(voice, voices) {
  return typeof voice === "string" && voices.some(v => v.ShortName === voice);
}

Try / catch

try {
  const audio = await edgeTts.synthesize(text, voiceId);
} catch (e) {
  if (e.message === "Bing TTS returned empty audio") {
    // usually invalid voice or empty text — retry once with a known-good default voice
    return edgeTts.synthesize(text, "en-US-AriaNeural");
  }
  throw e;
}

Prevention

When it happens

Trigger: The Bing TTS endpoint returns HTTP 200 but a tiny body — typically when Bing silently rejects the SSML/token (returns an empty or stub response), the requested voiceId is invalid/unsupported, the text is empty or only whitespace, or Bing returns an HTML error page smaller than 1KB with a 200 status.

Common situations: Passing a made-up or removed voice id (e.g. "xx-YY-FooNeural") that Bing accepts but cannot synthesize; sending empty text after client-side trimming; Bing A/B-testing a new response format that no longer returns MP3; intermittent Bing flakiness where the endpoint 200s with an empty body.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/ff9ff6697e90e08c. Report an issue: GitHub.