Skip to main content

selene_daemon/
player.rs

1use std::{
2    fmt::Display,
3    sync::{
4        Arc, Mutex, PoisonError,
5        atomic::{AtomicU8, 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::{DatabaseError, DbHandle},
15};
16use rubato::ResamplerConstructionError;
17use selene_core::{
18    database::LibraryDb,
19    library::{collectable::Collectable, track::TrackId},
20    media_container::ContainerFormat,
21    symphonia_helpers::raw_decoder::DecodingError,
22};
23use serde::{Deserialize, Serialize};
24use symphonia::core::errors::Error as SymphoniaError;
25use thiserror::Error;
26
27use crate::{
28    LoopMode, ShuffleMode, changed_state,
29    decoder::{DecoderCommand, DecoderTx},
30    event_handler::EventTx,
31    player::playback::{CpalError, DeviceConfig},
32    playlist::Playlist,
33};
34
35mod opened_decoder;
36pub use opened_decoder::*;
37
38pub mod playback;
39
40mod ipc;
41pub use ipc::*;
42
43mod state;
44pub(crate) use state::*;
45
46mod thread;
47
48#[derive(Debug, Error)]
49pub enum PlayerError {
50    #[error("{0}")]
51    Io(#[from] std::io::Error),
52
53    #[error("{0}")]
54    Config(#[from] ConfigError),
55
56    #[error("{0}")]
57    Cpal(#[from] CpalError),
58
59    #[error("{0}")]
60    Symphonia(#[from] SymphoniaError),
61
62    #[error("{0}")]
63    Database(#[from] DatabaseError),
64
65    #[error("{0}")]
66    Decoder(#[from] DecodingError),
67
68    #[error("{0}")]
69    Resampler(#[from] ResamplerConstructionError),
70
71    #[error("Player attempted to play an unsupported container: '{0:?}'")]
72    UnsupportedContainer(ContainerFormat),
73
74    #[error("A critical thread panicked holding an interior mutex")]
75    CriticalMutexPoisoned,
76
77    #[error("A sender or receiver to a critical thread disconnected")]
78    CriticalThreadDisconnected,
79}
80
81impl<T> From<PoisonError<T>> for PlayerError {
82    fn from(_value: PoisonError<T>) -> Self {
83        Self::CriticalMutexPoisoned
84    }
85}
86
87impl From<RecvError> for PlayerError {
88    fn from(_value: RecvError) -> Self {
89        Self::CriticalThreadDisconnected
90    }
91}
92
93impl<T> From<SendError<T>> for PlayerError {
94    fn from(_value: SendError<T>) -> Self {
95        Self::CriticalThreadDisconnected
96    }
97}
98
99pub struct Player {
100    rx: Receiver<PlayerRequest>,
101    decoder_tx: Sender<DecoderCommand>,
102
103    event_tx: Sender<PlayerEvent>,
104
105    playlist: Playlist,
106
107    device_config: Arc<Mutex<DeviceConfig>>,
108    playback_state: Arc<AtomicPlaybackStatus>,
109    volume: Arc<AtomicU32>,
110}
111
112impl Player {
113    /// Sets or increments the volume to the input volume, clamps between `[0..1]`
114    fn set_volume(&mut self, volume: f32, increment: bool) -> f32 {
115        let volume = if increment {
116            f32::from_bits(self.volume.load(Ordering::Relaxed)) + volume
117        } else {
118            volume
119        }
120        .clamp(0.0, 1.0);
121
122        self.event_tx.event(PlayerEvent::VolumeChanged { volume });
123        self.volume.store(volume.to_bits(), Ordering::Relaxed);
124
125        changed_state();
126
127        volume
128    }
129
130    fn try_preload(&self, db: &LibraryDb) -> Result<(), PlayerError> {
131        if let Some(playable) = self.playlist.peek_next(db)? {
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.preload(decoder)?;
135        } else {
136            self.decoder_tx.unpreload()?;
137        }
138
139        Ok(())
140    }
141
142    fn replace_decoders(&mut self, pop: bool) -> Result<bool, PlayerError> {
143        let db = DbHandle::<LibraryDb>::open()?;
144        let load = if pop {
145            self.playlist.pop_next(&db)?
146        } else {
147            self.playlist.current(&db)?
148        };
149        let preload = self.playlist.peek_next(&db)?;
150
151        if let Some(load) = load {
152            let output_sample_rate = self.device_config.lock()?.config.sample_rate() as usize;
153            let load = OpenedDecoder::new(load, output_sample_rate)?;
154            if let Some(preload) = preload {
155                let preload = OpenedDecoder::new(preload, output_sample_rate)?;
156                self.decoder_tx.load_and_preload(load, preload)?;
157                return Ok(true);
158            }
159
160            self.decoder_tx.load(load)?;
161            Ok(true)
162        } else {
163            self.decoder_tx.stop()?;
164            Ok(false)
165        }
166    }
167}
168
169bitflags! {
170    #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
171    pub struct PlayerQueryFlags: u16 {
172        const PLAYBACK_STATE        = 1 << 0;
173
174        const CURRENTLY_PLAYING     = 1 << 1;
175
176        const VOLUME                = 1 << 2;
177        const TIME                  = 1 << 3;
178
179        const QUEUE                 = 1 << 4;
180        const QUEUE_STATE           = 1 << 5;
181
182        const PLAYLIST              = 1 << 6;
183        const PLAYLIST_STATE        = 1 << 7;
184
185        const TRACKLIST             = 1 << 8;
186        const TRACKLIST_POSITION    = 1 << 9;
187
188        const SHUFFLE_MODE          = 1 << 10;
189        const LOOP_MODE             = 1 << 11;
190    }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, Default)]
194pub struct QueryResult {
195    pub playback_state: Option<PlaybackStatus>,
196
197    pub currently_playing: Option<Option<TrackId>>,
198    pub currently_playing_index: Option<Option<usize>>,
199
200    pub volume: Option<f32>,
201    pub time: Option<Option<f64>>,
202
203    pub queue: Option<Vec<TrackId>>,
204    pub queue_state: Option<Hash>,
205
206    pub playlist: Option<Vec<Collectable>>,
207    pub playlist_state: Option<Hash>,
208
209    pub tracklist: Option<Vec<TrackId>>,
210    pub tracklist_position: Option<Option<usize>>,
211
212    pub shuffle_mode: Option<ShuffleMode>,
213    pub loop_mode: Option<LoopMode>,
214}
215
216#[repr(u8)]
217#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
218pub enum PlaybackStatus {
219    Playing,
220    Paused,
221    Stopped,
222}
223
224impl Display for PlaybackStatus {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        match self {
227            PlaybackStatus::Playing => f.write_str("Playing"),
228            PlaybackStatus::Paused => f.write_str("Paused"),
229            PlaybackStatus::Stopped => f.write_str("Stopped"),
230        }
231    }
232}
233
234pub struct AtomicPlaybackStatus(AtomicU8);
235
236impl AtomicPlaybackStatus {
237    #[must_use]
238    pub fn new(state: PlaybackStatus) -> Self {
239        Self(AtomicU8::new(state as u8))
240    }
241
242    pub fn load(&self, order: Ordering) -> PlaybackStatus {
243        match self.0.load(order) {
244            0 => PlaybackStatus::Playing,
245            1 => PlaybackStatus::Paused,
246            2 => PlaybackStatus::Stopped,
247            _ => unreachable!(),
248        }
249    }
250
251    pub fn store(&self, state: PlaybackStatus, order: Ordering) {
252        self.0.store(state as u8, order);
253    }
254}