mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
refactor(audio-tab): I.3 — split AudioTab.tsx 521 → 97 LOC across 6 files (#674)
* refactor(audio-tab): extract useAudioDevicesProbe hook Pull the device-list state, refreshAudioDevices callback, mount probe, and the audio:device-changed / audio:device-reset listener wiring into hooks/useAudioDevicesProbe.ts. macOS short-circuit lives in the hook. AudioTab.tsx: 521 → 463 LOC. * refactor(audio-tab): extract AudioOutputDeviceSection Pull the audio output device picker (macOS notice + CustomSelect + refresh button) into components/settings/audio/AudioOutputDeviceSection.tsx. AudioTab.tsx: 463 → 418 LOC. * refactor(audio-tab): extract NormalizationBlock Pull the engine picker (Off / ReplayGain / LUFS) and the engine-specific config blocks (RG mode + pre-gain + fallback; LUFS target + pre-analysis attenuation with reset) into components/settings/audio/NormalizationBlock.tsx. AudioTab.tsx: 418 → 267 LOC. * refactor(audio-tab): extract PlaybackBehaviorBlock Pull Crossfade ↔ Gapless mutually-exclusive toggles + Preserve Play Next Order into components/settings/audio/PlaybackBehaviorBlock.tsx. The crossfade-seconds slider only renders while crossfade is the active mode. AudioTab.tsx: 267 → 201 LOC. * refactor(audio-tab): extract TrackPreviewsSection Pull the track previews subsection (master toggle, per-location grid, start-ratio slider, duration slider) into components/settings/audio/TrackPreviewsSection.tsx. AudioTab.tsx: 201 → 97 LOC.
This commit is contained in:
committed by
GitHub
parent
f14c8f21e6
commit
59772db5ee
@@ -0,0 +1,100 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { IS_MACOS } from '../utils/platform';
|
||||
import { sortAudioDeviceIds } from '../utils/audioDeviceLabels';
|
||||
import { showToast } from '../utils/toast';
|
||||
|
||||
interface UseAudioDevicesProbeResult {
|
||||
audioDevices: string[];
|
||||
osDefaultAudioDeviceId: string | null;
|
||||
deviceSwitching: boolean;
|
||||
devicesLoading: boolean;
|
||||
setDeviceSwitching: (v: boolean) => void;
|
||||
refreshAudioDevices: (opts?: { silent?: boolean }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drive the audio-output-device picker in the audio settings tab. Calls
|
||||
* the Rust side to list devices + the current OS default, canonicalises
|
||||
* the persisted selection (filenames may have shifted between runs), and
|
||||
* re-runs whenever the backend reopens the stream
|
||||
* (`audio:device-changed` / `audio:device-reset`).
|
||||
*
|
||||
* macOS short-circuits — the audio stream is pinned to the system default
|
||||
* there (see `audioOutputDeviceMacNotice`) and the picker UI is hidden.
|
||||
*/
|
||||
export function useAudioDevicesProbe(t: TFunction): UseAudioDevicesProbeResult {
|
||||
const [audioDevices, setAudioDevices] = useState<string[]>([]);
|
||||
const [osDefaultAudioDeviceId, setOsDefaultAudioDeviceId] = useState<string | null>(null);
|
||||
const [deviceSwitching, setDeviceSwitching] = useState(false);
|
||||
const [devicesLoading, setDevicesLoading] = useState(false);
|
||||
|
||||
const refreshAudioDevices = useCallback((opts?: { silent?: boolean }) => {
|
||||
const silent = !!opts?.silent;
|
||||
if (!silent) setDevicesLoading(true);
|
||||
const listP = invoke<string[]>('audio_list_devices').catch((e) => {
|
||||
console.error(e);
|
||||
showToast(t('settings.audioOutputDeviceListError'), 5000, 'error');
|
||||
return [] as string[];
|
||||
});
|
||||
const defP = invoke<string | null>('audio_default_output_device_name').catch(() => null);
|
||||
Promise.all([listP, defP])
|
||||
.then(async ([devices, osDefault]) => {
|
||||
let canon: string | null = null;
|
||||
try {
|
||||
canon = await invoke<string | null>('audio_canonicalize_selected_device');
|
||||
if (canon) useAuthStore.getState().setAudioOutputDevice(canon);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const finalList = canon
|
||||
? await invoke<string[]>('audio_list_devices').catch(() => devices)
|
||||
: devices;
|
||||
const defId = osDefault ?? null;
|
||||
setAudioDevices(sortAudioDeviceIds(finalList, defId));
|
||||
setOsDefaultAudioDeviceId(defId);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!silent) setDevicesLoading(false);
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (IS_MACOS) return;
|
||||
refreshAudioDevices();
|
||||
}, [refreshAudioDevices]);
|
||||
|
||||
useEffect(() => {
|
||||
if (IS_MACOS) return;
|
||||
let cancelled = false;
|
||||
const unlisteners: Array<() => void> = [];
|
||||
(async () => {
|
||||
for (const ev of ['audio:device-changed', 'audio:device-reset'] as const) {
|
||||
const u = await listen(ev, () => {
|
||||
if (!cancelled) refreshAudioDevices({ silent: true });
|
||||
});
|
||||
if (cancelled) {
|
||||
u();
|
||||
return;
|
||||
}
|
||||
unlisteners.push(u);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
for (const u of unlisteners) u();
|
||||
};
|
||||
}, [refreshAudioDevices]);
|
||||
|
||||
return {
|
||||
audioDevices,
|
||||
osDefaultAudioDeviceId,
|
||||
deviceSwitching,
|
||||
devicesLoading,
|
||||
setDeviceSwitching,
|
||||
refreshAudioDevices,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user