No sound was produced when `rodio::Sink::append()` was called in a different function

I am using crate rodio and iced to write a GUI program with sounds. In order to handle the audio with buttons in the interface I used the Box<> pointer to allow different threads to control the audio. However, in this way no sound was produced even though I added the audio to the sink.
The source code is only a slight modification of the example on docs.rs or GitHub.

use iced::widget::{button, column, text};
use iced::{Alignment, Element, Sandbox, Settings};
use rodio::source::{SineWave, Source};
use std::time::Duration;

pub fn main() -> iced::Result {
    Counter::run(Settings::default())
}

struct Counter {
    value: i32,
}

#[derive(Debug, Clone, Copy)]
enum Message {
    IncrementPressed,
    DecrementPressed,
}

impl Sandbox for Counter {
    type Message = Message;

    fn new() -> Self {
        let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
        let sink = rodio::Sink::try_new(&stream_handle).unwrap();
        play_music(Box::new(sink));
        Self { value: 0 }
    }

    fn title(&self) -> String {
        String::from("Counter - Iced")
    }

    fn update(&mut self, message: Message) {
        match message {
            Message::IncrementPressed => {
                self.value += 1;
            }
            Message::DecrementPressed => {
                self.value -= 1;
            }
        }
    }

    fn view(&self) -> Element<Message> {
        column![
            button("Increment").on_press(Message::IncrementPressed),
            text(self.value).size(50),
            button("Decrement").on_press(Message::DecrementPressed)
        ]
        .padding(20)
        .align_items(Alignment::Center)
        .into()
    }
}

pub fn play_music(sink: Box<rodio::Sink>) {
    println!("Here on play_music()");
    let source = SineWave::new(440.0)
        .take_duration(Duration::from_secs_f32(10.0))
        .amplify(0.20);
    sink.append(source);
    println!("sink.empty = {}", sink.empty());
}

The above code works fine when moving the code from fn play_music() to fn new(). How can I solve the problem?

This might be related to the drop timing since dropping the sink will stop playback.

What happens if you add a sleep_until_end or detach to play_music?

Box brings you no advantage in terms of threads. It just moves the variable to the heap.

That works, but I don't want it to block the current thread. The interface pops out after the audio ends. Is there any way to keep the sink from being dropped?

You currently move the sink into play_music. You have choices:

  1. You can make play_music use an &mut rodio::Sink instead of taking ownership of a sink, so the audio keeps playing until you drop the sink some other way.
  2. You could call sink.detach() in play_music, which is Rodio's method for saying "let the audio keep playing after the sink is dropped.

I'm actually surprised that the code with play_music inlined works, since Counter::new doesn't store sink either - it's dropped at the and of new, and this is, well, only a few ticks later.

Maybe I should show the real situation I'm dealing with, because it might be too different from the code used to reproduce the problem.

pub struct Audios {
    pub sink: Arc<Mutex<Pin<Box<rodio::Sink>>>>,
    pub volume: f32,
}

pub async fn initialize() -> Result<State, crate::Error> {
    // fetch data from the web.
    let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
    let sink = Box::pin(Sink::try_new(&stream_handle).unwrap());
    let sink_mutex = Arc::new(Mutex::new(sink));
    let given_mutex = sink_mutex.clone();
    tokio::spawn(async move {
        play_music(given_mutex, audio_paths).await;
    });
    let fetched = img_mutex.lock().unwrap();
    Ok(State {
        // Some other fields.
        aud_module: Audios {
            volume: 1.0,
            sink: sink_mutex,
        },
    })
    // I am not sure if the sink drops here and causes the problem
}

pub async fn play_music(sink: Arc<Mutex<Pin<Box<rodio::Sink>>>>, mut paths: Vec<String>) {
    loop {
        for audio_dir in &paths {
            let audio_buf = std::fs::File::open(&audio_dir).unwrap();
            let file = std::io::BufReader::new(audio_buf);
            let source = rodio::Decoder::new(file).unwrap();
            let sink = sink.lock().unwrap();
            sink.append(source);
        }
        tokio::time::sleep(Duration::from_secs(10)).await;
    }
}

Here I am using tokio because I need to call tokio::time::pause() when user want the audio to stop in the GUI button. After it replays, it should continue to loop to add new audio to the queue so that the sound won't stop. In order to make the temporary variable sink not drop, I also tried to use Box::leak(Box::new(sink)), but it didn't seem to work either.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.