Skip to main content

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}