1use crate::common::{ChannelCount, SampleRate};
9use crate::decoder;
10use crate::mixer::{mixer, Mixer, MixerSource};
11use crate::sink::Sink;
12use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
13use cpal::{BufferSize, Sample, SampleFormat, StreamConfig};
14use std::io::{Read, Seek};
15use std::marker::Sync;
16use std::{error, fmt};
17
18const HZ_44100: SampleRate = 44_100;
19
20pub struct OutputStream {
44 config: OutputStreamConfig,
45 mixer: Mixer,
46 log_on_drop: bool,
47 _stream: cpal::Stream,
48}
49
50impl OutputStream {
51 pub fn mixer(&self) -> &Mixer {
53 &self.mixer
54 }
55
56 pub fn config(&self) -> &OutputStreamConfig {
58 &self.config
59 }
60
61 pub fn log_on_drop(&mut self, enabled: bool) {
64 self.log_on_drop = enabled;
65 }
66}
67
68impl Drop for OutputStream {
69 fn drop(&mut self) {
70 if self.log_on_drop && !std::thread::panicking() {
71 #[cfg(feature = "tracing")]
72 tracing::debug!("Dropping OutputStream, audio playing through this stream will stop");
73 #[cfg(not(feature = "tracing"))]
74 eprintln!("Dropping OutputStream, audio playing through this stream will stop")
75 }
76 }
77}
78
79#[derive(Copy, Clone, Debug)]
81pub struct OutputStreamConfig {
82 channel_count: ChannelCount,
83 sample_rate: SampleRate,
84 buffer_size: BufferSize,
85 sample_format: SampleFormat,
86}
87
88impl Default for OutputStreamConfig {
89 fn default() -> Self {
90 Self {
91 channel_count: 2,
92 sample_rate: HZ_44100,
93 buffer_size: BufferSize::Default,
94 sample_format: SampleFormat::F32,
95 }
96 }
97}
98
99impl OutputStreamConfig {
100 pub fn channel_count(&self) -> ChannelCount {
102 self.channel_count
103 }
104
105 pub fn sample_rate(&self) -> SampleRate {
107 self.sample_rate
108 }
109
110 pub fn buffer_size(&self) -> &BufferSize {
112 &self.buffer_size
113 }
114
115 pub fn sample_format(&self) -> SampleFormat {
117 self.sample_format
118 }
119}
120
121impl core::fmt::Debug for OutputStreamBuilder {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 let device = if let Some(device) = &self.device {
124 "Some(".to_owned() + device.name().as_deref().unwrap_or("UnNamed") + ")"
125 } else {
126 "None".to_owned()
127 };
128
129 f.debug_struct("OutputStreamBuilder")
130 .field("device", &device)
131 .field("config", &self.config)
132 .finish()
133 }
134}
135
136fn default_error_callback(err: cpal::StreamError) {
137 #[cfg(feature = "tracing")]
138 tracing::error!("audio stream error: {err}");
139 #[cfg(not(feature = "tracing"))]
140 eprintln!("audio stream error: {err}");
141}
142
143pub struct OutputStreamBuilder<E = fn(cpal::StreamError)>
150where
151 E: FnMut(cpal::StreamError) + Send + 'static,
152{
153 device: Option<cpal::Device>,
154 config: OutputStreamConfig,
155 error_callback: E,
156}
157
158impl Default for OutputStreamBuilder {
159 fn default() -> Self {
160 Self {
161 device: None,
162 config: OutputStreamConfig::default(),
163 error_callback: default_error_callback,
164 }
165 }
166}
167
168impl OutputStreamBuilder {
169 pub fn from_device(device: cpal::Device) -> Result<OutputStreamBuilder, StreamError> {
171 let default_config = device
172 .default_output_config()
173 .map_err(StreamError::DefaultStreamConfigError)?;
174
175 Ok(Self::default()
176 .with_device(device)
177 .with_supported_config(&default_config))
178 }
179
180 pub fn from_default_device() -> Result<OutputStreamBuilder, StreamError> {
182 let default_device = cpal::default_host()
183 .default_output_device()
184 .ok_or(StreamError::NoDevice)?;
185 Self::from_device(default_device)
186 }
187
188 pub fn open_default_stream() -> Result<OutputStream, StreamError> {
193 Self::from_default_device()
194 .and_then(|x| x.open_stream())
195 .or_else(|original_err| {
196 let mut devices = match cpal::default_host().output_devices() {
197 Ok(devices) => devices,
198 Err(err) => {
199 #[cfg(feature = "tracing")]
200 tracing::error!("error getting list of output devices: {err}");
201 #[cfg(not(feature = "tracing"))]
202 eprintln!("error getting list of output devices: {err}");
203 return Err(original_err);
204 }
205 };
206 devices
207 .find_map(|d| {
208 Self::from_device(d)
209 .and_then(|x| x.open_stream_or_fallback())
210 .ok()
211 })
212 .ok_or(original_err)
213 })
214 }
215}
216
217impl<E> OutputStreamBuilder<E>
218where
219 E: FnMut(cpal::StreamError) + Send + 'static,
220{
221 pub fn with_device(mut self, device: cpal::Device) -> OutputStreamBuilder<E> {
225 self.device = Some(device);
226 self
227 }
228
229 pub fn with_channels(mut self, channel_count: ChannelCount) -> OutputStreamBuilder<E> {
231 assert!(channel_count > 0);
232 self.config.channel_count = channel_count;
233 self
234 }
235
236 pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> OutputStreamBuilder<E> {
238 self.config.sample_rate = sample_rate;
239 self
240 }
241
242 pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OutputStreamBuilder<E> {
281 self.config.buffer_size = buffer_size;
282 self
283 }
284
285 pub fn with_sample_format(mut self, sample_format: SampleFormat) -> OutputStreamBuilder<E> {
287 self.config.sample_format = sample_format;
288 self
289 }
290
291 pub fn with_supported_config(
294 mut self,
295 config: &cpal::SupportedStreamConfig,
296 ) -> OutputStreamBuilder<E> {
297 self.config = OutputStreamConfig {
298 channel_count: config.channels() as ChannelCount,
299 sample_rate: config.sample_rate().0 as SampleRate,
300 sample_format: config.sample_format(),
301 ..Default::default()
302 };
303 self
304 }
305
306 pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder<E> {
308 self.config = OutputStreamConfig {
309 channel_count: config.channels as ChannelCount,
310 sample_rate: config.sample_rate.0 as SampleRate,
311 buffer_size: config.buffer_size,
312 ..self.config
313 };
314 self
315 }
316
317 pub fn with_error_callback<F>(self, callback: F) -> OutputStreamBuilder<F>
319 where
320 F: FnMut(cpal::StreamError) + Send + 'static,
321 {
322 OutputStreamBuilder {
323 device: self.device,
324 config: self.config,
325 error_callback: callback,
326 }
327 }
328
329 pub fn open_stream(self) -> Result<OutputStream, StreamError> {
331 let device = self.device.as_ref().expect("output device specified");
332
333 OutputStream::open(device, &self.config, self.error_callback)
334 }
335
336 pub fn open_stream_or_fallback(&self) -> Result<OutputStream, StreamError>
341 where
342 E: Clone,
343 {
344 let device = self.device.as_ref().expect("output device specified");
345 let error_callback = &self.error_callback;
346
347 OutputStream::open(device, &self.config, error_callback.clone()).or_else(|err| {
348 for supported_config in supported_output_configs(device)? {
349 if let Ok(handle) = OutputStreamBuilder::default()
350 .with_device(device.clone())
351 .with_supported_config(&supported_config)
352 .with_error_callback(error_callback.clone())
353 .open_stream()
354 {
355 return Ok(handle);
356 }
357 }
358 Err(err)
359 })
360 }
361}
362
363pub fn play<R>(mixer: &Mixer, input: R) -> Result<Sink, PlayError>
366where
367 R: Read + Seek + Send + Sync + 'static,
368{
369 let input = decoder::Decoder::new(input)?;
370 let sink = Sink::connect_new(mixer);
371 sink.append(input);
372 Ok(sink)
373}
374
375impl From<&OutputStreamConfig> for StreamConfig {
376 fn from(config: &OutputStreamConfig) -> Self {
377 cpal::StreamConfig {
378 channels: config.channel_count as cpal::ChannelCount,
379 sample_rate: cpal::SampleRate(config.sample_rate),
380 buffer_size: config.buffer_size,
381 }
382 }
383}
384
385#[derive(Debug)]
387pub enum PlayError {
388 DecoderError(decoder::DecoderError),
390 NoDevice,
392}
393
394impl From<decoder::DecoderError> for PlayError {
395 fn from(err: decoder::DecoderError) -> Self {
396 Self::DecoderError(err)
397 }
398}
399
400impl fmt::Display for PlayError {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 match self {
403 Self::DecoderError(e) => e.fmt(f),
404 Self::NoDevice => write!(f, "NoDevice"),
405 }
406 }
407}
408
409impl error::Error for PlayError {
410 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
411 match self {
412 Self::DecoderError(e) => Some(e),
413 Self::NoDevice => None,
414 }
415 }
416}
417
418#[derive(Debug)]
420pub enum StreamError {
421 PlayStreamError(cpal::PlayStreamError),
424 DefaultStreamConfigError(cpal::DefaultStreamConfigError),
427 BuildStreamError(cpal::BuildStreamError),
429 SupportedStreamConfigsError(cpal::SupportedStreamConfigsError),
432 NoDevice,
434 UnsupportedSampleFormat,
437}
438
439impl fmt::Display for StreamError {
440 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441 match self {
442 Self::PlayStreamError(e) => e.fmt(f),
443 Self::BuildStreamError(e) => e.fmt(f),
444 Self::DefaultStreamConfigError(e) => e.fmt(f),
445 Self::SupportedStreamConfigsError(e) => e.fmt(f),
446 Self::NoDevice => write!(f, "NoDevice"),
447 Self::UnsupportedSampleFormat => write!(f, "UnsupportedSampleFormat"),
448 }
449 }
450}
451
452impl error::Error for StreamError {
453 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
454 match self {
455 Self::PlayStreamError(e) => Some(e),
456 Self::BuildStreamError(e) => Some(e),
457 Self::DefaultStreamConfigError(e) => Some(e),
458 Self::SupportedStreamConfigsError(e) => Some(e),
459 Self::NoDevice => None,
460 Self::UnsupportedSampleFormat => None,
461 }
462 }
463}
464
465impl OutputStream {
466 fn validate_config(config: &OutputStreamConfig) {
467 if let BufferSize::Fixed(sz) = config.buffer_size {
468 assert!(sz > 0, "fixed buffer size is greater than zero");
469 }
470 assert!(config.sample_rate > 0, "sample rate is greater than zero");
471 assert!(
472 config.channel_count > 0,
473 "channel number is greater than zero"
474 );
475 }
476
477 fn open<E>(
478 device: &cpal::Device,
479 config: &OutputStreamConfig,
480 error_callback: E,
481 ) -> Result<OutputStream, StreamError>
482 where
483 E: FnMut(cpal::StreamError) + Send + 'static,
484 {
485 Self::validate_config(config);
486 let (controller, source) = mixer(config.channel_count, config.sample_rate);
487 Self::init_stream(device, config, source, error_callback).and_then(|stream| {
488 stream.play().map_err(StreamError::PlayStreamError)?;
489 Ok(Self {
490 _stream: stream,
491 mixer: controller,
492 config: *config,
493 log_on_drop: true,
494 })
495 })
496 }
497
498 fn init_stream<E>(
499 device: &cpal::Device,
500 config: &OutputStreamConfig,
501 mut samples: MixerSource,
502 error_callback: E,
503 ) -> Result<cpal::Stream, StreamError>
504 where
505 E: FnMut(cpal::StreamError) + Send + 'static,
506 {
507 let sample_format = config.sample_format;
508 let config = config.into();
509
510 match sample_format {
511 cpal::SampleFormat::F32 => device.build_output_stream::<f32, _, _>(
512 &config,
513 move |data, _| {
514 data.iter_mut()
515 .for_each(|d| *d = samples.next().unwrap_or(0f32))
516 },
517 error_callback,
518 None,
519 ),
520 cpal::SampleFormat::F64 => device.build_output_stream::<f64, _, _>(
521 &config,
522 move |data, _| {
523 data.iter_mut()
524 .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0f64))
525 },
526 error_callback,
527 None,
528 ),
529 cpal::SampleFormat::I8 => device.build_output_stream::<i8, _, _>(
530 &config,
531 move |data, _| {
532 data.iter_mut()
533 .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i8))
534 },
535 error_callback,
536 None,
537 ),
538 cpal::SampleFormat::I16 => device.build_output_stream::<i16, _, _>(
539 &config,
540 move |data, _| {
541 data.iter_mut()
542 .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i16))
543 },
544 error_callback,
545 None,
546 ),
547 cpal::SampleFormat::I32 => device.build_output_stream::<i32, _, _>(
548 &config,
549 move |data, _| {
550 data.iter_mut()
551 .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i32))
552 },
553 error_callback,
554 None,
555 ),
556 cpal::SampleFormat::I64 => device.build_output_stream::<i64, _, _>(
557 &config,
558 move |data, _| {
559 data.iter_mut()
560 .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i64))
561 },
562 error_callback,
563 None,
564 ),
565 cpal::SampleFormat::U8 => device.build_output_stream::<u8, _, _>(
566 &config,
567 move |data, _| {
568 data.iter_mut().for_each(|d| {
569 *d = samples
570 .next()
571 .map(Sample::from_sample)
572 .unwrap_or(u8::MAX / 2)
573 })
574 },
575 error_callback,
576 None,
577 ),
578 cpal::SampleFormat::U16 => device.build_output_stream::<u16, _, _>(
579 &config,
580 move |data, _| {
581 data.iter_mut().for_each(|d| {
582 *d = samples
583 .next()
584 .map(Sample::from_sample)
585 .unwrap_or(u16::MAX / 2)
586 })
587 },
588 error_callback,
589 None,
590 ),
591 cpal::SampleFormat::U32 => device.build_output_stream::<u32, _, _>(
592 &config,
593 move |data, _| {
594 data.iter_mut().for_each(|d| {
595 *d = samples
596 .next()
597 .map(Sample::from_sample)
598 .unwrap_or(u32::MAX / 2)
599 })
600 },
601 error_callback,
602 None,
603 ),
604 cpal::SampleFormat::U64 => device.build_output_stream::<u64, _, _>(
605 &config,
606 move |data, _| {
607 data.iter_mut().for_each(|d| {
608 *d = samples
609 .next()
610 .map(Sample::from_sample)
611 .unwrap_or(u64::MAX / 2)
612 })
613 },
614 error_callback,
615 None,
616 ),
617 _ => return Err(StreamError::UnsupportedSampleFormat),
618 }
619 .map_err(StreamError::BuildStreamError)
620 }
621}
622
623pub fn supported_output_configs(
625 device: &cpal::Device,
626) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, StreamError> {
627 let mut supported: Vec<_> = device
628 .supported_output_configs()
629 .map_err(StreamError::SupportedStreamConfigsError)?
630 .collect();
631 supported.sort_by(|a, b| b.cmp_default_heuristics(a));
632
633 Ok(supported.into_iter().flat_map(|sf| {
634 let max_rate = sf.max_sample_rate();
635 let min_rate = sf.min_sample_rate();
636 let mut formats = vec![sf.with_max_sample_rate()];
637 let preferred_rate = cpal::SampleRate(HZ_44100);
638 if preferred_rate < max_rate && preferred_rate > min_rate {
639 formats.push(sf.with_sample_rate(preferred_rate))
640 }
641 formats.push(sf.with_sample_rate(min_rate));
642 formats
643 }))
644}