Skip to main content

selene_daemon/
player.rs

1use std::{
2    fmt::Display,
3    sync::{
4        Arc, Mutex, PoisonError,
5        atomic::{AtomicU32, Ordering},
6        mpsc::{Receiver, RecvError, SendError, Sender},
7    },
8};
9
10use bitflags::bitflags;
11use blake3::Hash;
12use lunar_lib::{
13    config::ConfigError,
14    database::{Database, DatabaseError},
15};
16use rubato::ResamplerConstructionError;
17use selene_core::{
18    database::LibraryDb,
19    library::{
20        collection::Collectable,
21        track::{Track, TrackId},
22    },
23    media_container::ContainerFormat,
24    symphonia_helpers::raw_decoder::DecodingError,
25};
26use serde::{Deserialize, Serialize};
27use symphonia::core::errors::Error as SymphoniaError;
28use thiserror::Error;
29
30use crate::{
31    PlayerEvent,
32    decoder::{DecoderCommand, DecoderTx},
33    event_handler::EventTx,
34    player::playback::{CpalError, DeviceConfig},
35    playlist::{
36        AtomicPlaybackStatus, LoopMode, Playable, PlaybackStatus, Playlist, ResolvedTrack,
37        ShuffleMode,
38    },
39};
40
41mod opened_decoder;
42pub use opened_decoder::*;
43
44pub mod playback;
45
46mod ipc;
47pub use ipc::*;
48
49mod thread;
50
51#[derive(Debug, Error)]
52pub enum PlayerError {
53    #[error("{0}")]
54    Io(#[from] std::io::Error),
55
56    #[error("{0}")]
57    Config(#[from] ConfigError),
58
59    #[error("{0}")]
60    Cpal(#[from] CpalError),
61
62    #[error("{0}")]
63    Symphonia(#[from] SymphoniaError),
64
65    #[error("{0}")]
66    Database(#[from] DatabaseError),
67
68    #[error("{0}")]
69    Decoder(#[from] DecodingError),
70
71    #[error("{0}")]
72    Resampler(#[from] ResamplerConstructionError),
73
74    #[error("Player attempted to play an unsupported container: '{0:?}'")]
75    UnsupportedContainer(ContainerFormat),
76
77    #[error("A critical thread panicked holding an interior mutex")]
78    CriticalMutexPoisoned,
79
80    #[error("A sender or receiver to a critical thread disconnected")]
81    CriticalThreadDisconnected,
82}
83
84impl<T> From<PoisonError<T>> for PlayerError {
85    fn from(_value: PoisonError<T>) -> Self {
86        Self::CriticalMutexPoisoned
87    }
88}
89
90impl From<RecvError> for PlayerError {
91    fn from(_value: RecvError) -> Self {
92        Self::CriticalThreadDisconnected
93    }
94}
95
96impl<T> From<SendError<T>> for PlayerError {
97    fn from(_value: SendError<T>) -> Self {
98        Self::CriticalThreadDisconnected
99    }
100}
101
102pub struct Player {
103    rx: Receiver<PlayerRequest>,
104    decoder_tx: Sender<DecoderCommand>,
105
106    event_tx: Sender<PlayerEvent>,
107
108    playlist: Playlist,
109
110    device_config: Arc<Mutex<DeviceConfig>>,
111    playback_state: Arc<AtomicPlaybackStatus>,
112    volume: Arc<AtomicU32>,
113}
114
115impl Player {
116    /// Sets or increments the volume to the input volume, clamps between `[0..1]`
117    fn set_volume(&mut self, volume: f32, increment: bool) -> f32 {
118        let volume = if increment {
119            f32::from_bits(self.volume.load(Ordering::Relaxed)) + volume
120        } else {
121            volume
122        }
123        .clamp(0.0, 1.0);
124
125        self.event_tx.event(PlayerEvent::VolumeChanged { volume });
126        self.volume.store(volume.to_bits(), Ordering::Relaxed);
127
128        volume
129    }
130
131    pub fn load(&self, playable: ResolvedTrack) -> Result<(), PlayerError> {
132        let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
133        let decoder = OpenedDecoder::new(playable, output_sample_rate)?;
134        self.decoder_tx.load(decoder)?;
135        Ok(())
136    }
137
138    pub fn preload(&self, playable: ResolvedTrack) -> Result<(), PlayerError> {
139        let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
140        let decoder = OpenedDecoder::new(playable, output_sample_rate)?;
141        self.decoder_tx.preload(decoder)?;
142        Ok(())
143    }
144
145    fn replace_decoders(&mut self, pop: bool) -> Result<bool, PlayerError> {
146        let db = LibraryDb::open().unwrap();
147        let load = if pop {
148            self.playlist.pop_next(&db)?
149        } else {
150            self.playlist.current(&db)?
151        };
152        let preload = self.playlist.peek_next(&db)?;
153
154        if let Some(load) = load {
155            let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
156            let load = OpenedDecoder::new(load, output_sample_rate)?;
157            if let Some(preload) = preload {
158                let preload = OpenedDecoder::new(preload, output_sample_rate)?;
159                self.decoder_tx.load_and_preload(load, preload)?;
160                return Ok(true);
161            }
162
163            self.decoder_tx.load(load)?;
164            Ok(true)
165        } else {
166            self.decoder_tx.stop()?;
167            Ok(false)
168        }
169    }
170}
171
172bitflags! {
173    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
174    pub struct PlayerQueryFlags: u16 {
175        const PLAYBACK_STATE        = 1 << 0;
176
177        const CURRENTLY_PLAYING     = 1 << 1;
178        const CURRENTLY_PLAYING_RAW = 1 << 2;
179
180        const VOLUME                = 1 << 3;
181        const TIME                  = 1 << 4;
182
183        const QUEUE                 = 1 << 5;
184        const QUEUE_RAW             = 1 << 6;
185        const QUEUE_STATE           = 1 << 7;
186
187        const PLAYLIST              = 1 << 8;
188        const PLAYLIST_RAW          = 1 << 9;
189        const PLAYLIST_STATE        = 1 << 10;
190
191        const TRACKLIST             = 1 << 11;
192        const TRACKLIST_RAW         = 1 << 12;
193        const TRACKLIST_POSITION    = 1 << 13;
194
195        const SHUFFLE_MODE          = 1 << 14;
196        const LOOP_MODE             = 1 << 15;
197
198        const ALL_SIMPLE = Self::PLAYBACK_STATE.bits()
199            | Self::CURRENTLY_PLAYING_RAW.bits()
200            | Self::VOLUME.bits()
201            | Self::TIME.bits()
202            | Self::QUEUE.bits()
203            | Self::PLAYLIST.bits()
204            | Self::TRACKLIST.bits()
205            | Self::TRACKLIST_POSITION.bits()
206            | Self::SHUFFLE_MODE.bits()
207            | Self::LOOP_MODE.bits();
208    }
209}
210
211/// Wrapper around [`Option`] for ciborium ser/de workaround
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub enum QueryWrapper<T> {
214    None,
215    Some(T),
216}
217
218impl<T> QueryWrapper<T> {
219    pub fn into_option(self) -> Option<T> {
220        match self {
221            QueryWrapper::None => None,
222            QueryWrapper::Some(t) => Some(t),
223        }
224    }
225
226    pub fn as_option(&self) -> Option<&T> {
227        match self {
228            QueryWrapper::None => None,
229            QueryWrapper::Some(t) => Some(t),
230        }
231    }
232}
233
234impl<T> From<Option<T>> for QueryWrapper<T> {
235    fn from(value: Option<T>) -> Self {
236        match value {
237            Some(t) => QueryWrapper::Some(t),
238            None => QueryWrapper::None,
239        }
240    }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, Default)]
244pub struct QueryResult {
245    pub playback_state: Option<PlaybackStatus>,
246
247    pub currently_playing: Option<QueryWrapper<TrackId>>,
248    pub currently_playing_raw: Option<QueryWrapper<ResolvedTrack>>,
249
250    pub volume: Option<f32>,
251    pub time: Option<QueryWrapper<f64>>,
252
253    pub queue: Option<Vec<TrackId>>,
254    pub queue_raw: Option<Vec<Track>>,
255    pub queue_state: Option<Hash>,
256
257    pub playlist: Option<Vec<Collectable>>,
258    pub playlist_raw: Option<Vec<Playable>>,
259    pub playlist_state: Option<Hash>,
260
261    pub tracklist: Option<Vec<TrackId>>,
262    pub tracklist_raw: Option<Vec<Track>>,
263    pub tracklist_position: Option<(usize, usize)>,
264
265    pub shuffle_mode: Option<ShuffleMode>,
266    pub loop_mode: Option<LoopMode>,
267}
268
269impl Display for QueryResult {
270    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271        if let Some(playback_state) = self.playback_state {
272            writeln!(f, "Playback State: {playback_state}")?;
273        }
274
275        if let Some(tracklist_track) = &self.currently_playing {
276            if let Some(tracklist_track) = tracklist_track.as_option() {
277                writeln!(f, "Currently Playing: {}", tracklist_track.to_selene_id())?;
278            } else {
279                f.write_str("Currently Playing: None\n")?;
280            }
281        }
282
283        if let Some(volume) = self.volume {
284            writeln!(f, "Volume: {volume}")?;
285        }
286
287        if let Some(time) = &self.time {
288            let time = time.as_option().unwrap_or(&0.0);
289            writeln!(f, "Time: {time}")?;
290        }
291
292        if let Some(queue) = &self.queue {
293            writeln!(
294                f,
295                "Queue: {}",
296                queue
297                    .iter()
298                    .map(TrackId::to_selene_id)
299                    .collect::<Vec<_>>()
300                    .join(";")
301            )?;
302        }
303
304        if let Some(state) = self.queue_state {
305            writeln!(f, "Queue State: {state}")?;
306        }
307
308        if let Some(playlist) = &self.playlist {
309            writeln!(
310                f,
311                "Playlist: {}",
312                playlist
313                    .iter()
314                    .map(Collectable::to_selene_id)
315                    .collect::<Vec<_>>()
316                    .join(";")
317            )?;
318        }
319
320        if let Some(state) = self.playlist_state {
321            writeln!(f, "State: {state}")?;
322        }
323
324        if let Some(shuffle_mode) = self.shuffle_mode {
325            writeln!(f, "Shuffle Mode: {shuffle_mode}")?;
326        }
327
328        if let Some(loop_mode) = self.loop_mode {
329            writeln!(f, "Loop Mode: {loop_mode}")?;
330        }
331
332        Ok(())
333    }
334}