fix(qbz-slint): cold-start current track on play when no audio is loaded

Local toggle_play always called resume(), which fails with "cannot resume -
no audio data available" when the player holds no loaded stream — e.g. a
freshly materialized QConnect renderer queue whose cursor sits on a track that
was never loaded. Detect the no-loaded-audio case via Player::has_loaded_audio()
and load+play the current queue track instead of resuming. A normal pause keeps
the stream loaded, so the pause/resume path is unchanged.

cc
This commit is contained in:
vicrodh committed 2026-06-05 21:09:14 -06:00
1 parent b4c3bab91b
commit 1287d4feaf
2 files changed
+46 -10

No files matched your search

+3 -1
View File
@@ -3012,11 +3012,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// renderer/local playback because the gate is `is_peer_renderer_active`.
{
let runtime = app_runtime.clone();
let weak = window.as_weak();
let handle = tokio_rt.handle().clone();
window
.global::<NowPlayingState>()
.on_toggle_play(move || {
let runtime = runtime.clone();
let weak = weak.clone();
let handle = handle.clone();
handle.clone().spawn(async move {
if let Some(svc) = qconnect_service::service() {
@@ -3029,7 +3031,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
}
playback::toggle_play_pause(runtime, handle);
playback::toggle_play_pause(runtime, weak, handle);
});
});
}
+43 -9
View File
@@ -1868,16 +1868,50 @@ pub fn enqueue_local_tracks(
}
/// Toggle play / pause on the live player.
pub fn toggle_play_pause(runtime: Runtime, handle: tokio::runtime::Handle) {
///
/// Resume is only valid when the audio engine actually holds a loaded stream.
/// When the player has NO loaded audio but the queue has a current track —
/// e.g. a freshly materialized QConnect renderer queue whose cursor sits on a
/// track that was never loaded, or a cold cursor after the queue ended — a
/// bare `resume()` fails with "cannot resume - no audio data available" and the
/// user sees a dead Play button. In that case LOAD and play the current queue
/// track instead, so Play works from a cold cursor. A normal pause leaves the
/// stream loaded (`has_loaded_audio` stays true), so the pause/resume path is
/// unchanged.
pub fn toggle_play_pause(
runtime: Runtime,
weak: slint::Weak<AppWindow>,
handle: tokio::runtime::Handle,
) {
handle.spawn(async move {
let playing = runtime.core().get_playback_state().is_playing;
let result = if playing {
runtime.core().pause()
} else {
runtime.core().resume()
};
if let Err(e) = result {
log::error!("[qbz-slint] playback: toggle play/pause failed: {e}");
if runtime.core().get_playback_state().is_playing {
if let Err(e) = runtime.core().pause() {
log::error!("[qbz-slint] playback: pause failed: {e}");
}
return;
}
// Not playing: resume an existing stream, or cold-start the current
// queue track when nothing is loaded.
if runtime.core().player().has_loaded_audio() {
if let Err(e) = runtime.core().resume() {
log::error!("[qbz-slint] playback: resume failed: {e}");
}
return;
}
match runtime.core().current_track().await {
Some(track) => {
log::info!(
"[qbz-slint] playback: play with no loaded audio -> cold-starting current track {}",
track.id
);
after_track_change(&runtime, &weak, track.id).await;
refresh_sidebar(true);
}
None => {
log::info!(
"[qbz-slint] playback: toggle play ignored (no loaded audio, empty queue)"
);
}
}
});
}