Fix inline audio duration detection
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
//! shared status snapshot at its redraw cadence.
|
//! shared status snapshot at its redraw cadence.
|
||||||
|
|
||||||
use crate::files::AttachmentId;
|
use crate::files::AttachmentId;
|
||||||
use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source};
|
use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source, decoder::DecoderError};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::sync::{Arc, Mutex, mpsc};
|
use std::sync::{Arc, Mutex, mpsc};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -103,7 +103,11 @@ fn playback_worker(command_rx: mpsc::Receiver<ClipCommand>, status: SharedClipSt
|
|||||||
loop {
|
loop {
|
||||||
match command_rx.recv_timeout(Duration::from_millis(100)) {
|
match command_rx.recv_timeout(Duration::from_millis(100)) {
|
||||||
Ok(ClipCommand::Play(id, bytes)) => {
|
Ok(ClipCommand::Play(id, bytes)) => {
|
||||||
let source = match Decoder::new(Cursor::new(bytes)) {
|
// In-memory readers do not expose file metadata to rodio. Pass
|
||||||
|
// the known attachment length explicitly so formats without a
|
||||||
|
// duration in their headers (notably MP3 and Vorbis) can derive
|
||||||
|
// a total duration and support reliable seeking.
|
||||||
|
let source = match decode_clip(bytes) {
|
||||||
Ok(source) => source,
|
Ok(source) => source,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
fail(
|
fail(
|
||||||
@@ -194,6 +198,14 @@ fn playback_worker(command_rx: mpsc::Receiver<ClipCommand>, status: SharedClipSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_clip(bytes: Vec<u8>) -> Result<Decoder<Cursor<Vec<u8>>>, DecoderError> {
|
||||||
|
let byte_len = bytes.len() as u64;
|
||||||
|
Decoder::builder()
|
||||||
|
.with_data(Cursor::new(bytes))
|
||||||
|
.with_byte_len(byte_len)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
fn fail(status: &SharedClipStatus, id: AttachmentId, error: String, invalid_audio: bool) {
|
fn fail(status: &SharedClipStatus, id: AttachmentId, error: String, invalid_audio: bool) {
|
||||||
crate::log_msg(&format!("Inline audio playback failed: {error}"));
|
crate::log_msg(&format!("Inline audio playback failed: {error}"));
|
||||||
update_status(status, |s| {
|
update_status(status, |s| {
|
||||||
@@ -248,6 +260,20 @@ pub fn seek_target(fraction: f32, total: Duration) -> Duration {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use base64::Engine;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn in_memory_mp3_reports_duration() {
|
||||||
|
// One headerless constant-bitrate MP3 frame repeated to model files
|
||||||
|
// that do not carry an Xing/VBR duration header.
|
||||||
|
let frame = base64::engine::general_purpose::STANDARD
|
||||||
|
.decode("//sQxAAABIQVWVRggDCqCKiDNlAAAAGgS4BgAmTT2AQAABCxOD5d7gQOfqBAEHS4Ph/EAIRI7//0A0KBNpABgMRIDCSI04PcIFdF0PJKFgzlUf5eAoF8BRIPfh4FTvUDQl+dUi5pc0w=")
|
||||||
|
.expect("valid test fixture");
|
||||||
|
let bytes = frame.repeat(20);
|
||||||
|
|
||||||
|
let decoder = decode_clip(bytes).expect("CBR MP3 should decode");
|
||||||
|
assert!(decoder.total_duration().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn formats_clip_time() {
|
fn formats_clip_time() {
|
||||||
|
|||||||
Reference in New Issue
Block a user