max / audiofiles
1 file changed,
+30 insertions,
-12 deletions
| @@ -119,6 +119,10 @@ pub fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> { | |||
| 119 | 119 | ||
| 120 | 120 | let mut all_samples: Vec<f32> = Vec::new(); | |
| 121 | 121 | ||
| 122 | + | // Cap at 10 minutes of stereo 48kHz to prevent OOM on files with bad metadata | |
| 123 | + | // that bypass the streaming threshold. | |
| 124 | + | const MAX_SAMPLES: usize = 10 * 60 * 48_000 * 2; | |
| 125 | + | ||
| 122 | 126 | loop { | |
| 123 | 127 | let packet = match format.next_packet() { | |
| 124 | 128 | Ok(p) => p, | |
| @@ -153,6 +157,11 @@ pub fn decode_to_f32(path: &Path) -> Result<PreviewBuffer, PreviewError> { | |||
| 153 | 157 | ||
| 154 | 158 | // Convert to interleaved stereo | |
| 155 | 159 | interleaved_to_stereo(samples, num_channels, num_frames, &mut all_samples); | |
| 160 | + | ||
| 161 | + | if all_samples.len() >= MAX_SAMPLES { | |
| 162 | + | tracing::warn!("decode_to_f32: hit {MAX_SAMPLES} sample cap, truncating"); | |
| 163 | + | break; | |
| 164 | + | } | |
| 156 | 165 | } | |
| 157 | 166 | ||
| 158 | 167 | if all_samples.is_empty() { | |
| @@ -199,6 +208,9 @@ pub fn estimate_duration(path: &Path) -> Option<f64> { | |||
| 199 | 208 | if is_wav { | |
| 200 | 209 | let reader = hound::WavReader::open(path).ok()?; | |
| 201 | 210 | let spec = reader.spec(); | |
| 211 | + | if spec.sample_rate == 0 { | |
| 212 | + | return None; | |
| 213 | + | } | |
| 202 | 214 | let frames = reader.len() as f64 / spec.channels as f64; | |
| 203 | 215 | return Some(frames / spec.sample_rate as f64); | |
| 204 | 216 | } | |
| @@ -277,8 +289,8 @@ pub fn start_streaming_decode( | |||
| 277 | 289 | .make(&codec_params, &DecoderOptions::default()) | |
| 278 | 290 | .map_err(|e| PreviewError::Decoder(e.to_string()))?; | |
| 279 | 291 | ||
| 280 | - | // Cancel any previous streaming decode thread | |
| 281 | - | shared.decode_cancel.store(true, Ordering::Release); | |
| 292 | + | // Increment generation to cancel any previous streaming decode thread | |
| 293 | + | let my_generation = shared.decode_generation.fetch_add(1, Ordering::AcqRel) + 1; | |
| 282 | 294 | ||
| 283 | 295 | // Set up the initial buffer and playback state (not yet playing) | |
| 284 | 296 | { | |
| @@ -296,17 +308,14 @@ pub fn start_streaming_decode( | |||
| 296 | 308 | guard.total_frames_estimate = n_frames_estimate; | |
| 297 | 309 | } | |
| 298 | 310 | ||
| 299 | - | // Clear cancel flag for the new decode thread | |
| 300 | - | shared.decode_cancel.store(false, Ordering::Release); | |
| 301 | - | ||
| 302 | 311 | let shared = std::sync::Arc::clone(shared); | |
| 303 | 312 | std::thread::spawn(move || { | |
| 304 | 313 | let mut total_frames = 0usize; | |
| 305 | 314 | let mut started = false; | |
| 306 | 315 | ||
| 307 | 316 | loop { | |
| 308 | - | // Check cancellation before each packet | |
| 309 | - | if shared.decode_cancel.load(Ordering::Acquire) { | |
| 317 | + | // Check if a newer decode has started — if so, this thread exits | |
| 318 | + | if shared.decode_generation.load(Ordering::Acquire) != my_generation { | |
| 310 | 319 | let mut guard = shared.preview.lock(); | |
| 311 | 320 | guard.streaming = false; | |
| 312 | 321 | return; | |
| @@ -391,19 +400,28 @@ fn interleaved_to_stereo( | |||
| 391 | 400 | num_frames: usize, | |
| 392 | 401 | out: &mut Vec<f32>, | |
| 393 | 402 | ) { | |
| 403 | + | // Sanitize: replace NaN/Inf with silence to prevent downstream propagation | |
| 404 | + | // (NaN.clamp() returns NaN, so the audio output clamp won't catch it). | |
| 405 | + | let clean = |s: f32| if s.is_finite() { s } else { 0.0 }; | |
| 406 | + | ||
| 394 | 407 | match num_channels { | |
| 395 | 408 | 1 => { | |
| 396 | 409 | for &s in samples { | |
| 397 | - | out.push(s); | |
| 398 | - | out.push(s); | |
| 410 | + | let v = clean(s); | |
| 411 | + | out.push(v); | |
| 412 | + | out.push(v); | |
| 413 | + | } | |
| 414 | + | } | |
| 415 | + | 2 => { | |
| 416 | + | for &s in samples { | |
| 417 | + | out.push(clean(s)); | |
| 399 | 418 | } | |
| 400 | 419 | } | |
| 401 | - | 2 => out.extend_from_slice(samples), | |
| 402 | 420 | n => { | |
| 403 | 421 | for frame in 0..num_frames { | |
| 404 | 422 | let base = frame * n; | |
| 405 | - | let left = samples.get(base).copied().unwrap_or(0.0); | |
| 406 | - | let right = samples.get(base + 1).copied().unwrap_or(0.0); | |
| 423 | + | let left = clean(samples.get(base).copied().unwrap_or(0.0)); | |
| 424 | + | let right = clean(samples.get(base + 1).copied().unwrap_or(0.0)); | |
| 407 | 425 | out.push(left); | |
| 408 | 426 | out.push(right); | |
| 409 | 427 | } |