fix: prevent ALSA device lock on streaming failure, add Drop for PlaybackEngine
Add Drop impl to PlaybackEngine so the ALSA Direct writer thread is properly stopped when the engine is dropped (e.g. streaming timeout). Previously, a failed streaming attempt would orphan the writer thread, keeping the ALSA device locked and blocking all subsequent playback. Also force HTTP/1.1 for audio downloads and add detailed error chain logging to diagnose persistent "error decoding response body" failures for specific tracks.
This commit is contained in:
2 files changed
+46
-15
No files matched your search
@@ -199,8 +199,15 @@ impl PlaybackEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop
|
||||
pub fn stop(self) {
|
||||
/// Stop playback and release resources.
|
||||
/// For ALSA Direct, signals the writer thread and waits for it to exit.
|
||||
/// The Drop impl handles the same cleanup if stop() is not called explicitly.
|
||||
pub fn stop(mut self) {
|
||||
self.stop_inner();
|
||||
}
|
||||
|
||||
/// Internal stop logic shared by stop() and Drop
|
||||
fn stop_inner(&mut self) {
|
||||
match self {
|
||||
Self::Rodio { sink } => {
|
||||
sink.stop();
|
||||
@@ -212,11 +219,14 @@ impl PlaybackEngine {
|
||||
playback_thread,
|
||||
..
|
||||
} => {
|
||||
if should_stop.load(Ordering::SeqCst) {
|
||||
return; // Already stopped
|
||||
}
|
||||
log::info!("[ALSA Direct Engine] Stop requested");
|
||||
should_stop.store(true, Ordering::SeqCst);
|
||||
is_playing.store(false, Ordering::SeqCst);
|
||||
|
||||
if let Some(handle) = playback_thread {
|
||||
if let Some(handle) = playback_thread.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
|
||||
@@ -424,3 +434,9 @@ fn alsa_writer_thread(
|
||||
is_playing.store(false, Ordering::SeqCst);
|
||||
log::info!("[ALSA Direct Engine] Writer thread finished");
|
||||
}
|
||||
|
||||
impl Drop for PlaybackEngine {
|
||||
fn drop(&mut self) {
|
||||
self.stop_inner();
|
||||
}
|
||||
}
|
||||
@@ -322,9 +322,8 @@ async fn download_audio(url: &str) -> Result<Vec<u8>, String> {
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.no_gzip()
|
||||
.no_brotli()
|
||||
.no_deflate()
|
||||
.timeout(Duration::from_secs(120))
|
||||
.http1_only()
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
|
||||
|
||||
@@ -332,7 +331,6 @@ async fn download_audio(url: &str) -> Result<Vec<u8>, String> {
|
||||
|
||||
let response = client
|
||||
.get(url)
|
||||
.header("User-Agent", "Mozilla/5.0")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch audio: {}", e))?;
|
||||
@@ -349,7 +347,18 @@ async fn download_audio(url: &str) -> Result<Vec<u8>, String> {
|
||||
let bytes = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read audio bytes: {}", e))?;
|
||||
.map_err(|e| {
|
||||
// Log detailed error chain for debugging
|
||||
use std::error::Error as _;
|
||||
let mut msg = format!("Failed to read audio bytes: {}", e);
|
||||
let mut source = e.source();
|
||||
while let Some(cause) = source {
|
||||
msg.push_str(&format!(" | caused by: {}", cause));
|
||||
source = cause.source();
|
||||
}
|
||||
log::error!("[V2] Download error details: {}", msg);
|
||||
msg
|
||||
})?;
|
||||
|
||||
log::info!("[V2] Downloaded {} bytes", bytes.len());
|
||||
Ok(bytes.to_vec())
|
||||
@@ -371,9 +380,7 @@ async fn v2_get_stream_info(url: &str) -> Result<V2StreamInfo, String> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.no_gzip()
|
||||
.no_brotli()
|
||||
.no_deflate()
|
||||
.http1_only()
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
|
||||
|
||||
@@ -463,9 +470,8 @@ async fn v2_download_and_stream(
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.no_gzip()
|
||||
.no_brotli()
|
||||
.no_deflate()
|
||||
.timeout(Duration::from_secs(300))
|
||||
.http1_only()
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
|
||||
|
||||
@@ -493,7 +499,16 @@ async fn v2_download_and_stream(
|
||||
let mut last_log_time = Instant::now();
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| format!("Stream chunk error: {}", e))?;
|
||||
let chunk = chunk_result.map_err(|e| {
|
||||
let mut msg = format!("Stream chunk error: {}", e);
|
||||
let mut source = std::error::Error::source(&e);
|
||||
while let Some(cause) = source {
|
||||
msg.push_str(&format!(" | caused by: {}", cause));
|
||||
source = std::error::Error::source(cause);
|
||||
}
|
||||
log::error!("[V2/STREAMING] Chunk error details: {}", msg);
|
||||
msg
|
||||
})?;
|
||||
bytes_received += chunk.len() as u64;
|
||||
|
||||
all_data.extend_from_slice(&chunk);
|
||||
|
||||
Reference in new issue
Block a user