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.
Decoder
s 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();
}