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

  1. Gate on listOutputDevices() — on web it returns [], so skip device routing.
  2. Fall back to default HTMLAudioElement playback on web instead of playToDevices().
  3. 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

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


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/7edb8fe638ebd52e. Report an issue: GitHub.