fix(player): detect track completion to enable auto-advance

Change audio thread from blocking recv to recv_timeout, periodically
checking if the sink is empty. When playback finishes naturally, set
is_playing to false so the frontend can trigger auto-advance to next.
This commit is contained in:
vicrodh committed 2026-01-09 12:43:58 -06:00
1 parent 7a53d62341
commit ddbe6b5706
1 file changed
+19 -3
+19 -3
View File
@@ -11,9 +11,10 @@
use std::io::{BufReader, Cursor};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::{self, Sender};
use std::sync::mpsc::{self, Sender, RecvTimeoutError};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use rodio::{Decoder, OutputStream, Sink, Source};
use crate::api::{client::QobuzClient, models::Quality};
@@ -198,7 +199,8 @@ impl Player {
log::info!("Audio thread ready and waiting for commands");
loop {
match rx.recv() {
// Use recv_timeout so we can periodically check if sink is empty
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(AudioCommand::Play { data, track_id, duration_secs }) => {
log::info!("Audio thread: playing track {}", track_id);
@@ -283,7 +285,21 @@ impl Player {
}
log::info!("Audio thread: volume set to {}", volume);
}
Err(_) => {
Err(RecvTimeoutError::Timeout) => {
// Check if sink is empty (playback finished)
if let Some(ref sink) = current_sink {
if sink.empty() && thread_state.is_playing.load(Ordering::SeqCst) {
// Track finished playing naturally
log::info!("Audio thread: track finished (sink empty)");
thread_state.is_playing.store(false, Ordering::SeqCst);
// Set position to duration to signal completion
let duration = thread_state.duration.load(Ordering::SeqCst);
thread_state.position.store(duration, Ordering::SeqCst);
thread_state.playback_start_millis.store(0, Ordering::SeqCst);
}
}
}
Err(RecvTimeoutError::Disconnected) => {
// Channel closed, exit thread
log::info!("Audio thread: channel closed, exiting");
break;