chilen_ipc/playback.rs
1use std::{path::PathBuf, time::Duration};
2
3use serde::{Deserialize, Serialize};
4
5use crate::library::Track;
6
7/// Playback state of the player.
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum PlaybackState {
10 /// The player is playing.
11 Playing,
12 /// The player is paused.
13 Paused,
14 /// The player is stopped.
15 ///
16 /// Playing will play the current track from the beginning.
17 #[default]
18 Stopped,
19}
20
21impl std::fmt::Display for PlaybackState {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::Playing => write!(f, "Playing"),
25 Self::Paused => write!(f, "Paused"),
26 Self::Stopped => write!(f, "Stopped"),
27 }
28 }
29}
30
31/// Specifies how the player loops playback.
32#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum LoopState {
34 /// The playback will stop when there are no more tracks to play.
35 #[default]
36 Off,
37 /// The current track will start again from the beginning once it has finished playing.
38 Track,
39 /// The playback will loop through the entire queue.
40 Playlist,
41}
42
43/// Shuffle state of the player.
44#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45pub enum ShuffleState {
46 /// Play tracks in their original order.
47 ///
48 /// For example tracks from a playlist will be played in the order they appear in the playlist.
49 #[default]
50 Off,
51 /// Shuffle the tracks in the queue.
52 On,
53}
54
55impl From<ShuffleState> for bool {
56 fn from(s: ShuffleState) -> bool {
57 match s {
58 ShuffleState::Off => false,
59 ShuffleState::On => true,
60 }
61 }
62}
63
64impl From<bool> for ShuffleState {
65 fn from(value: bool) -> Self {
66 if value { Self::On } else { Self::Off }
67 }
68}
69
70impl std::fmt::Display for ShuffleState {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 match self {
73 Self::Off => write!(f, "Off"),
74 Self::On => write!(f, "On"),
75 }
76 }
77}
78
79impl std::fmt::Display for LoopState {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 Self::Off => write!(f, "Off"),
83 Self::Track => write!(f, "Track"),
84 Self::Playlist => write!(f, "Playlist"),
85 }
86 }
87}
88
89/// Signed duration type used for seeking.
90///
91/// This is just a bare bones type that should only be used for
92/// [seeking player position](PlaybackCommand::Seek), and not as a [`Duration`] replacement.
93#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
94pub enum SignedDuration {
95 Positive(Duration),
96 Negative(Duration),
97}
98
99impl SignedDuration {
100 pub fn from_secs<T: Into<i64> + Copy>(secs: T) -> SignedDuration {
101 if secs.into() < 0 {
102 SignedDuration::Negative(Duration::from_secs(secs.into().abs().try_into().unwrap()))
103 } else {
104 SignedDuration::Positive(Duration::from_secs(secs.into().abs().try_into().unwrap()))
105 }
106 }
107}
108
109/// Player volume.
110///
111/// Values passed to this struct will be clamped between `0.0` (no sound at all) and `1.0` (regular
112/// volume).
113#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
114pub struct PlayerVolume {
115 volume: f64,
116}
117
118impl Default for PlayerVolume {
119 fn default() -> Self {
120 Self { volume: 1.0 }
121 }
122}
123
124impl std::fmt::Display for PlayerVolume {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "Player volume: {}", self.volume)
127 }
128}
129
130impl PlayerVolume {
131 /// Create a new [`PlayerVolume`] struct with the specified volume.
132 ///
133 /// The passed `volume` parameter will be clamped between `0.0` and `1.0`.
134 pub fn new(volume: f64) -> Self {
135 Self {
136 volume: volume.clamp(0.0, 1.0),
137 }
138 }
139
140 /// Set the volume.
141 ///
142 /// The passed `volume` parameter will be clamped between 0.0 and 1.0.
143 pub fn set(&mut self, volume: f64) {
144 self.volume = volume.clamp(0.0, 1.0);
145 }
146
147 /// Get the wrapped value.
148 pub fn get(&self) -> f64 {
149 self.volume
150 }
151}
152
153/// The speed at which tracks are played.
154///
155/// Rate of `1.0` will play tracks at their original speed, `2.0` will play them twice as fast, and
156/// `0.5` will slow them to half speed.
157#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
158pub struct PlaybackRate {
159 rate: f64,
160 min: f64,
161 max: f64,
162}
163
164impl Default for PlaybackRate {
165 fn default() -> Self {
166 Self {
167 rate: 1.0,
168 min: 0.1,
169 max: 10.0,
170 }
171 }
172}
173
174impl<T: Into<f64>> From<T> for PlaybackRate {
175 fn from(value: T) -> Self {
176 let mut def = Self::default();
177 def.set_value(value.into());
178 def
179 }
180}
181
182impl std::fmt::Display for PlaybackRate {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(
185 f,
186 "Current playback rate: {}, min: {}, max: {}",
187 self.rate, self.min, self.max
188 )
189 }
190}
191
192impl PlaybackRate {
193 /// Create a new [PlaybackRate] struct.
194 pub fn new(rate: f64, min: f64, max: f64) -> PlaybackRate {
195 Self { rate, min, max }
196 }
197
198 fn clamp(&mut self) {
199 self.rate = self.rate.clamp(self.min, self.max)
200 }
201
202 /// Checks if the given rate value is in the allowed range.
203 pub fn is_in_range(&self, rate: f64) -> bool {
204 rate >= self.min && rate <= self.max
205 }
206
207 /// Set the rate multiplier.
208 ///
209 /// This value will be clamped with the min and max values.
210 pub fn set_value(&mut self, rate: f64) {
211 self.rate = rate.clamp(self.min, self.max)
212 }
213
214 /// Get the rate multiplier.
215 pub fn get_value(&self) -> f64 {
216 self.rate
217 }
218
219 /// Get the rate multiplier clamped to `f32`.
220 pub fn get_value_f32(&self) -> f32 {
221 if self.rate.is_nan() {
222 f32::NAN
223 } else if self.rate > f32::MAX as f64 {
224 f32::MAX
225 } else if self.rate < f32::MIN as f64 {
226 f32::MIN
227 } else {
228 self.rate as f32
229 }
230 }
231
232 /// Set the minimum acceptable value for the playback rate.
233 pub fn set_min(&mut self, min: f64) {
234 self.min = min.clamp(0.0, self.max);
235 self.clamp();
236 }
237
238 /// Get the minimum acceptable value for the playback rate.
239 pub fn get_min(&self) -> f64 {
240 self.min
241 }
242
243 /// Set the maximum acceptable value for the playback rate.
244 pub fn set_max(&mut self, max: f64) {
245 self.max = max.clamp(self.min, f64::MAX);
246 self.clamp();
247 }
248
249 /// Get the maximum acceptable value for the playback rate.
250 pub fn get_max(&self) -> f64 {
251 self.max
252 }
253}
254
255/// Subcommand of [`Command`](crate::Command) for managing audio playback in the `daemon`.
256///
257/// The expected response may be different depending on the command sent. If it isn't specified in
258/// the variant documentation, assume [`Response::Ok`](crate::Response::Ok) is the expected
259/// response.
260#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
261pub enum PlaybackCommand {
262 /// Play the current track or play a track at a specific index in the queue.
263 Play(Option<usize>),
264 /// Pause the player.
265 Pause,
266 /// Stop the player.
267 Stop,
268 /// Toggle between play/pause.
269 TogglePlaying,
270 /// Get the [`PlaybackState`] of the player.
271 ///
272 /// The daemon will respond with [`PlaybackResponse::PlaybackState`] if successful.
273 GetPlaybackState,
274 /// Set a new queue for the player.
275 ///
276 /// If any of the provided tracks are not registered by chilen (added after last library
277 /// reload), the daemon will return
278 /// [`LibraryError::NoSuchTrack`](crate::library::LibraryError::NoSuchTrack).
279 SetQueue(Vec<PathBuf>),
280 /// Append tracks to the queue.
281 ///
282 /// If any of the provided tracks are not registered by chilen (added after last library
283 /// reload), the daemon will return
284 /// [`LibraryError::NoSuchTrack`](crate::library::LibraryError::NoSuchTrack).
285 AppendToQueue(Vec<PathBuf>),
286 /// Load a playlist and append its tracks to the queue.
287 ///
288 /// If there's no [playlist](crate::library::Playlist) with the provided name present in the
289 /// [music library](crate::library::MusicLibrary),
290 /// [`LibraryError::NoSuchPlaylist`](crate::library::LibraryError::NoSuchPlaylist) will be
291 /// returned, and no changes to the queue will be made.
292 AppendPlaylist(String),
293 /// Load a playlist and put its tracks in the queue.
294 ///
295 /// If there's no [playlist](crate::library::Playlist) with the provided name present in the
296 /// [music library](crate::library::MusicLibrary),
297 /// [`LibraryError::NoSuchPlaylist`](crate::library::LibraryError::NoSuchPlaylist) will be
298 /// returned, and no changes to the queue will be made.
299 SetPlaylist(String),
300 /// Get the current [track](Track).
301 ///
302 /// The `daemon` will respond to this with [`PlaybackResponse::Track`] if successful.
303 GetCurrentTrack,
304 /// Skip to the next track.
305 Next,
306 /// Skip to the previous track.
307 Previous,
308 /// Set the [loop state](LoopState) of the player.
309 SetLoopState(LoopState),
310 /// Get the [loop state](LoopState) of the player.
311 ///
312 /// The `daemon` will respond to this with [`PlaybackResponse::LoopState`] if successful.
313 GetLoopState,
314 /// Set the [playback rate](PlaybackRate) of the player.
315 ///
316 /// This command will fail if the `daemon` is configured to not allow playback rate
317 /// modification or if the specified rate value was out of the acceptable range.
318 ///
319 /// If the `daemon` is configured not to allow playback rate modification,
320 /// [`PlaybackError::FixedRate`] will be returned.
321 ///
322 /// If the provided rate value is out of the allowed range, [`PlaybackError::RateOutOfRange`]
323 /// will be returned.
324 SetRate(f64),
325 /// Get the [playback rate](PlaybackRate) of the player.
326 ///
327 /// The `daemon` will respond to this with [`PlaybackResponse::PlaybackRate`] if
328 /// successful.
329 GetRate,
330 /// Set the [shuffle state](ShuffleState) of the player.
331 ///
332 /// The `daemon` will always respond to this command with [`PlaybackError::ShuffleNotSupported`]
333 /// if it was built without shuffle support.
334 SetShuffleState(ShuffleState),
335 /// Get the [shuffle state](ShuffleState) of the player.
336 ///
337 /// If the `daemon` was built without shuffle support, it will always respond to this command with
338 /// [`ShuffleState::Off`]. Otherwise, it will return [`PlaybackResponse::ShuffleState`].
339 GetShuffleState,
340 /// Set the position of the player.
341 SetPlayerPosition(Duration),
342 /// Change the player position by a time delta.
343 Seek(SignedDuration),
344 /// Get the position of the player.
345 ///
346 /// The `daemon` will respond to this with [`PlaybackResponse::PlayerPosition`] if
347 /// successful.
348 GetPlayerPosition,
349 /// Set the volume of the player.
350 SetPlayerVolume(PlayerVolume),
351 /// Get the volume of the player.
352 ///
353 /// The `daemon` will respond to this with [`PlaybackResponse::PlayerVolume`] if
354 /// successful.
355 GetPlayerVolume,
356}
357
358/// Error originating from the playback module of the `daemon`.
359#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
360pub enum PlaybackError {
361 /// The audio player is not connected.
362 ///
363 /// This may happen if the device doesn't have an audio device or none of the audio devices are
364 /// marked as default.
365 PlayerNotConnected,
366 /// The playback state is not initialized.
367 ///
368 /// This error may occur when a [`PlaybackCommand`] is sent to the `daemon` too early, before the
369 /// state is restored from cache.
370 StateNotInitialized,
371 /// The queue is empty.
372 QueueEmpty,
373 /// The audio file could not be opened, has an unsupported format or is corrupt.
374 SourceError,
375 /// The player is already playing.
376 PlayerPlaying,
377 /// The player is already paused.
378 PlayerPaused,
379 /// Thrown when a client attempts to stop the player when it was already stopped or when a
380 /// client attempts to seek while the player is stopped.
381 PlayerStopped,
382 /// Seek is not supported for the current audio source.
383 SeekNotSupported,
384 /// Cannot go to the previous track.
385 ///
386 /// This means that the current track is first in the queue and the [loop state](LoopState) is
387 /// set to [`LoopState::Off`].
388 CannotGoPrevious,
389 /// Cannot go to the next track.
390 ///
391 /// This means that the current track is last in the queue and the [loop state](LoopState) is
392 /// set to [`LoopState::Off`].
393 CannotGoNext,
394 /// The daemon was not built with shuffle support.
395 ShuffleNotSupported,
396 /// No track at this index.
397 NoTrackAtIndex(usize),
398 /// The specified rate value was out of the allowed range.
399 RateOutOfRange,
400 /// The modification of the playback rate is not allowed.
401 FixedRate,
402 /// The player position could not be set because the duration provided was invalid.
403 ///
404 /// The player will refuse to seek by 0s to prevent audio popping.
405 InvalidDuration,
406 /// Overflow detected while performing a seek operation.
407 DurationOverflow,
408}
409
410impl std::fmt::Display for PlaybackError {
411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412 match self {
413 Self::PlayerNotConnected => write!(f, "The audio player is not connected"),
414 Self::StateNotInitialized => write!(f, "The playback state is not initialized"),
415 Self::QueueEmpty => write!(f, "The queue is empty"),
416 Self::SourceError => write!(
417 f,
418 "The audio file could not be opened, has an unsupported format or is corrupt"
419 ),
420 Self::PlayerPlaying => write!(f, "The player is already playing"),
421 Self::PlayerPaused => write!(f, "The player is already paused"),
422 Self::PlayerStopped => write!(f, "The player is stopped"),
423 Self::SeekNotSupported => write!(f, "Seek is not supported"),
424 Self::CannotGoPrevious => write!(f, "Cannot go to the previous track"),
425 Self::CannotGoNext => write!(f, "Cannot go to the next track"),
426 Self::ShuffleNotSupported => write!(f, "The daemon was not built with shuffle support"),
427 Self::NoTrackAtIndex(index) => write!(f, "No track was found at index {index}"),
428 Self::RateOutOfRange => {
429 write!(f, "The specified rate value was out of the allowed range")
430 }
431 Self::FixedRate => write!(f, "The modification of the playback rate is disallowed"),
432 Self::InvalidDuration => write!(
433 f,
434 "The player position could not be set because the duration provided was invalid"
435 ),
436 Self::DurationOverflow => {
437 write!(f, "Overflow detected while performing a seek operation")
438 }
439 }
440 }
441}
442
443/// Response originating from the playback module of the `daemon` used in
444/// ed as an enum value for th[`Response`](crate::Response).
445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub enum PlaybackResponse {
447 PlaybackState(PlaybackState),
448 LoopState(LoopState),
449 PlaybackRate(PlaybackRate),
450 ShuffleState(ShuffleState),
451 Track(Option<Box<Track>>),
452 PlayerVolume(PlayerVolume),
453 PlayerPosition(Duration),
454}
455
456impl std::fmt::Display for PlaybackResponse {
457 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
458 match self {
459 Self::PlaybackState(state) => write!(f, "{state}"),
460 Self::LoopState(state) => write!(f, "{state}"),
461 Self::PlaybackRate(rate) => write!(f, "{rate}"),
462 Self::ShuffleState(state) => write!(f, "{state}"),
463 Self::Track(track) => match track {
464 Some(track) => write!(f, "{track}"),
465 None => write!(f, "None"),
466 },
467 Self::PlayerVolume(volume) => write!(f, "{volume}"),
468 Self::PlayerPosition(position) => write!(f, "{position:?}"),
469 }
470 }
471}
472
473/// Event originating from the playback module of the `daemon`.
474#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
475pub enum PlaybackEvent {
476 /// Sent when the [playback state](PlaybackState) changes, for instance when the player is
477 /// paused.
478 PlaybackStateChanged(PlaybackState),
479 /// Sent when the [loop state](LoopState) of the player changes.
480 LoopStateChanged(LoopState),
481 /// Sent when the [shuffle state](ShuffleState) of the player changes.
482 ShuffleStateChanged(ShuffleState),
483 /// Sent when the track queue changes.
484 QueueChanged(Vec<Track>),
485 /// Sent when the current track changes.
486 PositionChanged(usize),
487 /// Sent when the position of the player changes.
488 PlayerPositionChanged(Duration),
489 /// Sent when the [volume](PlayerVolume) of the player changes.
490 PlayerVolumeChanged(PlayerVolume),
491 /// Sent when the [playback rate](PlaybackRate) of the player changes.
492 RateChanged(PlaybackRate),
493}