selene_daemon/player/
playback.rs1use std::{
2 collections::VecDeque,
3 sync::{
4 Arc,
5 atomic::{AtomicU32, Ordering},
6 mpsc::{Receiver, SendError, Sender, channel},
7 },
8};
9
10use cpal::{
11 Device, DeviceDescription, Stream, SupportedStreamConfig,
12 traits::{DeviceTrait, HostTrait, StreamTrait},
13};
14use lunar_lib::debug;
15use ringbuf::{
16 CachingCons, CachingProd, HeapRb, SharedRb,
17 storage::Heap,
18 traits::{Consumer, Producer, Split},
19};
20
21use crate::{config::daemon_config, player::PlayerError};
22
23pub struct DeviceConfig {
24 pub device: Device,
25 pub config: SupportedStreamConfig,
26}
27
28impl DeviceConfig {
29 pub fn default_config() -> Result<Self, CpalError> {
30 let host = cpal::default_host();
31 let device = host
32 .default_output_device()
33 .ok_or(CpalError::NoDefaultDevice)?;
34 let config = device.default_output_config()?;
35
36 Ok(Self { device, config })
37 }
38}
39
40pub struct CpalHandle {
41 pub(crate) _stream: Stream,
42 pub tx: Sender<()>,
43 pub audio_buf: CachingProd<Arc<SharedRb<Heap<f32>>>>,
44 pub(crate) pending_packet: VecDeque<f32>,
45}
46
47impl Drop for CpalHandle {
48 fn drop(&mut self) {
49 debug!("Cpal handle was dropped");
50 }
51}
52
53impl CpalHandle {
54 pub fn open(device_config: &DeviceConfig, volume: Arc<AtomicU32>) -> Result<Self, PlayerError> {
55 let (tx, rx) = channel();
56
57 let rb = HeapRb::new(daemon_config().playback.audio_buffer_size * 1024);
58 let (prod, cons) = rb.split();
59
60 let stream = open_cpal_stream(cons, volume, rx, device_config)?;
61
62 Ok(Self {
63 _stream: stream,
64 tx,
65 audio_buf: prod,
66 pending_packet: VecDeque::new(),
67 })
68 }
69
70 pub fn clear_buf(&self) -> Result<(), SendError<()>> {
71 self.tx.send(())
72 }
73
74 pub fn consume_packet(&mut self) -> Option<bool> {
80 if !self.pending_packet.is_empty() {
81 let pushed = self
82 .audio_buf
83 .push_iter(self.pending_packet.iter().copied());
84
85 self.pending_packet.drain(..pushed);
86 return Some(self.pending_packet.is_empty());
87 }
88 None
89 }
90}
91
92#[derive(Debug, thiserror::Error)]
93pub enum CpalError {
94 #[error("{0}")]
95 PlayStreamError(#[from] cpal::PlayStreamError),
96
97 #[error("{0}")]
98 BuildStreamError(#[from] cpal::BuildStreamError),
99
100 #[error("{0}")]
101 DefaultStreamConfigError(#[from] cpal::DefaultStreamConfigError),
102
103 #[error("{0}")]
104 DeviceIdError(#[from] cpal::DeviceIdError),
105
106 #[error("Failed to find the default device")]
107 NoDefaultDevice,
108}
109
110fn open_cpal_stream(
111 mut audio_buf: CachingCons<Arc<SharedRb<Heap<f32>>>>,
112 volume: Arc<AtomicU32>,
113 rx: Receiver<()>,
114 device_config: &DeviceConfig,
115) -> Result<Stream, CpalError> {
116 let data_callback = move |output: &mut [f32], _: &cpal::OutputCallbackInfo| {
117 if let Ok(()) = rx.try_recv() {
118 audio_buf.clear();
119 }
120
121 let volume = f32::from_bits(volume.load(Ordering::Relaxed));
122 for sample in output {
123 *sample = audio_buf.try_pop().unwrap_or(0.0) * volume.powf(3.0);
124 }
125 };
126
127 let error_callback = |err| eprintln!("Audio stream error: {err:?}");
128
129 let stream = device_config.device.build_output_stream(
130 &device_config.config.clone().into(),
131 data_callback,
132 error_callback,
133 None,
134 )?;
135
136 debug!(
137 "CPAL stream opened for {}",
138 device_config
139 .device
140 .description()
141 .as_ref()
142 .map_or("Unknown Device", DeviceDescription::name)
143 );
144 stream.play()?;
145
146 Ok(stream)
147}