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 [`Error::UnknownTrack`](crate::Error::UnknownTrack).
278 SetQueue(Vec<PathBuf>),
279 /// Append tracks to the queue.
280 ///
281 /// If any of the provided tracks are not registered by chilen (added after last library
282 /// reload), the daemon will return [`Error::UnknownTrack`](crate::Error::UnknownTrack).
283 AppendToQueue(Vec<PathBuf>),
284 /// Load a playlist and append its tracks to the queue.
285 ///
286 /// If there's no [playlist](crate::library::Playlist) with the provided name present in the
287 /// [music library](crate::library::MusicLibrary),
288 /// [`Error::UnknownPlaylist`](crate::Error::UnknownPlaylist) will be returned, and no changes
289 /// to the queue will be made.
290 AppendPlaylist(String),
291 /// Load a playlist and put its tracks in the queue.
292 ///
293 /// If there's no [playlist](crate::library::Playlist) with the provided name present in the
294 /// [music library](crate::library::MusicLibrary),
295 /// [`Error::UnknownPlaylist`](crate::Error::UnknownPlaylist) will be returned, and no changes
296 /// to the queue will be made.
297 SetPlaylist(String),
298 /// Get the current [track](Track).
299 ///
300 /// The daemon will respond to this with [`PlaybackResponse::Track`] if successful.
301 GetCurrentTrack,
302 /// Skip to the next track.
303 Next,
304 /// Skip to the previous track.
305 Previous,
306 /// Set the [loop state](LoopState) of the player.
307 SetLoopState(LoopState),
308 /// Get the [loop state](LoopState) of the player.
309 ///
310 /// The daemon will respond to this with [`PlaybackResponse::LoopState`] if successful.
311 GetLoopState,
312 /// Set the [playback rate](PlaybackRate) of the player.
313 ///
314 /// This command will fail if the daemon is configured to not allow playback rate
315 /// modification or if the specified rate value was out of the acceptable range.
316 ///
317 /// If the daemon is configured not to allow playback rate modification,
318 /// [`Error::FixedRate`](crate::Error::FixedRate) will be returned.
319 ///
320 /// If the provided rate value is out of the allowed range,
321 /// [`Error::RateOutOfRange`](crate::Error::RateOutOfRange) will be returned.
322 SetRate(f64),
323 /// Get the [playback rate](PlaybackRate) of the player.
324 ///
325 /// The daemon will respond to this with [`PlaybackResponse::PlaybackRate`] if
326 /// successful.
327 GetRate,
328 /// Set the [shuffle state](ShuffleState) of the player.
329 ///
330 /// The daemon will always respond to this command with
331 /// [`Error::ShuffleNotSupported`](crate::Error::ShuffleNotSupported) if it was built without
332 /// shuffle support.
333 SetShuffleState(ShuffleState),
334 /// Get the [shuffle state](ShuffleState) of the player.
335 ///
336 /// If the daemon was built without shuffle support, it will always respond to this command with
337 /// [`ShuffleState::Off`]. Otherwise, it will return [`PlaybackResponse::ShuffleState`].
338 GetShuffleState,
339 /// Set the position of the player.
340 SetPlayerPosition(Duration),
341 /// Change the player position by a time delta.
342 Seek(SignedDuration),
343 /// Get the position of the player.
344 ///
345 /// The daemon will respond to this with [`PlaybackResponse::PlayerPosition`] if
346 /// successful.
347 GetPlayerPosition,
348 /// Set the volume of the player.
349 SetPlayerVolume(PlayerVolume),
350 /// Get the volume of the player.
351 ///
352 /// The daemon will respond to this with [`PlaybackResponse::PlayerVolume`] if
353 /// successful.
354 GetPlayerVolume,
355 /// Set a track, a directory with tracks or an M3U8 playlist as the current queue.
356 ///
357 /// Tracks outside of the music library will be ignored.
358 OpenURI(String),
359}
360
361/// Response originating from the playback module of the daemon used in
362/// ed as an enum value for th[`Response`](crate::Response).
363#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
364pub enum PlaybackResponse {
365 PlaybackState(PlaybackState),
366 LoopState(LoopState),
367 PlaybackRate(PlaybackRate),
368 ShuffleState(ShuffleState),
369 Track(Option<Box<Track>>),
370 PlayerVolume(PlayerVolume),
371 PlayerPosition(Duration),
372}
373
374impl std::fmt::Display for PlaybackResponse {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 match self {
377 Self::PlaybackState(state) => write!(f, "{state}"),
378 Self::LoopState(state) => write!(f, "{state}"),
379 Self::PlaybackRate(rate) => write!(f, "{rate}"),
380 Self::ShuffleState(state) => write!(f, "{state}"),
381 Self::Track(track) => match track {
382 Some(track) => write!(f, "{track}"),
383 None => write!(f, "None"),
384 },
385 Self::PlayerVolume(volume) => write!(f, "{volume}"),
386 Self::PlayerPosition(position) => write!(f, "{position:?}"),
387 }
388 }
389}
390
391/// Event originating from the playback module of the daemon.
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub enum PlaybackEvent {
394 /// Sent when the [playback state](PlaybackState) changes, for instance when the player is
395 /// paused.
396 PlaybackStateChanged(PlaybackState),
397 /// Sent when the [loop state](LoopState) of the player changes.
398 LoopStateChanged(LoopState),
399 /// Sent when the [shuffle state](ShuffleState) of the player changes.
400 ShuffleStateChanged(ShuffleState),
401 /// Sent when the track queue changes.
402 QueueChanged(Vec<Track>),
403 /// Sent when the current track changes.
404 PositionChanged(usize),
405 /// Sent when the position of the player changes.
406 PlayerPositionChanged(Duration),
407 /// Sent when the [volume](PlayerVolume) of the player changes.
408 PlayerVolumeChanged(PlayerVolume),
409 /// Sent when the [playback rate](PlaybackRate) of the player changes.
410 RateChanged(PlaybackRate),
411}