odysseus-dev/odysseus · warning · Error
Browser TTS not supported
Error message
Browser TTS not supported
What it means
Thrown when the user clicks TTS preview with provider 'browser' but window.speechSynthesis is absent. It is a hard feature-detection failure, not a network or config error — the browser genuinely lacks the Web Speech API.
Source
Thrown at static/js/settings.js:921
var previewPlaying = false;
function resetPreview() { previewPlaying = false; previewBtn.textContent = 'Preview'; previewBtn.style.borderColor = ''; }
previewBtn.addEventListener('click', async function() {
if (previewPlaying) {
if (previewAudio) { previewAudio.pause(); previewAudio = null; }
window.speechSynthesis.cancel();
resetPreview(); return;
}
var prov = provSel.value;
if (prov === 'disabled') {
ttsMsg.textContent = 'Select a provider first'; ttsMsg.style.color = 'var(--red, #e55)';
setTimeout(function() { ttsMsg.textContent = ''; }, 2000); return;
}
var testText = 'Hello, this is a test of text to speech.';
previewPlaying = true; previewBtn.textContent = 'Loading...';
try {
if (prov === 'browser') {
if (!('speechSynthesis' in window)) throw new Error('Browser TTS not supported');
var utt = new SpeechSynthesisUtterance(testText);
var voiceVal = getVoice();
if (voiceVal) {
var voices = window.speechSynthesis.getVoices();
var target = voiceVal.toLowerCase();
var match = voices.find(function(v) { return v.name.toLowerCase() === target; }) ||
voices.find(function(v) { return v.name.toLowerCase().includes(target); });
if (match) utt.voice = match;
}
utt.rate = parseFloat(speedSelect.value) || 1;
previewBtn.textContent = 'Stop'; previewBtn.style.borderColor = 'var(--red, #e55)';
await new Promise(function(resolve, reject) {
utt.onend = resolve;
utt.onerror = function(e) { reject(new Error('Browser TTS: ' + e.error)); };
window.speechSynthesis.speak(utt);
});
} else {
var res = await fetch('/api/tts/synthesize', {View on GitHub (pinned to f9235ebbf1)
Solutions
- Hide or disable the 'browser' provider option when 'speechSynthesis' in window is false, before the user can pick it
- Fall back to the server-side /api/tts/synthesize provider for these browsers
- Detect once at settings-render time and show a hint instead of failing at preview time
- Example guard: if (!('speechSynthesis' in window)) disableOption()
Example fix
// before
if (!('speechSynthesis' in window)) throw new Error('Browser TTS not supported');
// after
if (!('speechSynthesis' in window)) {
ttsMsg.textContent = 'Browser TTS not supported — use a server TTS provider';
ttsMsg.style.color = 'var(--red, #e55)';
return;
} Defensive patterns
Strategy: validation
Validate before calling
const browserTtsAvailable = 'speechSynthesis' in window && typeof window.speechSynthesis.speak === 'function';
if (!browserTtsAvailable) { /* disable 'browser' provider option in provSel */ } Type guard
function supportsBrowserTts() {
return typeof window !== 'undefined' &&
'speechSynthesis' in window &&
typeof window.speechSynthesis.speak === 'function';
} Try / catch
try { if (!supportsBrowserTts()) { showHint('Browser TTS unsupported — pick a server provider'); return; } ... } catch (e) { ttsMsg.textContent = 'Preview failed: ' + e.message; } Prevention
- Feature-detect at render time, not at click time
- Offer the server TTS provider as fallback
- Test in the lowest-common WebView you support
- Check getVoices() non-empty before promising browser TTS
When it happens
Trigger: Selecting the 'browser' TTS provider and clicking preview in any browser without speechSynthesis: older/NIghtly builds, some WebViews/embedded browsers (Electron without the flag, kiosk WebViews), or speechSynthesis removed at runtime.
Common situations: App loaded inside an embedded WebView; user on an exotic or locked-down browser; CI/screenshot environments; the API exists but getVoices() returns empty on some platforms (related but distinct failure).
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/14ab7e6ce796765d.
Report an issue: GitHub.