rodio/
stream.rs

1use std::io::{Read, Seek};
2use std::sync::{Arc, Weak};
3use std::{error, fmt};
4
5use crate::decoder;
6use crate::dynamic_mixer::{self, DynamicMixerController};
7use crate::sink::Sink;
8use crate::source::Source;
9use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
10use cpal::{Sample, SupportedStreamConfig};
11
12/// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`.
13///
14/// If this is dropped playback will end & attached `OutputStreamHandle`s will no longer work.
15pub struct OutputStream {
16    mixer: Arc<DynamicMixerController<f32>>,
17    _stream: cpal::Stream,
18}
19
20/// More flexible handle to a `OutputStream` that provides playback.
21#[derive(Clone)]
22pub struct OutputStreamHandle {
23    mixer: Weak<DynamicMixerController<f32>>,
24}
25
26impl OutputStream {
27    /// Returns a new stream & handle using the given output device and the default output
28    /// configuration.
29    pub fn try_from_device(
30        device: &cpal::Device,
31    ) -> Result<(Self, OutputStreamHandle), StreamError> {
32        let default_config = device
33            .default_output_config()
34            .map_err(StreamError::DefaultStreamConfigError)?;
35        OutputStream::try_from_device_config(device, default_config)
36    }
37
38    /// Returns a new stream & handle using the given device and stream config.
39    ///
40    /// If the supplied `SupportedStreamConfig` is invalid for the device this function will
41    /// fail to create an output stream and instead return a `StreamError`
42    pub fn try_from_device_config(
43        device: &cpal::Device,
44        config: SupportedStreamConfig,
45    ) -> Result<(Self, OutputStreamHandle), StreamError> {
46        let (mixer, _stream) = device.try_new_output_stream_config(config)?;
47        _stream.play().map_err(StreamError::PlayStreamError)?;
48        let out = Self { mixer, _stream };
49        let handle = OutputStreamHandle {
50            mixer: Arc::downgrade(&out.mixer),
51        };
52        Ok((out, handle))
53    }
54
55    /// Return a new stream & handle using the default output device.
56    ///
57    /// On failure will fallback to trying any non-default output devices.
58    pub fn try_default() -> Result<(Self, OutputStreamHandle), StreamError> {
59        let default_device = cpal::default_host()
60            .default_output_device()
61            .ok_or(StreamError::NoDevice)?;
62
63        let default_stream = Self::try_from_device(&default_device);
64
65        default_stream.or_else(|original_err| {
66            // default device didn't work, try other ones
67            let mut devices = match cpal::default_host().output_devices() {
68                Ok(d) => d,
69                Err(_) => return Err(original_err),
70            };
71
72            devices
73                .find_map(|d| Self::try_from_device(&d).ok())
74                .ok_or(original_err)
75        })
76    }
77}
78
79impl OutputStreamHandle {
80    /// Plays a source with a device until it ends.
81    pub fn play_raw<S>(&self, source: S) -> Result<(), PlayError>
82    where
83        S: Source<Item = f32> + Send + 'static,
84    {
85        let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?;
86        mixer.add(source);
87        Ok(())
88    }
89
90    /// Plays a sound once. Returns a `Sink` that can be used to control the sound.
91    pub fn play_once<R>(&self, input: R) -> Result<Sink, PlayError>
92    where
93        R: Read + Seek + Send + Sync + 'static,
94    {
95        let input = decoder::Decoder::new(input)?;
96        let sink = Sink::try_new(self)?;
97        sink.append(input);
98        Ok(sink)
99    }
100}
101
102/// An error occurred while attempting to play a sound.
103#[derive(Debug)]
104pub enum PlayError {
105    /// Attempting to decode the audio failed.
106    DecoderError(decoder::DecoderError),
107    /// The output device was lost.
108    NoDevice,
109}
110
111impl From<decoder::DecoderError> for PlayError {
112    fn from(err: decoder::DecoderError) -> Self {
113        Self::DecoderError(err)
114    }
115}
116
117impl fmt::Display for PlayError {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        match self {
120            Self::DecoderError(e) => e.fmt(f),
121            Self::NoDevice => write!(f, "NoDevice"),
122        }
123    }
124}
125
126impl error::Error for PlayError {
127    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
128        match self {
129            Self::DecoderError(e) => Some(e),
130            Self::NoDevice => None,
131        }
132    }
133}
134
135/// Errors that might occur when interfacing with audio output.
136#[derive(Debug)]
137pub enum StreamError {
138    /// Could not start playing the stream, see [cpal::PlayStreamError] for
139    /// details.
140    PlayStreamError(cpal::PlayStreamError),
141    /// Failed to get the stream config for device the given device. See
142    /// [cpal::DefaultStreamConfigError] for details
143    DefaultStreamConfigError(cpal::DefaultStreamConfigError),
144    /// Error opening stream with OS. See [cpal::BuildStreamError] for details
145    BuildStreamError(cpal::BuildStreamError),
146    /// Could not list supported stream configs for device. Maybe it
147    /// disconnected, for details see: [cpal::SupportedStreamConfigsError].
148    SupportedStreamConfigsError(cpal::SupportedStreamConfigsError),
149    /// Could not find any output device
150    NoDevice,
151}
152
153impl fmt::Display for StreamError {
154    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155        match self {
156            Self::PlayStreamError(e) => e.fmt(f),
157            Self::BuildStreamError(e) => e.fmt(f),
158            Self::DefaultStreamConfigError(e) => e.fmt(f),
159            Self::SupportedStreamConfigsError(e) => e.fmt(f),
160            Self::NoDevice => write!(f, "NoDevice"),
161        }
162    }
163}
164
165impl error::Error for StreamError {
166    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
167        match self {
168            Self::PlayStreamError(e) => Some(e),
169            Self::BuildStreamError(e) => Some(e),
170            Self::DefaultStreamConfigError(e) => Some(e),
171            Self::SupportedStreamConfigsError(e) => Some(e),
172            Self::NoDevice => None,
173        }
174    }
175}
176
177/// Extensions to `cpal::Device`
178pub(crate) trait CpalDeviceExt {
179    fn new_output_stream_with_format(
180        &self,
181        format: cpal::SupportedStreamConfig,
182    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError>;
183
184    fn try_new_output_stream_config(
185        &self,
186        config: cpal::SupportedStreamConfig,
187    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError>;
188}
189
190impl CpalDeviceExt for cpal::Device {
191    fn new_output_stream_with_format(
192        &self,
193        format: cpal::SupportedStreamConfig,
194    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError> {
195        let (mixer_tx, mut mixer_rx) =
196            dynamic_mixer::mixer::<f32>(format.channels(), format.sample_rate().0);
197
198        let error_callback = |err| {
199            #[cfg(feature = "tracing")]
200            tracing::error!("an error occurred on output stream: {err}");
201            #[cfg(not(feature = "tracing"))]
202            eprintln!("an error occurred on output stream: {err}");
203        };
204
205        match format.sample_format() {
206            cpal::SampleFormat::F32 => self.build_output_stream::<f32, _, _>(
207                &format.config(),
208                move |data, _| {
209                    data.iter_mut()
210                        .for_each(|d| *d = mixer_rx.next().unwrap_or(0f32))
211                },
212                error_callback,
213                None,
214            ),
215            cpal::SampleFormat::F64 => self.build_output_stream::<f64, _, _>(
216                &format.config(),
217                move |data, _| {
218                    data.iter_mut()
219                        .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0f64))
220                },
221                error_callback,
222                None,
223            ),
224            cpal::SampleFormat::I8 => self.build_output_stream::<i8, _, _>(
225                &format.config(),
226                move |data, _| {
227                    data.iter_mut()
228                        .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i8))
229                },
230                error_callback,
231                None,
232            ),
233            cpal::SampleFormat::I16 => self.build_output_stream::<i16, _, _>(
234                &format.config(),
235                move |data, _| {
236                    data.iter_mut()
237                        .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i16))
238                },
239                error_callback,
240                None,
241            ),
242            cpal::SampleFormat::I32 => self.build_output_stream::<i32, _, _>(
243                &format.config(),
244                move |data, _| {
245                    data.iter_mut()
246                        .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i32))
247                },
248                error_callback,
249                None,
250            ),
251            cpal::SampleFormat::I64 => self.build_output_stream::<i64, _, _>(
252                &format.config(),
253                move |data, _| {
254                    data.iter_mut()
255                        .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i64))
256                },
257                error_callback,
258                None,
259            ),
260            cpal::SampleFormat::U8 => self.build_output_stream::<u8, _, _>(
261                &format.config(),
262                move |data, _| {
263                    data.iter_mut().for_each(|d| {
264                        *d = mixer_rx
265                            .next()
266                            .map(Sample::from_sample)
267                            .unwrap_or(u8::MAX / 2)
268                    })
269                },
270                error_callback,
271                None,
272            ),
273            cpal::SampleFormat::U16 => self.build_output_stream::<u16, _, _>(
274                &format.config(),
275                move |data, _| {
276                    data.iter_mut().for_each(|d| {
277                        *d = mixer_rx
278                            .next()
279                            .map(Sample::from_sample)
280                            .unwrap_or(u16::MAX / 2)
281                    })
282                },
283                error_callback,
284                None,
285            ),
286            cpal::SampleFormat::U32 => self.build_output_stream::<u32, _, _>(
287                &format.config(),
288                move |data, _| {
289                    data.iter_mut().for_each(|d| {
290                        *d = mixer_rx
291                            .next()
292                            .map(Sample::from_sample)
293                            .unwrap_or(u32::MAX / 2)
294                    })
295                },
296                error_callback,
297                None,
298            ),
299            cpal::SampleFormat::U64 => self.build_output_stream::<u64, _, _>(
300                &format.config(),
301                move |data, _| {
302                    data.iter_mut().for_each(|d| {
303                        *d = mixer_rx
304                            .next()
305                            .map(Sample::from_sample)
306                            .unwrap_or(u64::MAX / 2)
307                    })
308                },
309                error_callback,
310                None,
311            ),
312            _ => return Err(cpal::BuildStreamError::StreamConfigNotSupported),
313        }
314        .map(|stream| (mixer_tx, stream))
315    }
316
317    fn try_new_output_stream_config(
318        &self,
319        config: SupportedStreamConfig,
320    ) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError> {
321        self.new_output_stream_with_format(config).or_else(|err| {
322            // look through all supported formats to see if another works
323            supported_output_formats(self)?
324                .find_map(|format| self.new_output_stream_with_format(format).ok())
325                // return original error if nothing works
326                .ok_or(StreamError::BuildStreamError(err))
327        })
328    }
329}
330
331/// All the supported output formats with sample rates
332fn supported_output_formats(
333    device: &cpal::Device,
334) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, StreamError> {
335    const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);
336
337    let mut supported: Vec<_> = device
338        .supported_output_configs()
339        .map_err(StreamError::SupportedStreamConfigsError)?
340        .collect();
341    supported.sort_by(|a, b| b.cmp_default_heuristics(a));
342
343    Ok(supported.into_iter().flat_map(|sf| {
344        let max_rate = sf.max_sample_rate();
345        let min_rate = sf.min_sample_rate();
346        let mut formats = vec![sf.with_max_sample_rate()];
347        if HZ_44100 < max_rate && HZ_44100 > min_rate {
348            formats.push(sf.with_sample_rate(HZ_44100))
349        }
350        formats.push(sf.with_sample_rate(min_rate));
351        formats
352    }))
353}