fix(audio): default to shared System output and resolve Auto to a concrete backend (#470)

OOTB playback froze (seekbar advancing, no audio) on hosts where the
configured backend was not usable — e.g. PipeWire as the Linux default but
`pactl` absent (minimal Debian/Ubuntu). "Auto" (a null backend_type) fell
through to a legacy CPAL path that forced the track rate onto the shared
default device, producing a suspended/silent stream and leaving a
process-wide stuck audio handle that survived Reset.

- Add a cross-platform "System" backend (CpalDefaultBackend now on Linux
  too): opens the OS default output at its negotiated rate with rodio
  resampling and an explicit ~100ms CPAL buffer (MixerDeviceSink has zero
  internal buffering, so the bare default underran and stayed suspended).
  Shared, no exclusivity, no `pactl` — mixes with other apps like any
  normal player.
- Make System the OOTB default and what Reset lands on; seed it on first
  run. Drop the per-startup #375 NULL->PipeWire backfill migration.
- Resolve a null backend_type to System in the player so the legacy CPAL
  path is never reached (covers legacy installs and headless callers).
- "Auto" in Settings is now a resolve-and-set action: it picks the best
  available backend (PipeWire if available, else System), stores that
  concrete value and updates the dropdown + device picker, so backend_type
  is never null.
- Explicit PipeWire (DAC passthrough) and ALSA Direct (exclusive) configs
  are untouched and remain bit-perfect.

cc
This commit is contained in:
vicrodh committed 2026-05-24 21:13:11 -06:00
1 parent bf56eefd7a
commit a0051ab513
10 files changed
+111 -75

No files matched your search

+17 -2
View File
@@ -3358,11 +3358,26 @@
}
async function handleBackendChange(backendName: string) {
// "Auto" is a resolve-and-set action, not a persisted mode. Pick the best
// available backend — PipeWire if available (best quality + device routing),
// otherwise System (always works) — and continue exactly as if the user had
// selected that concrete backend. This keeps backend_type always concrete: it
// never stays null, which on Linux fell through to the legacy CPAL path that
// froze the seekbar with no audio and left a process-wide stuck audio handle
// surviving Reset (#470). The dropdown then shows what was chosen, and the
// device picker lets the user correct the DAC if the auto pick isn't theirs.
if (backendName === 'Auto') {
const pipewire = availableBackends.find(
b => b.backend_type === 'PipeWire' && b.is_available
);
const system = availableBackends.find(b => b.backend_type === 'SystemDefault');
backendName = (pipewire ?? system)?.name ?? 'System';
}
selectedBackend = backendName;
// Map UI name to backend type
// Map UI name to backend type (always concrete after the Auto resolution above)
const backend = availableBackends.find(b => b.name === backendName);
const backendType = backendName === 'Auto' ? null : backend?.backend_type ?? null;
const backendType = backend?.backend_type ?? null;
// Auto-disable incompatible features
// DAC Passthrough and PW force bit-perfect only work with PipeWire
+1 -1
View File
@@ -2145,7 +2145,7 @@
"syncAudioOnStartup": "Audio-Einstellungen beim Start synchronisieren",
"syncAudioOnStartupDesc": "Audiokonfiguration beim App-Start aus der Datenbank neu laden. Aktiviere dies, wenn dein Audiogerät oder deine Einstellungen nach dem Starten falsch erscheinen.",
"audioBackend": "Audio-Backend",
"audioBackendDesc": "Wähle Audiosystem: Auto (empfohlen), PipeWire (modern), ALSA Direct (bitperfekt, exklusiv) oder PulseAudio (veraltet).",
"audioBackendDesc": "Wähle Audiosystem: System (Standard — geteilte Ausgabe wie jede App, kein Bitperfect), Auto (empfohlen — wählt das beste verfügbare Backend; ggf. musst du noch deinen DAC auswählen), PipeWire (modern), ALSA Direct (bitperfekt, exklusiv) oder PulseAudio (veraltet).",
"currentSampleRate": "Aktuelle Abtastrate",
"noActivePlayback": "Keine aktive Wiedergabe",
"loadingDevices": "Geräte werden geladen...",
+1 -1
View File
@@ -2145,7 +2145,7 @@
"syncAudioOnStartup": "Sync audio settings on startup",
"syncAudioOnStartupDesc": "Reload audio configuration from database when the app starts. Enable if your audio device or settings appear incorrect after launching.",
"audioBackend": "Audio Backend",
"audioBackendDesc": "Choose audio system: Auto (recommended), PipeWire (modern), ALSA Direct (bit-perfect, exclusive), or PulseAudio (legacy).",
"audioBackendDesc": "Choose audio system: System (default — shared output like any app, no bit-perfect), Auto (recommended — picks the best available backend; you may still need to select your DAC), PipeWire (modern), ALSA Direct (bit-perfect, exclusive), or PulseAudio (legacy).",
"currentSampleRate": "Current Sample Rate",
"noActivePlayback": "No active playback",
"loadingDevices": "Loading devices...",
+1 -1
View File
@@ -2145,7 +2145,7 @@
"syncAudioOnStartup": "Sincronizar configuración de audio al iniciar",
"syncAudioOnStartupDesc": "Recarga la configuración de audio desde la base de datos al iniciar la app. Actívalo si tu dispositivo o configuración de audio aparecen incorrectos al abrir.",
"audioBackend": "Backend de Audio",
"audioBackendDesc": "Elige el sistema de audio: Auto (recomendado), PipeWire (moderno), ALSA Direct (bit-perfect, exclusivo), o PulseAudio (legacy).",
"audioBackendDesc": "Elige el sistema de audio: System (predeterminado — salida compartida como cualquier app, sin bit-perfect), Auto (recomendado — elige el mejor backend disponible; quizá aún debas seleccionar tu DAC), PipeWire (moderno), ALSA Direct (bit-perfect, exclusivo), o PulseAudio (legacy).",
"currentSampleRate": "Tasa de Muestreo Actual",
"noActivePlayback": "Sin reproducción activa",
"loadingDevices": "Cargando dispositivos...",
+1 -1
View File
@@ -2145,7 +2145,7 @@
"syncAudioOnStartup": "Synchroniser la configuration audio au démarrage",
"syncAudioOnStartupDesc": "Recharge la configuration audio depuis la base de données au démarrage de l'application. Activez si votre appareil ou vos paramètres audio semblent incorrects après le lancement.",
"audioBackend": "Backend audio",
"audioBackendDesc": "Choisissez le système audio : Auto (recommandé), PipeWire (moderne), ALSA Direct (bit-perfect, exclusif) ou PulseAudio (ancien).",
"audioBackendDesc": "Choisissez le système audio : System (par défaut — sortie partagée comme toute app, pas de bit-perfect), Auto (recommandé — choisit le meilleur backend disponible ; vous devrez peut-être encore sélectionner votre DAC), PipeWire (moderne), ALSA Direct (bit-perfect, exclusif) ou PulseAudio (ancien).",
"currentSampleRate": "Taux d'échantillonnage actuel",
"noActivePlayback": "Pas de lecture en cours",
"loadingDevices": "Chargement des appareils...",
+1 -1
View File
@@ -2145,7 +2145,7 @@
"syncAudioOnStartup": "Sincronizar configurações de áudio na inicialização",
"syncAudioOnStartupDesc": "Recarregar configuração de áudio do banco de dados quando o app iniciar. Ative se o dispositivo ou configurações de áudio parecerem incorretos após o lançamento.",
"audioBackend": "Backend de Áudio",
"audioBackendDesc": "Escolha o sistema de áudio: Auto (recomendado), PipeWire (moderno), ALSA Direct (bit-perfect, exclusivo) ou PulseAudio (legado).",
"audioBackendDesc": "Escolha o sistema de áudio: System (padrão — saída compartilhada como qualquer app, sem bit-perfect), Auto (recomendado — escolhe o melhor backend disponível; talvez ainda precise selecionar seu DAC), PipeWire (moderno), ALSA Direct (bit-perfect, exclusivo) ou PulseAudio (legado).",
"currentSampleRate": "Taxa de Amostragem Atual",
"noActivePlayback": "Nenhuma reprodução ativa",
"loadingDevices": "Carregando dispositivos...",