{"record":{"id":"7edb8fe638ebd52e","repo":"jamiepine/voicebox","slug":"native-audio-device-routing-is-only-available-in-t","errorCode":null,"errorMessage":"Native audio device routing is only available in the desktop app.","messagePattern":"Native audio device routing is only available in the desktop app\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/platform/audio.ts","lineNumber":21,"sourceCode":"export const webAudio: PlatformAudio = {\n  async isSystemAudioSupported(): Promise<boolean> {\n    return false; // System audio capture not supported in web\n  },\n\n  async startSystemAudioCapture(_maxDurationSecs: number): Promise<void> {\n    throw new Error('System audio capture is only available in the desktop app.');\n  },\n\n  async stopSystemAudioCapture(): Promise<Blob> {\n    throw new Error('System audio capture is only available in the desktop app.');\n  },\n\n  async listOutputDevices(): Promise<AudioDevice[]> {\n    return []; // No native device routing in web\n  },\n\n  async playToDevices(_audioData: Uint8Array, _deviceIds: string[]): Promise<void> {\n    throw new Error('Native audio device routing is only available in the desktop app.');\n  },\n\n  stopPlayback(): void {\n    // No-op for web\n  },\n};\n","sourceCodeStart":3,"sourceCodeEnd":28,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/web/src/platform/audio.ts#L3-L28","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait platform.audio.playToDevices(buf, deviceIds);\n// after\nconst devices = await platform.audio.listOutputDevices();\nif (devices.length === 0) {\n  await playWithAudioElement(buf); // web fallback\n  return;\n}\nawait platform.audio.playToDevices(buf, deviceIds);","handlingStrategy":"validation","validationCode":"async function routePlayback(audio: PlatformAudio, data: Uint8Array, ids: string[]) {\n  const devices = await audio.listOutputDevices();\n  if (devices.length === 0 || ids.length === 0) {\n    await playWithDefaultAudioElement(data); // web fallback\n    return;\n  }\n  await audio.playToDevices(data, ids);\n}","typeGuard":null,"tryCatchPattern":"const devices = await platform.audio.listOutputDevices();\nif (devices.length === 0) {\n  await playWithDefaultAudioElement(buf);\n  return;\n}\ntry {\n  await platform.audio.playToDevices(buf, deviceIds);\n} catch (e) {\n  toast({ title: 'Device playback failed', description: (e as Error).message });\n}","preventionTips":["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."],"tags":["platform","audio","web","capability","typescript","desktop-only"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}