rustaudioborrow-checkerrodio

How can I make Rust, with the Rodio crate, load multiple sources in a Vec, so I can play them later as needed without having to load them every time?


I am using Rust with the Rodio crate and I wanted to make a Vec of loaded sources to use whenever they are needed, so that the program doesn't need to load it every time. I've made a SoundHandler class that contains an OutputStream, an OutputStreamHandle and a Vec<Decoder<BufReader<File>>>. The first two are instanced by using OutputStream::try_default(), which returns both in a tuple. The Vec is a vector of loaded sources. Its content are, as I understand it, loaded and then pushed in the following SoundHandler method:

pub fn load_source(&mut self, file_path: PathBuf) -> SoundId {
    let id = self.sources.len();
        
    self.sources.push(
        Decoder::new(
            BufReader::new(
                File::open(file_path).expect("file not found")
            )
        ).expect("could not load source")
    );

    SoundId(id)
}

Next, the source should be played by calling the play_sound() method.

Previously, it directly loaded and played the sound:

pub fn play_sound(file_path: PathBuf) {
    self.stream_handle.play_raw(
        Decoder::new(
            BufReader::new(
                File::open(file_path).expect("file not found")
            )
        ).expect("could not load source")
    );
}

But now it must handle the sources that come from the Vec. I had trouble while trying to do that. I need help with that. I am pretty new to the language so knowing how to solve that problem in more than one way would be even better, but my priority is really the path I've chosen to do it, because knowing how to solve that would undoubtedly increase my Rust problem solving skills. Keep in mind my main goal here is to learn the language, and the snippets below are not at all an urgent problem to be solved for a company.

Ok, so I tried the most straightforward thing possible. I directly changed the old method to do the same thing but with the Vec:

pub fn play_sound(&self, sound_id: SoundId) {
    self.stream_handle
        .play_raw(self.sources.get(sound_id.0).unwrap().convert_samples()).unwrap();
}

But the compiler won't compile that method, because, according to it,

error[E0507]: cannot move out of a shared reference
        self.stream_handle.play_raw(self.sources.get(sound_id.0).unwrap().convert_samples()).unwrap();
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------
                                    |                                     |
                                    |                                     value moved due to this method call
                                     move occurs because value has type `rodio::Decoder<std::io::BufReader<std::fs::File>>`, which does not implement the `Copy` trait
note: this function takes ownership of the receiver `self`, which moves value

I understand what the compiler is saying. Of course that can't be possible. convert_samples() tries to make the value inside the Vec invalid, and thus it could make the Vec unsafe. So I naively tried to clone the Decoder, but the struct does not implement the Clone trait, and apparently does not have any method to do so. Finally, I found a way to get the value and own it through the Vec::remove() method, but I do not want to remove it, and I do want it to be fallible.

So my question is: in this situation, how can I make that Vec of loaded sources? Maybe I could even make a Vec out of SamplesConverter. But then the name of the type gets huge, so that is probably not an intended way to do it: Vec<SamplesConverter<Decoder<BufReader<File>>, f32>>. But it has the same problems of the other implementation.


Solution

  • Decoders can't be cloned because the underlying data source may not be clonable (and in fact BufReader isn't). So in order to clone the audio sources you will need to ensure that the data is stored in memory. This can be accomplished by the buffered method, which returns a Buffered source, which can be cloned. So something like this should work:

    pub fn load_source(&mut self, file_path: PathBuf) -> SoundId {
        let id = self.sources.len();
            
        self.sources.push(
            Decoder::new(
                BufReader::new(
                    File::open(file_path).expect("file not found")
                )
            ).expect("could not load source")
            .buffered()
        );
    
        SoundId(id)
    }
    
    pub fn play_sound(&self, sound_id: SoundId) {
        self.stream_handle.play_raw(
            self.sources.get(sound_id.0).unwrap().clone().convert_samples()).unwrap();
    }