Skip to main content

selene_daemon/player/
playback.rs

1use 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    /// Attempts to consume the rest of the pending packet.
75    ///
76    /// Returns [`None`] if there is no packet
77    /// Returns [`true`] if there is a packet and it finished consuming
78    /// Returns [`false`] if there is a packet and it has not finished consuming
79    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}