jamiepine/voicebox · error · Error
Native audio device routing is only available in the desktop
Error message
Native audio device routing is only available in the desktop app.
What it means
Thrown by webAudio.playToDevices() — the web stub for native output-device routing. Browsers cannot route synthesized audio to arbitrary output devices by ID, so this method throws unconditionally. listOutputDevices() returns [] on web, signalling no native routing is available.
Source
Thrown at web/src/platform/audio.ts:21
export const webAudio: PlatformAudio = {
async isSystemAudioSupported(): Promise<boolean> {
return false; // System audio capture not supported in web
},
async startSystemAudioCapture(_maxDurationSecs: number): Promise<void> {
throw new Error('System audio capture is only available in the desktop app.');
},
async stopSystemAudioCapture(): Promise<Blob> {
throw new Error('System audio capture is only available in the desktop app.');
},
async listOutputDevices(): Promise<AudioDevice[]> {
return []; // No native device routing in web
},
async playToDevices(_audioData: Uint8Array, _deviceIds: string[]): Promise<void> {
throw new Error('Native audio device routing is only available in the desktop app.');
},
stopPlayback(): void {
// No-op for web
},
};
View on GitHub (pinned to 51f49dea19)
Solutions
- Gate on listOutputDevices() — on web it returns [], so skip device routing.
- Fall back to default HTMLAudioElement playback on web instead of playToDevices().
- Hide the device-picker UI when listOutputDevices() is empty.
Example fix
// before
await platform.audio.playToDevices(buf, deviceIds);
// after
const devices = await platform.audio.listOutputDevices();
if (devices.length === 0) {
await playWithAudioElement(buf); // web fallback
return;
}
await platform.audio.playToDevices(buf, deviceIds); Defensive patterns
Strategy: validation
Validate before calling
async function routePlayback(audio: PlatformAudio, data: Uint8Array, ids: string[]) {
const devices = await audio.listOutputDevices();
if (devices.length === 0 || ids.length === 0) {
await playWithDefaultAudioElement(data); // web fallback
return;
}
await audio.playToDevices(data, ids);
} Try / catch
const devices = await platform.audio.listOutputDevices();
if (devices.length === 0) {
await playWithDefaultAudioElement(buf);
return;
}
try {
await platform.audio.playToDevices(buf, deviceIds);
} catch (e) {
toast({ title: 'Device playback failed', description: (e as Error).message });
} Prevention
- Treat an empty listOutputDevices() result as 'no native routing'.
- Hide the device picker when no devices are enumerated.
- Provide a default-output fallback path for web builds.
When it happens
Trigger: A multi-device playback feature (select target speakers by deviceId) is invoked on the web build. Code calls playToDevices(audioData, deviceIds) without first confirming listOutputDevices() returned non-empty.
Common situations: Porting the desktop 'play to specific device' feature to web, where only the default output device is reachable. A voice that was assigned a non-default device on desktop is replayed in the web UI.
Related errors
- System audio capture is only available in the desktop app.
- Failed to resolve save path from dialog
- e instanceof Error ? e.message : errorMessage
- HTTP ${res.status}
- Jupiter HTTP ${res.status}
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/7edb8fe638ebd52e.
Report an issue: GitHub.