You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

用Rust Rodio开发MP3播放器,如何检测Sink切换到下一个源?

解决Rodio中检测单首歌曲播放完成的问题

你可以通过为每个音频源包装回调的方式精确检测单首歌曲播放结束,替代无法满足需求的sink.empty()方法。具体实现步骤如下:

  • 自定义实现Source trait的包装器,在音频源耗尽时触发回调:
use rodio::{Source, Decoder};
use std::sync::{Arc, Mutex};

// 包装带结束回调的音频源
struct TrackWithCallback<S> {
    inner: S,
    callback: Option<Box<dyn FnOnce() + Send + 'static>>,
}

impl<S: Source> Source for TrackWithCallback<S> {
    type Item = S::Item;

    fn current_frame_len(&self) -> Option<usize> {
        self.inner.current_frame_len()
    }

    fn channels(&self) -> u16 {
        self.inner.channels()
    }

    fn sample_rate(&self) -> u32 {
        self.inner.sample_rate()
    }

    fn total_duration(&self) -> Option<std::time::Duration> {
        self.inner.total_duration()
    }
}

impl<S: Source> Iterator for TrackWithCallback<S> {
    type Item = S::Item;

    fn next(&mut self) -> Option<Self::Item> {
        let next_sample = self.inner.next();
        // 当没有下一个音频样本时,触发结束回调
        if next_sample.is_none() {
            if let Some(callback) = self.callback.take() {
                callback();
            }
        }
        next_sample
    }
}

// 辅助函数:为音频源绑定结束回调
fn with_callback<S: Source + 'static>(
    source: S,
    callback: impl FnOnce() + Send + 'static
) -> TrackWithCallback<S> {
    TrackWithCallback {
        inner: source,
        callback: Some(Box::new(callback)),
    }
}
  • 在将歌曲添加到sink时,绑定更新queueindex的回调:
// 用Arc+Mutex保证多线程下的安全共享(音频回调在单独线程执行)
let queue = Arc::new(Mutex::new(vec![/* 你的Song实例集合 */]));
let queue_index = Arc::new(Mutex::new(0));

// 处理当前要播放的歌曲
let current_idx = *queue_index.lock().unwrap();
let song = &queue.lock().unwrap()[current_idx];
let decoder = Decoder::new(std::fs::File::open(&song.path).unwrap()).unwrap();

// 克隆Arc以便在回调中访问
let queue_clone = Arc::clone(&queue);
let index_clone = Arc::clone(&queue_index);

// 定义播放结束后的回调逻辑
let callback = move || {
    let mut idx = index_clone.lock().unwrap();
    *idx += 1;
    // 这里可以直接更新当前播放歌曲的显示
    let updated_queue = queue_clone.lock().unwrap();
    if *idx < updated_queue.len() {
        let current_song = &updated_queue[*idx];
        println!("当前播放:{} - {}", current_song.title, current_song.artist);
    }
};

// 绑定回调并添加到sink
let source_with_callback = with_callback(decoder, callback);
sink.append(source_with_callback);

关键提示

  • Rodio的sink运行在独立线程中,因此必须用Arc<Mutex<>>来安全共享队列和索引,避免线程竞争导致的 panic。
  • 这个方案可以精准捕获每首歌的结束时刻,立刻更新索引并刷新播放状态,不受sink中待播歌曲的影响。
  • 如果需要实现循环播放、暂停恢复等功能,只需在回调逻辑中添加对应的判断即可。

内容的提问来源于stack exchange,提问作者baxtermaia5000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 15:18:44