Skip to content

Error handling

Every failure the SDK raises is a TtsError carrying a machine-readable code. Catch broadly, then branch on the code.

ts
import { TtsError, isTtsError } from "@optimalai/react-native";

try {
  await tts.synthesizeToFile({ text: "你好。" });
} catch (error) {
  if (isTtsError(error)) {
    console.warn(error.code, error.message, error.details);
  } else {
    throw error;
  }
}

Prefer isTtsError over instanceof — it survives a duplicated module instance in the bundle.

The error codes

CodeMeaningRecoverable?
MODEL_NOT_FOUNDA model asset could not be resolved.Fatal — needs reset().
MODEL_CORRUPTAn asset exists but failed validation.Fatal — needs reset().
UNSUPPORTED_DEVICEThe device or OS is below the baseline.Fatal — needs reset().
BUSYAnother load or synthesis is already running.Yes — retry after the current one finishes.
INVALID_TEXTThe text contains an out-of-vocabulary word or unsupported character.Yes — change the text.
TEXT_TOO_LONGInput exceeds 2,000 code points.Yes — split the text.
TEXT_SEGMENT_TOO_LONGA segment cannot be split below the 300-code-point cap.Yes — split the text.
INFERENCE_FAILEDThe ONNX session failed while generating audio.Usually — retry, or reset() if it repeats.
OUTPUT_EXISTSoutputPath already points at a file.Yes — delete the file or use another path.
OUTPUT_WRITE_FAILEDThe WAV could not be written to disk.Yes — check permissions and free space.

Fatal codes need a reset

MODEL_NOT_FOUND, MODEL_CORRUPT, and UNSUPPORTED_DEVICE move the client to failed. From failed you cannot call load() directly — you must reset() first:

ts
if (tts.status() === "failed") {
  await tts.reset();
}
await tts.load();

One synthesis at a time

A client runs a single synthesis at a time. Starting another while one is in flight throws BUSY:

ts
// This will throw BUSY.
await Promise.all([
  tts.synthesizeToFile({ text: "第一句。" }),
  tts.synthesizeToFile({ text: "第二句。" }),
]);

Serialize instead:

ts
for (const text of ["第一句。", "第二句。"]) {
  await tts.synthesizeToFile({ text });
}

The same applies to load(): calling it while the client is loading or synthesizing throws BUSY. If you need parallelism, create a second client — but be aware each one opens its own ONNX session and holds its own memory.

Out-of-vocabulary text

English words not present in the bundled lexicon fail with INVALID_TEXT. There is no eSpeak fallback in the Beta — this is deliberate, to keep GPL code out of the dependency graph.

Chinese characters missing from the Chinese lexicon fail the same way.

ts
try {
  await tts.synthesizeToFile({ text: "Supercalifragilistic" });
} catch (error) {
  if (isTtsError(error) && error.code === "INVALID_TEXT") {
    // Show the user a "this word isn't supported" message.
  }
}

Validate or sanitize user-supplied text before you synthesize it, rather than letting a stray token disrupt a batch.

Surfacing errors in UI

A small helper keeps the code visible without leaking stack traces:

ts
function errorMessage(error: unknown): string {
  if (isTtsError(error)) return `${error.code}: ${error.message}`;
  return error instanceof Error ? error.message : "Unknown error";
}

The TtsError also carries an optional details record with extra context for diagnostics — log it, don't show it to users.