Add audio device enumeration and streaming quality persistence
- Add get_audio_devices command to list available output devices - Show real audio devices in Settings dropdown - Persist streaming quality preference in localStorage - Persist 'prefer highest available' setting
This commit is contained in:
1 parent
9b8e1352eb
commit
9d040fee55
3 files changed
+89
-3
No files matched your search
@@ -239,3 +239,38 @@ pub fn seek(position: u64, state: State<'_, AppState>) -> Result<(), String> {
|
|||||||
pub fn get_playback_state(state: State<'_, AppState>) -> Result<PlaybackState, String> {
|
pub fn get_playback_state(state: State<'_, AppState>) -> Result<PlaybackState, String> {
|
||||||
state.player.get_state()
|
state.player.get_state()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Audio device information
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct AudioDevice {
|
||||||
|
pub name: String,
|
||||||
|
pub is_default: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get available audio output devices
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_audio_devices() -> Result<Vec<AudioDevice>, String> {
|
||||||
|
log::info!("Command: get_audio_devices");
|
||||||
|
|
||||||
|
use rodio::cpal::traits::{DeviceTrait, HostTrait};
|
||||||
|
|
||||||
|
let host = rodio::cpal::default_host();
|
||||||
|
|
||||||
|
let default_device_name = host
|
||||||
|
.default_output_device()
|
||||||
|
.and_then(|d| d.name().ok());
|
||||||
|
|
||||||
|
let devices: Vec<AudioDevice> = host
|
||||||
|
.output_devices()
|
||||||
|
.map_err(|e| format!("Failed to enumerate devices: {}", e))?
|
||||||
|
.filter_map(|device| {
|
||||||
|
device.name().ok().map(|name| {
|
||||||
|
let is_default = default_device_name.as_ref().map(|d| d == &name).unwrap_or(false);
|
||||||
|
AudioDevice { name, is_default }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
log::info!("Found {} audio output devices", devices.len());
|
||||||
|
Ok(devices)
|
||||||
|
}
|
||||||
@@ -100,6 +100,7 @@ pub fn run() {
|
|||||||
commands::seek,
|
commands::seek,
|
||||||
commands::get_playback_state,
|
commands::get_playback_state,
|
||||||
commands::set_media_metadata,
|
commands::set_media_metadata,
|
||||||
|
commands::get_audio_devices,
|
||||||
// Queue commands
|
// Queue commands
|
||||||
commands::add_to_queue,
|
commands::add_to_queue,
|
||||||
commands::add_tracks_to_queue,
|
commands::add_tracks_to_queue,
|
||||||
|
|||||||
@@ -22,12 +22,21 @@
|
|||||||
fetching_count: number;
|
fetching_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AudioDevice {
|
||||||
|
name: string;
|
||||||
|
is_default: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
let { onBack, onLogout, userName = 'User', userEmail = '', subscription = 'Qobuz' }: Props = $props();
|
let { onBack, onLogout, userName = 'User', userEmail = '', subscription = 'Qobuz' }: Props = $props();
|
||||||
|
|
||||||
// Cache state
|
// Cache state
|
||||||
let cacheStats = $state<CacheStats | null>(null);
|
let cacheStats = $state<CacheStats | null>(null);
|
||||||
let isClearing = $state(false);
|
let isClearing = $state(false);
|
||||||
|
|
||||||
|
// Audio device state
|
||||||
|
let audioDevices = $state<AudioDevice[]>([]);
|
||||||
|
let audioDeviceOptions = $derived(['System Default', ...audioDevices.map(d => d.name)]);
|
||||||
|
|
||||||
// Theme mapping: display name -> data-theme value
|
// Theme mapping: display name -> data-theme value
|
||||||
const themeMap: Record<string, string> = {
|
const themeMap: Record<string, string> = {
|
||||||
'Dark': '',
|
'Dark': '',
|
||||||
@@ -65,14 +74,55 @@
|
|||||||
|
|
||||||
// Load saved settings on mount
|
// Load saved settings on mount
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
// Load theme
|
||||||
const savedTheme = localStorage.getItem('qbz-theme') || '';
|
const savedTheme = localStorage.getItem('qbz-theme') || '';
|
||||||
theme = themeReverseMap[savedTheme] || 'Dark';
|
theme = themeReverseMap[savedTheme] || 'Dark';
|
||||||
applyTheme(savedTheme);
|
applyTheme(savedTheme);
|
||||||
|
|
||||||
|
// Load streaming quality preference
|
||||||
|
const savedQuality = localStorage.getItem('qbz-streaming-quality');
|
||||||
|
if (savedQuality) {
|
||||||
|
streamingQuality = savedQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load prefer highest setting
|
||||||
|
const savedPreferHighest = localStorage.getItem('qbz-prefer-highest');
|
||||||
|
if (savedPreferHighest !== null) {
|
||||||
|
preferHighest = savedPreferHighest === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
// Load cache stats
|
// Load cache stats
|
||||||
loadCacheStats();
|
loadCacheStats();
|
||||||
|
|
||||||
|
// Load audio devices
|
||||||
|
loadAudioDevices();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleQualityChange(quality: string) {
|
||||||
|
streamingQuality = quality;
|
||||||
|
localStorage.setItem('qbz-streaming-quality', quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePreferHighestChange(enabled: boolean) {
|
||||||
|
preferHighest = enabled;
|
||||||
|
localStorage.setItem('qbz-prefer-highest', String(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAudioDevices() {
|
||||||
|
try {
|
||||||
|
const devices = await invoke<AudioDevice[]>('get_audio_devices');
|
||||||
|
audioDevices = devices;
|
||||||
|
|
||||||
|
// Set current device to the default one
|
||||||
|
const defaultDevice = devices.find(d => d.is_default);
|
||||||
|
if (defaultDevice) {
|
||||||
|
outputDevice = defaultDevice.name;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load audio devices:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadCacheStats() {
|
async function loadCacheStats() {
|
||||||
try {
|
try {
|
||||||
cacheStats = await invoke<CacheStats>('get_cache_stats');
|
cacheStats = await invoke<CacheStats>('get_cache_stats');
|
||||||
@@ -154,18 +204,18 @@
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
value={streamingQuality}
|
value={streamingQuality}
|
||||||
options={['MP3', 'CD Quality', 'Hi-Res', 'Hi-Res+']}
|
options={['MP3', 'CD Quality', 'Hi-Res', 'Hi-Res+']}
|
||||||
onchange={(v) => (streamingQuality = v)}
|
onchange={handleQualityChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<span class="setting-label">Prefer highest available</span>
|
<span class="setting-label">Prefer highest available</span>
|
||||||
<Toggle enabled={preferHighest} onchange={(v) => (preferHighest = v)} />
|
<Toggle enabled={preferHighest} onchange={handlePreferHighestChange} />
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
<div class="setting-row">
|
||||||
<span class="setting-label">Output Device</span>
|
<span class="setting-label">Output Device</span>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
value={outputDevice}
|
value={outputDevice}
|
||||||
options={['System Default', 'Built-in Speakers', 'USB DAC', 'HDMI Output']}
|
options={audioDeviceOptions.length > 1 ? audioDeviceOptions : ['System Default']}
|
||||||
onchange={(v) => (outputDevice = v)}
|
onchange={(v) => (outputDevice = v)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user