How OpenMAIC Works Around Browser TTS Audio Bugs: Chrome Speech Synthesis Fixes

OpenMAIC eliminates Chrome's speech synthesis "sticking" bug by explicitly canceling active utterances before starting new ones and immediately synchronizing TTS generation results into live scenes to prevent audio metadata desynchronization.

OpenMAIC runs browser-based text-to-speech pipelines that must contend with a known Chrome bug where previously started synthesis requests can interfere with subsequent playback. The platform implements defensive workarounds in lib/playback/engine.ts and lib/workbench/tts-stage-sync.ts to ensure reliable TTS delivery without audio glitches.

The Chrome Speech Synthesis Sticking Bug

Browser-based TTS in Chrome suffers from a bug where a previously-started SpeechSynthesisUtterance can remain "stuck" in the synthesis queue, corrupting or blocking subsequent speech requests. This manifests when rapid or overlapping TTS calls create stale synthesis states that prevent new audio from starting cleanly. OpenMAIC addresses this through a two-layer defense strategy targeting both playback initialization and scene state management.

Cancel-Then-Speak Pattern in the Playback Engine

OpenMAIC's primary defense is a cancel-then-speak pattern that clears any lingering synthesis state before initiating new utterances. This workaround ensures that Chrome's internal speech synthesis queue never retains stale references that could contaminate the next playback request.

Implementation in lib/playback/engine.ts

In the playback engine, the playSpeech function wraps the Web Speech API with a defensive cancellation step. According to the source code comment in lib/playback/engine.ts, this "Chrome bug workaround" ensures that cancel() is invoked immediately before speak().

// lib/playback/engine.ts (excerpt)
function playSpeech(utterance: SpeechSynthesisUtterance) {
  // Chrome bug workaround: cancel any existing utterance first
  window.speechSynthesis.cancel();
  window.speechSynthesis.speak(utterance);
}

This explicit cancellation clears the browser's internal synthesis queue, preventing the "sticking" behavior that would otherwise corrupt the new utterance.

Live Scene Synchronization for TTS Results

Beyond playback fixes, OpenMAIC ensures the UI reflects corrected audio immediately through a dedicated synchronization routine that folds generate_tts results back into the active scene. This prevents the interface from displaying stale action arrays while waiting for a full checkpoint refresh.

The applyGenerateTtsResultToScenes Function

Located in lib/workbench/tts-stage-sync.ts, the applyGenerateTtsResultToScenes function intercepts successful TTS generation events and updates the live scene copy. This guarantees that the Workbench UI immediately reflects the corrected audio metadata without waiting for background sync processes.

// lib/workbench/tts-stage-sync.ts
export function applyGenerateTtsResultToScenes(
  scenes: readonly Scene[],
  event: WorkbenchEvent,
): readonly Scene[] {
  if (event.type !== 'tool_execution_end') return scenes;
  const data = (event.data ?? {}) as Record<string, unknown>;
  if (data.toolName !== 'generate_tts' || data.isError === true) return scenes;
  const result = data.result as { details?: GenerateTtsDetails } | undefined;
  const details = result?.details;
  if (typeof details?.sceneId !== 'string' || !Array.isArray(details.sceneActions))
    return scenes;
  const sceneIndex = scenes.findIndex((scene) => scene.id === details.sceneId);
  if (sceneIndex < 0) return scenes;
  const next = [...scenes];
  next[sceneIndex] = { ...next[sceneIndex], actions: details.sceneActions as Action[] };
  return next;
}

The function validates that the event represents a successful tool_execution_end for the generate_tts tool, then extracts the sceneId and updated sceneActions to patch the scene array immutably.

Handling TTS Generation Completion

When the generate_tts tool finishes, the result—including the exact array of actions containing newly generated audio IDs—passes through this synchronization layer. The code checks data.isError to ensure only successful completions trigger updates, preventing partial or failed TTS results from corrupting the scene state. The presentation layer in components/workbench/chat/tool-presentation.ts then renders the synchronized audio data in the chat interface.

Summary

  • Cancel-then-speak pattern: OpenMAIC calls window.speechSynthesis.cancel() immediately before speak() in lib/playback/engine.ts to clear Chrome's stale synthesis state.
  • Live scene synchronization: The applyGenerateTtsResultToScenes function in lib/workbench/tts-stage-sync.ts patches TTS results directly into live scenes upon successful generate_tts completion.
  • Event-driven updates: The workaround relies on tool_execution_end events to trigger UI synchronization without waiting for checkpoint refreshes.
  • Defensive validation: Both workarounds include guard clauses to prevent error states and ensure only valid TTS data affects the playback and display.

Frequently Asked Questions

What causes the Chrome TTS audio bug in OpenMAIC?

The Chrome browser maintains an internal queue for SpeechSynthesisUtterance objects that can "stick" or fail to clear properly when synthesis requests overlap or execute rapidly. This leaves stale synthesis states active, causing subsequent speak() calls to fail or produce corrupted audio output.

How does the cancel() method fix speech synthesis sticking?

Calling window.speechSynthesis.cancel() forcibly clears the browser's internal synthesis queue and aborts any active utterances. In lib/playback/engine.ts, invoking this method immediately before speak() ensures a clean slate for each new TTS request, eliminating interference from previous synthesis attempts.

Why is scene synchronization necessary after generate_tts?

The Workbench UI displays scene actions that include audio metadata. Without immediate synchronization via applyGenerateTtsResultToScenes, the interface would display stale action arrays until the next checkpoint sync, creating a lag between TTS generation and visual feedback. This function bridges the gap by patching results in real-time.

Where are the TTS workarounds implemented in the OpenMAIC codebase?

The playback workaround lives in lib/playback/engine.ts within the speech playback functions, while the synchronization logic resides in lib/workbench/tts-stage-sync.ts. The test suite in tests/workbench/tts-stage-sync.test.ts validates the scene synchronization behavior, and components/workbench/chat/tool-presentation.ts handles the UI presentation layer for TTS-generated audio.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →