diff --git a/examples/basic.rs b/examples/basic.rs index cba054a..d715f69 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -3,24 +3,24 @@ use std::thread; use std::time::Duration; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); + let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); let file = std::fs::File::open("examples/beep.wav").unwrap(); - let beep1 = stream.play_once(BufReader::new(file)).unwrap(); + let beep1 = stream_handle.play_once(BufReader::new(file)).unwrap(); beep1.set_volume(0.2); println!("Started beep1"); thread::sleep(Duration::from_millis(1500)); let file = std::fs::File::open("examples/beep2.wav").unwrap(); - let beep2 = stream.play_once(BufReader::new(file)).unwrap(); + let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap(); beep2.set_volume(0.3); beep2.detach(); println!("Started beep2"); thread::sleep(Duration::from_millis(1500)); let file = std::fs::File::open("examples/beep3.ogg").unwrap(); - let beep3 = stream.play_once(file).unwrap(); + let beep3 = stream_handle.play_once(file).unwrap(); beep3.set_volume(0.2); println!("Started beep3"); diff --git a/examples/music_flac.rs b/examples/music_flac.rs index 028eae4..216a1b1 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::new(&stream); + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.flac").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index acbaaf3..8091043 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::new(&stream); + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.mp3").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index fb8f042..0b75333 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::new(&stream); + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.ogg").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_wav.rs b/examples/music_wav.rs index ae7a2f3..2dda1bf 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::new(&stream); + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.wav").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/reverb.rs b/examples/reverb.rs index dc9d8ae..0174551 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -3,8 +3,8 @@ use std::io::BufReader; use std::time::Duration; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::new(&stream); + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("examples/music.ogg").unwrap(); let source = rodio::Decoder::new(BufReader::new(file)).unwrap(); diff --git a/examples/spatial.rs b/examples/spatial.rs index 2b12e60..fdf200d 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -3,13 +3,13 @@ use std::thread; use std::time::Duration; fn main() { - let stream = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::SpatialSink::new( - &stream, + let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let sink = rodio::SpatialSink::try_new( + &handle, [-10.0, 0.0, 0.0], [1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], - ); + ).unwrap(); let file = std::fs::File::open("examples/music.ogg").unwrap(); let source = rodio::Decoder::new(BufReader::new(file)).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 9d6e1ef..0f35986 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,11 +20,11 @@ //! use std::io::BufReader; //! use rodio::Source; //! -//! let stream = rodio::OutputStream::try_default().unwrap(); +//! let (stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); //! //! let file = File::open("sound.ogg").unwrap(); //! let source = rodio::Decoder::new(BufReader::new(file)).unwrap(); -//! stream.play_raw(source.convert_samples()); +//! stream_handle.play_raw(source.convert_samples()); //! ``` //! //! ## Sink @@ -38,8 +38,8 @@ //! ```no_run //! use rodio::Sink; //! -//! let stream = rodio::OutputStream::try_default().unwrap(); -//! let sink = rodio::Sink::new(&stream); +//! let (stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); +//! let sink = rodio::Sink::try_new(&stream_handle).unwrap(); //! //! // Add a dummy source of the sake of the example. //! let source = rodio::source::SineWave::new(440); diff --git a/src/sink.rs b/src/sink.rs index 2ec1ef2..9e142b2 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -1,4 +1,4 @@ -use crate::stream::OutputStream; +use crate::stream::{OutputStreamHandle, PlayError}; use std::sync::atomic::Ordering; use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::mpsc::Receiver; @@ -32,12 +32,12 @@ struct Controls { } impl Sink { - /// Builds a new `Sink`, beginning playback on a Device. + /// Builds a new `Sink`, beginning playback on a stream. #[inline] - pub fn new(stream: &OutputStream) -> Sink { + pub fn try_new(stream: &OutputStreamHandle) -> Result { let (sink, queue_rx) = Sink::new_idle(); - stream.play_raw(queue_rx); - sink + stream.play_raw(queue_rx)?; + Ok(sink) } /// Builds a new `Sink`. diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 271e0cb..d9ff4c3 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -1,5 +1,5 @@ use crate::source::Spatial; -use crate::stream::OutputStream; +use crate::stream::{OutputStreamHandle, PlayError}; use crate::Sample; use crate::Sink; use crate::Source; @@ -21,21 +21,20 @@ struct SoundPositions { impl SpatialSink { /// Builds a new `SpatialSink`. - #[inline] - pub fn new( - stream: &OutputStream, + pub fn try_new( + stream: &OutputStreamHandle, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3], - ) -> SpatialSink { - SpatialSink { - sink: Sink::new(stream), + ) -> Result { + Ok(SpatialSink { + sink: Sink::try_new(stream)?, positions: Arc::new(Mutex::new(SoundPositions { emitter_position, left_ear, right_ear, })), - } + }) } /// Sets the position of the sound emitter in 3 dimensional space. diff --git a/src/stream.rs b/src/stream.rs index 4f43cfb..cf1b7c0 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -6,55 +6,79 @@ use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, Sample, }; -use std::convert::TryFrom; use std::io::{Read, Seek}; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use std::{error, fmt}; +/// Immovable `cpal::Stream` container. +/// If this is dropped playback will end & attached `OutputStreamHandle`s will no longer work. pub struct OutputStream { mixer: Arc>, _stream: cpal::Stream, } -impl TryFrom<&'_ cpal::Device> for OutputStream { - type Error = StreamError; - - fn try_from(device: &cpal::Device) -> Result { - let (mixer, _stream) = device.new_output_stream(); - _stream.play()?; - Ok(Self { mixer, _stream }) - } +/// More flexible handle to a `OutputStream` that provides playback. +#[derive(Clone)] +pub struct OutputStreamHandle { + mixer: Weak>, } impl OutputStream { - pub fn try_default() -> Result { + pub fn try_from_device( + device: &cpal::Device, + ) -> Result<(Self, OutputStreamHandle), StreamError> { + let (mixer, _stream) = device.new_output_stream(); + _stream.play()?; + let out = Self { mixer, _stream }; + let handle = OutputStreamHandle { + mixer: Arc::downgrade(&out.mixer), + }; + Ok((out, handle)) + } + + pub fn try_default() -> Result<(Self, OutputStreamHandle), StreamError> { let device = cpal::default_host() .default_output_device() .ok_or(StreamError::NoDevice)?; - Self::try_from(&device) + Self::try_from_device(&device) } +} +impl OutputStreamHandle { /// Plays a source with a device until it ends. - pub fn play_raw(&self, source: S) + pub fn play_raw(&self, source: S) -> Result<(), PlayError> where S: Source + Send + 'static, { - self.mixer.add(source); + let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?; + mixer.add(source); + Ok(()) } /// Plays a sound once. Returns a `Sink` that can be used to control the sound. - #[inline] - pub fn play_once(&self, input: R) -> Result + pub fn play_once(&self, input: R) -> Result where R: Read + Seek + Send + 'static, { let input = decoder::Decoder::new(input)?; - let sink = Sink::new(&self); + let sink = Sink::try_new(self)?; sink.append(input); Ok(sink) } } +#[derive(Debug)] +pub enum PlayError { + DecoderError(decoder::DecoderError), + NoDevice, +} + +impl From for PlayError { + fn from(err: decoder::DecoderError) -> Self { + Self::DecoderError(err) + } +} + #[derive(Debug)] pub enum StreamError { PlayStreamError(cpal::PlayStreamError),