fix(audio): return format_id from play_track and set device name for backend system

1. play_track now returns PlayTrackResult with format_id from Qobuz API
   - Returns actual format (5=MP3, 6=FLAC16, 7=FLAC24, 27=Hi-Res)
   - Returns None for cache hits (format unknown)

2. Frontend updates track.format based on format_id
   - QualityBadge can now correctly show "MP3" when format_id=5
   - Previously badge showed "CD" even for MP3 because format was empty

3. Device name now set after backend system init
   - PipeWire and ALSA backends now set current_device from settings
   - Fixes tooltip showing no device when using backend system
This commit is contained in:
vicrodh committed 2026-02-01 04:27:23 -06:00
1 parent e08593af41
commit 52bde38d9f
3 files changed
+68 -10

No files matched your search

+17 -6
View File
@@ -24,6 +24,14 @@ fn parse_quality(quality_str: Option<&str>) -> Quality {
}
}
/// Result from play_track command with format info
#[derive(serde::Serialize)]
pub struct PlayTrackResult {
/// The actual format_id returned by Qobuz (5=MP3, 6=FLAC 16-bit, 7=24-bit, 27=Hi-Res)
/// None when playing from cache (format unknown)
pub format_id: Option<u32>,
}
/// Play a track by ID (with caching support)
#[tauri::command]
pub async fn play_track(
@@ -33,7 +41,7 @@ pub async fn play_track(
state: State<'_, AppState>,
offline_cache: State<'_, OfflineCacheState>,
audio_settings: State<'_, AudioSettingsState>,
) -> Result<(), String> {
) -> Result<PlayTrackResult, String> {
let preferred_quality = parse_quality(quality.as_deref());
log::info!(
"Command: play_track {} (duration: {:?}s, quality_str={:?}, parsed={:?}, format_id={})",
@@ -73,7 +81,7 @@ pub async fn play_track(
skip_prefetch,
);
return Ok(());
return Ok(PlayTrackResult { format_id: None });
}
}
}
@@ -100,7 +108,7 @@ pub async fn play_track(
skip_prefetch,
);
return Ok(());
return Ok(PlayTrackResult { format_id: None });
}
// Check if track is in playback cache (L2 - disk)
@@ -128,7 +136,7 @@ pub async fn play_track(
skip_prefetch,
);
return Ok(());
return Ok(PlayTrackResult { format_id: None });
}
}
@@ -211,6 +219,9 @@ pub async fn play_track(
}
});
// Capture format_id before returning
let actual_format_id = stream_url.format_id;
// Prefetch next track in background
spawn_prefetch(
state.client.clone(),
@@ -220,7 +231,7 @@ pub async fn play_track(
streaming_only,
);
return Ok(());
return Ok(PlayTrackResult { format_id: Some(actual_format_id) });
}
// Standard download path (streaming disabled)
@@ -255,7 +266,7 @@ pub async fn play_track(
streaming_only,
);
Ok(())
Ok(PlayTrackResult { format_id: Some(stream_url.format_id) })
}
/// Prefetch a track into the in-memory cache without starting playback
+22 -3
View File
@@ -682,7 +682,11 @@ impl Player {
log::info!("Initializing backend system with {}Hz/{}ch", sample_rate, channels);
match try_init_stream_with_backend(&settings, sample_rate, channels) {
Some(Ok(stream_type)) => {
log::info!("Audio output initialized via backend system at {}Hz", sample_rate);
// Set device name from settings for backend system
let device_name = settings.output_device.clone()
.unwrap_or_else(|| "Default".to_string());
log::info!("Audio output initialized via backend system at {}Hz (device: {})", sample_rate, device_name);
state.set_current_device(Some(device_name));
return Some(stream_type);
}
Some(Err(e)) => {
@@ -854,7 +858,13 @@ impl Player {
let stream_result = if let Some(settings) = thread_settings.lock().ok() {
match try_init_stream_with_backend(&settings, sample_rate, channels) {
Some(result) => {
// Backend system handled it - no need for CPAL device search
// Backend system handled it - set device name from settings
if result.is_ok() {
let device_name = settings.output_device.clone()
.unwrap_or_else(|| "Default".to_string());
log::info!("Backend system using device: {}", device_name);
thread_state.set_current_device(Some(device_name));
}
result
}
None => {
@@ -1151,7 +1161,16 @@ impl Player {
let stream_result = if let Some(settings) = thread_settings.lock().ok() {
match try_init_stream_with_backend(&settings, sample_rate, channels) {
Some(result) => result,
Some(result) => {
// Set device name from settings for backend system
if result.is_ok() {
let device_name = settings.output_device.clone()
.unwrap_or_else(|| "Default".to_string());
log::info!("Streaming backend using device: {}", device_name);
thread_state.set_current_device(Some(device_name));
}
result
}
None => {
log::info!("Backend system not configured, using legacy CPAL path");
let device = if let Some(ref name) = *current_device_name {
+29 -1
View File
@@ -49,6 +49,26 @@ import {
// ============ Types ============
/** Result from play_track command */
interface PlayTrackResult {
format_id: number | null;
}
/**
* Convert Qobuz format_id to format string for QualityBadge
* 5=MP3, 6=FLAC 16-bit (CD), 7=FLAC 24-bit (Hi-Res), 27=FLAC 24-bit Hi-Res+
*/
function formatIdToString(formatId: number | null): string | undefined {
if (formatId === null) return undefined;
switch (formatId) {
case 5: return 'mp3';
case 6: return 'flac';
case 7: return 'flac24';
case 27: return 'flac24';
default: return undefined;
}
}
export interface PlayTrackOptions {
isLocal?: boolean;
showLoadingToast?: boolean;
@@ -128,11 +148,19 @@ export async function playTrack(
if (isLocal) {
await invoke('library_play_track', { trackId: track.id });
} else {
await invoke('play_track', {
const result = await invoke<PlayTrackResult>('play_track', {
trackId: track.id,
durationSecs: track.duration,
quality: getStreamingQuality()
});
// Update track format based on actual stream format_id from Qobuz
const actualFormat = formatIdToString(result.format_id);
if (actualFormat) {
track.format = actualFormat;
// Re-set current track to update the UI with actual format
setCurrentTrack(track);
}
}
}