Play sound with rodio with cache

Hello,
I think it is maybe a global Rust philosophy question. I'm trying to use rodio crate which give this example to play a sound:

use std::fs::File;
use std::io::BufReader;
use rodio::{Decoder, OutputStream, source::Source};

// Get a output stream handle to the default physical sound device
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
// Load a sound from a file, using a path relative to Cargo.toml
let file = BufReader::new(File::open("examples/music.ogg").unwrap());
// Decode that sound file into a source
let source = Decoder::new(file).unwrap();
// Play the sound directly on the device
stream_handle.play_raw(source.convert_samples());

// The sound plays in a separate audio thread,
// so we need to keep the main thread alive while it's playing.
std::thread::sleep(std::time::Duration::from_secs(5));

For my project, I want to play the same song often. So I want to design a struct like this:

use rodio::source::{SamplesConverter, SineWave};
use rodio::{Decoder, OutputStream, OutputStreamHandle, source::Source};
use std::fs::File;
use std::io::BufReader;
use std::path::Path;

type SoundSource = Decoder<BufReader<File>>;

pub struct Audio {
    stream_handle: OutputStreamHandle,
    garand_m1_single_shot: SoundSource,
}

fn source(sound_file_string_path: &str) -> SoundSource {
    let file = BufReader::new(File::open(sound_file_string_path).unwrap());
    Decoder::new(file).unwrap()
}

impl Audio {
    pub fn new() -> Self {
        let (_stream, stream_handle) = OutputStream::try_default().unwrap();
        let garand_m1_single_shot = source("resources/audio/lmg_fire01.mp3");
        Self {
            stream_handle,
            garand_m1_single_shot,
        }
    }

    pub fn shot(&self) {
        self.stream_handle.play_raw(self.garand_m1_single_shot.convert_samples());
    }
}

But this produce the following error:

error[E0507]: cannot move out of `self.garand_m1_single_shot` which is behind a shared reference
  --> src/audio/mod.rs:74:37
   |
74 |         self.stream_handle.play_raw(self.garand_m1_single_shot.convert_samples());
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because `self.garand_m1_single_shot` has type `Decoder<BufReader<std::fs::File>>`, which does not implement the `Copy` trait

I understand .play_raw don't want a reference but take the ownership of parameter. But, if I have to give ownership, I have to build BufReader then Decoder etc each times ? How can I design code to prepare the Decoder<BufReader<File>> once per sound ?

In general I think you'd want a method like clone() to cheaply create a new value that can be consumed. In this case, rodio::source::Buffered is probably what you want. Here's a modified version of your code:

use rodio::source::{SamplesConverter, SineWave};
use rodio::{Decoder, OutputStream, OutputStreamHandle, source::{Buffered, Source}};
use std::fs::File;
use std::io::BufReader;
use std::path::Path;

type SoundSource = Buffered<Decoder<BufReader<File>>>;

pub struct Audio {
    stream_handle: OutputStreamHandle,
    garand_m1_single_shot: SoundSource,
}

fn source(sound_file_string_path: &str) -> SoundSource {
    let file = BufReader::new(File::open(sound_file_string_path).unwrap());
    Decoder::new(file).unwrap().buffered()
}

impl Audio {
    pub fn new() -> Self {
        let (_stream, stream_handle) = OutputStream::try_default().unwrap();
        let garand_m1_single_shot = source("resources/audio/lmg_fire01.mp3");
        Self {
            stream_handle,
            garand_m1_single_shot,
        }
    }

    pub fn shot(&self) {
        self.stream_handle.play_raw(self.garand_m1_single_shot.clone().convert_samples());
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

Ok, I see. Clone can be cheap ! Here clone only clone Buffered "things". Thanks !

1 Like

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.