fix(playback): persist streaming quality setting

Quality was hardcoded to UltraHiRes in backend. Now:
- Frontend passes quality from localStorage to play_track command
- Backend parses quality string to Quality enum
- All prefetch and stream URL calls use the user's preferred quality
This commit is contained in:
vicrodh committed 2026-01-28 11:24:11 -06:00
1 parent 700dc07014
commit a6fc264903
3 files changed
+54 -8

No files matched your search

+27 -6
View File
@@ -13,16 +13,29 @@ use crate::player::PlaybackState;
use crate::queue::QueueManager; use crate::queue::QueueManager;
use crate::AppState; use crate::AppState;
/// Convert quality string from frontend to Quality enum
fn parse_quality(quality_str: Option<&str>) -> Quality {
match quality_str {
Some("MP3") => Quality::Mp3,
Some("CD Quality") => Quality::Lossless,
Some("Hi-Res") => Quality::HiRes,
Some("Hi-Res+") => Quality::UltraHiRes,
_ => Quality::UltraHiRes, // Default to highest
}
}
/// Play a track by ID (with caching support) /// Play a track by ID (with caching support)
#[tauri::command] #[tauri::command]
pub async fn play_track( pub async fn play_track(
track_id: u64, track_id: u64,
duration_secs: Option<u64>, duration_secs: Option<u64>,
quality: Option<String>,
state: State<'_, AppState>, state: State<'_, AppState>,
offline_cache: State<'_, OfflineCacheState>, offline_cache: State<'_, OfflineCacheState>,
audio_settings: State<'_, AudioSettingsState>, audio_settings: State<'_, AudioSettingsState>,
) -> Result<(), String> { ) -> Result<(), String> {
log::info!("Command: play_track {} (duration: {:?}s)", track_id, duration_secs); let preferred_quality = parse_quality(quality.as_deref());
log::info!("Command: play_track {} (duration: {:?}s, quality: {:?})", track_id, duration_secs, preferred_quality);
// First check offline cache (persistent disk cache) // First check offline cache (persistent disk cache)
{ {
@@ -47,6 +60,7 @@ pub async fn play_track(
state.client.clone(), state.client.clone(),
state.audio_cache.clone(), state.audio_cache.clone(),
&state.queue, &state.queue,
preferred_quality,
); );
return Ok(()); return Ok(());
@@ -66,6 +80,7 @@ pub async fn play_track(
state.client.clone(), state.client.clone(),
state.audio_cache.clone(), state.audio_cache.clone(),
&state.queue, &state.queue,
preferred_quality,
); );
return Ok(()); return Ok(());
@@ -86,6 +101,7 @@ pub async fn play_track(
state.client.clone(), state.client.clone(),
state.audio_cache.clone(), state.audio_cache.clone(),
&state.queue, &state.queue,
preferred_quality,
); );
return Ok(()); return Ok(());
@@ -109,9 +125,9 @@ pub async fn play_track(
let client = state.client.lock().await; let client = state.client.lock().await;
// Get the stream URL with highest quality available // Get the stream URL with preferred quality
let stream_url = client let stream_url = client
.get_stream_url_with_fallback(track_id, Quality::UltraHiRes) .get_stream_url_with_fallback(track_id, preferred_quality)
.await .await
.map_err(|e| format!("Failed to get stream URL: {}", e))?; .map_err(|e| format!("Failed to get stream URL: {}", e))?;
@@ -162,6 +178,7 @@ pub async fn play_track(
state.client.clone(), state.client.clone(),
state.audio_cache.clone(), state.audio_cache.clone(),
&state.queue, &state.queue,
preferred_quality,
); );
return Ok(()); return Ok(());
@@ -190,6 +207,7 @@ pub async fn play_track(
state.client.clone(), state.client.clone(),
state.audio_cache.clone(), state.audio_cache.clone(),
&state.queue, &state.queue,
preferred_quality,
); );
Ok(()) Ok(())
@@ -199,10 +217,12 @@ pub async fn play_track(
#[tauri::command] #[tauri::command]
pub async fn prefetch_track( pub async fn prefetch_track(
track_id: u64, track_id: u64,
quality: Option<String>,
state: State<'_, AppState>, state: State<'_, AppState>,
offline_cache: State<'_, OfflineCacheState>, offline_cache: State<'_, OfflineCacheState>,
) -> Result<(), String> { ) -> Result<(), String> {
log::info!("Command: prefetch_track {}", track_id); let preferred_quality = parse_quality(quality.as_deref());
log::info!("Command: prefetch_track {} (quality: {:?})", track_id, preferred_quality);
let cache = state.audio_cache.clone(); let cache = state.audio_cache.clone();
@@ -236,7 +256,7 @@ pub async fn prefetch_track(
let client = state.client.lock().await; let client = state.client.lock().await;
let stream_url = client let stream_url = client
.get_stream_url_with_fallback(track_id, Quality::UltraHiRes) .get_stream_url_with_fallback(track_id, preferred_quality)
.await .await
.map_err(|e| format!("Failed to get stream URL: {}", e))?; .map_err(|e| format!("Failed to get stream URL: {}", e))?;
drop(client); drop(client);
@@ -578,6 +598,7 @@ fn spawn_prefetch(
client: Arc<Mutex<QobuzClient>>, client: Arc<Mutex<QobuzClient>>,
cache: Arc<AudioCache>, cache: Arc<AudioCache>,
queue: &QueueManager, queue: &QueueManager,
quality: Quality,
) { ) {
// Look further ahead to find Qobuz tracks in mixed playlists // Look further ahead to find Qobuz tracks in mixed playlists
let upcoming_tracks = queue.peek_upcoming(PREFETCH_LOOKAHEAD); let upcoming_tracks = queue.peek_upcoming(PREFETCH_LOOKAHEAD);
@@ -631,7 +652,7 @@ fn spawn_prefetch(
let result = async { let result = async {
let client_guard = client_clone.lock().await; let client_guard = client_clone.lock().await;
let stream_url = client_guard let stream_url = client_guard
.get_stream_url_with_fallback(track_id, Quality::UltraHiRes) .get_stream_url_with_fallback(track_id, quality)
.await .await
.map_err(|e| format!("Failed to get stream URL: {}", e))?; .map_err(|e| format!("Failed to get stream URL: {}", e))?;
drop(client_guard); drop(client_guard);
+14 -1
View File
@@ -10,6 +10,15 @@
*/ */
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
/**
* Get the preferred streaming quality from localStorage
* Returns: 'MP3', 'CD Quality', 'Hi-Res', or 'Hi-Res+' (default)
*/
function getStreamingQuality(): string {
if (typeof localStorage === 'undefined') return 'Hi-Res+';
return localStorage.getItem('qbz-streaming-quality') || 'Hi-Res+';
}
import { import {
setCurrentTrack, setCurrentTrack,
setIsPlaying, setIsPlaying,
@@ -114,7 +123,11 @@ export async function playTrack(
if (isLocal) { if (isLocal) {
await invoke('library_play_track', { trackId: track.id }); await invoke('library_play_track', { trackId: track.id });
} else { } else {
await invoke('play_track', { trackId: track.id, durationSecs: track.duration }); await invoke('play_track', {
trackId: track.id,
durationSecs: track.duration,
quality: getStreamingQuality()
});
} }
} }
+13 -1
View File
@@ -6,6 +6,14 @@
*/ */
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
/**
* Get the preferred streaming quality from localStorage
*/
function getStreamingQuality(): string {
if (typeof localStorage === 'undefined') return 'Hi-Res+';
return localStorage.getItem('qbz-streaming-quality') || 'Hi-Res+';
}
import { listen, type UnlistenFn } from '@tauri-apps/api/event'; import { listen, type UnlistenFn } from '@tauri-apps/api/event';
import { import {
isCasting, isCasting,
@@ -251,7 +259,11 @@ export async function togglePlay(): Promise<void> {
await invoke('library_play_track', { trackId: localTrackId }); await invoke('library_play_track', { trackId: localTrackId });
} else { } else {
// Qobuz track - use play_track with duration for seekbar // Qobuz track - use play_track with duration for seekbar
await invoke('play_track', { trackId: currentTrack.id, durationSecs: currentTrack.duration }); await invoke('play_track', {
trackId: currentTrack.id,
durationSecs: currentTrack.duration,
quality: getStreamingQuality()
});
} }
// Seek to saved position after a short delay to let audio load // Seek to saved position after a short delay to let audio load