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:
1 parent
700dc07014
commit
a6fc264903
3 files changed
+54
-8
No files matched your search
@@ -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);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user