mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
feat(audio): audio output device selection (closes #169)
Adds the ability to choose which audio output device Psysonic plays
through — useful for USB DACs, dedicated soundcards, and multi-device setups.
Rust (audio.rs):
- open_stream_for_device_and_rate(device_name, rate): opens a named
device by cpal name, falls back to system default if not found
- AudioEngine.selected_device: persists the chosen device across
stream reopens so hi-res rate switches stay on the right device
- audio_list_devices: Tauri command — returns all output device names
- audio_set_device: Tauri command — switches device immediately,
drops old sinks, emits audio:device-changed
- start_device_watcher: now handles two cases:
1. No pinned device + system default changed → reopen on new default
2. Pinned device disappeared (DAC unplugged) → clear selected_device,
fall back to system default, emit audio:device-reset
Frontend:
- authStore: audioOutputDevice (string | null), persisted
- playerStore: applies stored device on cold start
- App.tsx: listens to audio:device-reset, clears authStore device
and restarts playback on the fallback device
- Settings → Audio tab: device dropdown at top (above Hi-Res and EQ),
uses CustomSelect (portal-based, styled), all 8 locales
- CustomSelect: added disabled prop
Note: exclusive mode (WASAPI exclusive, CoreAudio exclusive) is out
of scope for now.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+20
@@ -437,6 +437,26 @@ function TauriEventBridge() {
|
||||
return () => { unlisten?.(); };
|
||||
}, []);
|
||||
|
||||
// Pinned output device was unplugged — Rust already fell back to system default.
|
||||
// Clear the stored device so the Settings dropdown resets to "System Default".
|
||||
useEffect(() => {
|
||||
let unlisten: (() => void) | undefined;
|
||||
listen('audio:device-reset', () => {
|
||||
useAuthStore.getState().setAudioOutputDevice(null);
|
||||
const { currentTrack, currentTime, isPlaying, playTrack, resetAudioPause } = usePlayerStore.getState();
|
||||
if (!currentTrack) return;
|
||||
if (isPlaying) {
|
||||
const pos = currentTime;
|
||||
const dur = currentTrack.duration || 1;
|
||||
playTrack(currentTrack);
|
||||
setTimeout(() => usePlayerStore.getState().seek(pos / dur), 600);
|
||||
} else {
|
||||
resetAudioPause();
|
||||
}
|
||||
}).then(u => { unlisten = u; });
|
||||
return () => { unlisten?.(); };
|
||||
}, []);
|
||||
|
||||
// Sync tray-icon visibility with the user's stored setting.
|
||||
// Runs once on mount (initial sync) and again whenever the setting changes.
|
||||
const showTrayIcon = useAuthStore(s => s.showTrayIcon);
|
||||
|
||||
Reference in New Issue
Block a user