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 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}