1#![allow(clippy::module_name_repetitions)]
3
4use std::{fmt::Display, ops::Range, time::Duration};
5
6use mecomp_storage::db::schemas::song::Song;
7use one_or_many::OneOrMany;
8
9use crate::{
10 format_duration,
11 state::{RepeatMode, SeekType, StateAudio},
12};
13
14#[derive(Debug)]
16pub enum AudioCommand {
17 Play,
18 Pause,
19 Stop,
20 TogglePlayback,
21 RestartSong,
22 ClearPlayer,
24 Queue(QueueCommand),
26 Exit,
28 ReportStatus(tokio::sync::oneshot::Sender<StateAudio>),
30 Volume(VolumeCommand),
32 Seek(SeekType, Duration),
34}
35
36impl PartialEq for AudioCommand {
37 #[inline]
38 fn eq(&self, other: &Self) -> bool {
39 match (self, other) {
40 (Self::Play, Self::Play)
41 | (Self::Pause, Self::Pause)
42 | (Self::TogglePlayback, Self::TogglePlayback)
43 | (Self::ClearPlayer, Self::ClearPlayer)
44 | (Self::RestartSong, Self::RestartSong)
45 | (Self::Exit, Self::Exit)
46 | (Self::Stop, Self::Stop)
47 | (Self::ReportStatus(_), Self::ReportStatus(_)) => true,
48 (Self::Queue(a), Self::Queue(b)) => a == b,
49 (Self::Volume(a), Self::Volume(b)) => a == b,
50 (Self::Seek(a, b), Self::Seek(c, d)) => a == c && b == d,
51 #[cfg(not(tarpaulin_include))]
52 _ => false,
53 }
54 }
55}
56
57impl Display for AudioCommand {
58 #[inline]
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Self::Play => write!(f, "Play"),
62 Self::Pause => write!(f, "Pause"),
63 Self::Stop => write!(f, "Stop"),
64 Self::TogglePlayback => write!(f, "Toggle Playback"),
65 Self::RestartSong => write!(f, "Restart Song"),
66 Self::ClearPlayer => write!(f, "Clear Player"),
67 Self::Queue(command) => write!(f, "Queue: {command}"),
68 Self::Exit => write!(f, "Exit"),
69 Self::ReportStatus(_) => write!(f, "Report Status"),
70 Self::Volume(command) => write!(f, "Volume: {command}"),
71 Self::Seek(seek_type, duration) => {
72 write!(
73 f,
74 "Seek: {seek_type} {} (HH:MM:SS)",
75 format_duration(duration)
76 )
77 }
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum QueueCommand {
85 PlayNextSong,
88 SkipForward(usize),
90 SkipBackward(usize),
92 SetPosition(usize),
94 Shuffle,
96 AddToQueue(Box<OneOrMany<Song>>),
98 RemoveRange(Range<usize>),
100 Clear,
102 SetRepeatMode(RepeatMode),
104}
105
106impl Display for QueueCommand {
107 #[inline]
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::SkipForward(n) => write!(f, "Skip Forward by {n}"),
111 Self::SkipBackward(n) => write!(f, "Skip Backward by {n}"),
112 Self::SetPosition(n) => write!(f, "Set Position to {n}"),
113 Self::Shuffle => write!(f, "Shuffle"),
114 Self::AddToQueue(song_box) => match &**song_box {
115 OneOrMany::None => write!(f, "Add nothing"),
116 OneOrMany::One(song) => {
117 write!(f, "Add \"{}\"", song.title)
118 }
119 OneOrMany::Many(songs) => {
120 write!(
121 f,
122 "Add {:?}",
123 songs
124 .iter()
125 .map(|song| song.title.to_string())
126 .collect::<Vec<_>>()
127 )
128 }
129 },
130 Self::RemoveRange(range) => {
131 write!(f, "Remove items {}..{}", range.start, range.end)
132 }
133 Self::Clear => write!(f, "Clear"),
134 Self::SetRepeatMode(mode) => {
135 write!(f, "Set Repeat Mode to {mode}")
136 }
137 Self::PlayNextSong => write!(f, "Play Next Song"),
138 }
139 }
140}
141
142#[derive(Debug, Copy, Clone, PartialEq)]
144pub enum VolumeCommand {
145 Up(f32),
146 Down(f32),
147 Set(f32),
148 Mute,
149 Unmute,
150 ToggleMute,
151}
152
153impl Display for VolumeCommand {
154 #[inline]
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 match self {
157 Self::Up(percent) => write!(f, "+{percent:.0}%", percent = percent * 100.0),
158 Self::Down(percent) => write!(f, "-{percent:.0}%", percent = percent * 100.0),
159 Self::Set(percent) => write!(f, "={percent:.0}%", percent = percent * 100.0),
160 Self::Mute => write!(f, "Mute"),
161 Self::Unmute => write!(f, "Unmute"),
162 Self::ToggleMute => write!(f, "Toggle Mute"),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use pretty_assertions::assert_str_eq;
170 use rstest::rstest;
171 use std::time::Duration;
172
173 use super::*;
174
175 #[rstest]
176 #[case(AudioCommand::Play, AudioCommand::Play, true)]
177 #[case(AudioCommand::Play, AudioCommand::Pause, false)]
178 #[case(AudioCommand::Pause, AudioCommand::Pause, true)]
179 #[case(AudioCommand::TogglePlayback, AudioCommand::TogglePlayback, true)]
180 #[case(AudioCommand::RestartSong, AudioCommand::RestartSong, true)]
181 #[case(
182 AudioCommand::Queue(QueueCommand::Clear),
183 AudioCommand::Queue(QueueCommand::Clear),
184 true
185 )]
186 #[case(
187 AudioCommand::Queue(QueueCommand::Clear),
188 AudioCommand::Queue(QueueCommand::Shuffle),
189 false
190 )]
191 #[case(
192 AudioCommand::Queue(QueueCommand::SkipForward(1)),
193 AudioCommand::Queue(QueueCommand::SkipForward(1)),
194 true
195 )]
196 #[case(
197 AudioCommand::Queue(QueueCommand::SkipForward(1)),
198 AudioCommand::Queue(QueueCommand::SkipForward(2)),
199 false
200 )]
201 #[case(
202 AudioCommand::Queue(QueueCommand::SkipBackward(1)),
203 AudioCommand::Queue(QueueCommand::SkipBackward(1)),
204 true
205 )]
206 #[case(
207 AudioCommand::Queue(QueueCommand::SkipBackward(1)),
208 AudioCommand::Queue(QueueCommand::SkipBackward(2)),
209 false
210 )]
211 #[case(
212 AudioCommand::Queue(QueueCommand::SetPosition(1)),
213 AudioCommand::Queue(QueueCommand::SetPosition(1)),
214 true
215 )]
216 #[case(
217 AudioCommand::Queue(QueueCommand::SetPosition(1)),
218 AudioCommand::Queue(QueueCommand::SetPosition(2)),
219 false
220 )]
221 #[case(
222 AudioCommand::Queue(QueueCommand::Shuffle),
223 AudioCommand::Queue(QueueCommand::Shuffle),
224 true
225 )]
226 #[case(
227 AudioCommand::Queue(QueueCommand::Shuffle),
228 AudioCommand::Queue(QueueCommand::Clear),
229 false
230 )]
231 #[case(
232 AudioCommand::Volume(VolumeCommand::Up(0.1)),
233 AudioCommand::Volume(VolumeCommand::Up(0.1)),
234 true
235 )]
236 #[case(
237 AudioCommand::Volume(VolumeCommand::Up(0.1)),
238 AudioCommand::Volume(VolumeCommand::Up(0.2)),
239 false
240 )]
241 #[case(
242 AudioCommand::Volume(VolumeCommand::Down(0.1)),
243 AudioCommand::Volume(VolumeCommand::Down(0.1)),
244 true
245 )]
246 #[case(
247 AudioCommand::Volume(VolumeCommand::Down(0.1)),
248 AudioCommand::Volume(VolumeCommand::Down(0.2)),
249 false
250 )]
251 #[case(
252 AudioCommand::Volume(VolumeCommand::Set(0.1)),
253 AudioCommand::Volume(VolumeCommand::Set(0.1)),
254 true
255 )]
256 #[case(
257 AudioCommand::Volume(VolumeCommand::Set(0.1)),
258 AudioCommand::Volume(VolumeCommand::Set(0.2)),
259 false
260 )]
261 #[case(
262 AudioCommand::Volume(VolumeCommand::Mute),
263 AudioCommand::Volume(VolumeCommand::Mute),
264 true
265 )]
266 #[case(
267 AudioCommand::Volume(VolumeCommand::Mute),
268 AudioCommand::Volume(VolumeCommand::Unmute),
269 false
270 )]
271 #[case(
272 AudioCommand::Volume(VolumeCommand::Unmute),
273 AudioCommand::Volume(VolumeCommand::Unmute),
274 true
275 )]
276 #[case(
277 AudioCommand::Volume(VolumeCommand::Unmute),
278 AudioCommand::Volume(VolumeCommand::Mute),
279 false
280 )]
281 #[case(
282 AudioCommand::Volume(VolumeCommand::ToggleMute),
283 AudioCommand::Volume(VolumeCommand::ToggleMute),
284 true
285 )]
286 #[case(
287 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(10)),
288 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(10)),
289 true
290 )]
291 #[case(
292 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(10)),
293 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(20)),
294 false
295 )]
296 #[case(
297 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(10)),
298 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(10)),
299 true
300 )]
301 #[case(
302 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(10)),
303 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(20)),
304 false
305 )]
306 #[case(
307 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(10)),
308 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(10)),
309 true
310 )]
311 #[case(
312 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(10)),
313 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(20)),
314 false
315 )]
316 #[case(
317 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(10)),
318 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(10)),
319 false
320 )]
321 #[case(
322 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(10)),
323 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(10)),
324 false
325 )]
326 #[case(
327 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(10)),
328 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(10)),
329 false
330 )]
331 fn test_audio_command_equality(
332 #[case] lhs: AudioCommand,
333 #[case] rhs: AudioCommand,
334 #[case] expected: bool,
335 ) {
336 let actual = lhs == rhs;
337 assert_eq!(actual, expected);
338 let actual = rhs == lhs;
339 assert_eq!(actual, expected);
340 }
341
342 fn dummy_song() -> Song {
344 Song {
345 id: Song::generate_id(),
346 title: "Song 1".into(),
347 artist: OneOrMany::None,
348 album_artist: OneOrMany::None,
349 album: "album".into(),
350 genre: OneOrMany::None,
351 runtime: Duration::from_secs(100),
352 track: None,
353 disc: None,
354 release_year: None,
355 extension: "mp3".into(),
356 path: "foo/bar.mp3".into(),
357 }
358 }
359
360 #[rstest]
361 #[case(AudioCommand::Play, "Play")]
362 #[case(AudioCommand::Pause, "Pause")]
363 #[case(AudioCommand::TogglePlayback, "Toggle Playback")]
364 #[case(AudioCommand::ClearPlayer, "Clear Player")]
365 #[case(AudioCommand::RestartSong, "Restart Song")]
366 #[case(AudioCommand::Queue(QueueCommand::Clear), "Queue: Clear")]
367 #[case(AudioCommand::Queue(QueueCommand::Shuffle), "Queue: Shuffle")]
368 #[case(
369 AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(OneOrMany::None))),
370 "Queue: Add nothing"
371 )]
372 #[case(
373 AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(OneOrMany::One(dummy_song())))),
374 "Queue: Add \"Song 1\""
375 )]
376 #[case(
377 AudioCommand::Queue(QueueCommand::AddToQueue(Box::new(OneOrMany::Many(vec![dummy_song()])))),
378 "Queue: Add [\"Song 1\"]"
379 )]
380 #[case(
381 AudioCommand::Queue(QueueCommand::RemoveRange(0..1)),
382 "Queue: Remove items 0..1"
383 )]
384 #[case(
385 AudioCommand::Queue(QueueCommand::SetRepeatMode(RepeatMode::None)),
386 "Queue: Set Repeat Mode to None"
387 )]
388 #[case(
389 AudioCommand::Queue(QueueCommand::SkipForward(1)),
390 "Queue: Skip Forward by 1"
391 )]
392 #[case(
393 AudioCommand::Queue(QueueCommand::SkipBackward(1)),
394 "Queue: Skip Backward by 1"
395 )]
396 #[case(
397 AudioCommand::Queue(QueueCommand::SetPosition(1)),
398 "Queue: Set Position to 1"
399 )]
400 #[case(AudioCommand::Volume(VolumeCommand::Up(0.1)), "Volume: +10%")]
401 #[case(AudioCommand::Volume(VolumeCommand::Down(0.1)), "Volume: -10%")]
402 #[case(AudioCommand::Volume(VolumeCommand::Set(0.1)), "Volume: =10%")]
403 #[case(AudioCommand::Volume(VolumeCommand::Mute), "Volume: Mute")]
404 #[case(AudioCommand::Volume(VolumeCommand::Unmute), "Volume: Unmute")]
405 #[case(AudioCommand::Volume(VolumeCommand::ToggleMute), "Volume: Toggle Mute")]
406 #[case(AudioCommand::Exit, "Exit")]
407 #[case(AudioCommand::ReportStatus(tokio::sync::oneshot::channel().0), "Report Status")]
408 #[case(
409 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(10)),
410 "Seek: Absolute 00:00:10.00 (HH:MM:SS)"
411 )]
412 #[case(
413 AudioCommand::Seek(SeekType::RelativeForwards, Duration::from_secs(10)),
414 "Seek: Forwards 00:00:10.00 (HH:MM:SS)"
415 )]
416 #[case(
417 AudioCommand::Seek(SeekType::RelativeBackwards, Duration::from_secs(10)),
418 "Seek: Backwards 00:00:10.00 (HH:MM:SS)"
419 )]
420 #[case(
421 AudioCommand::Seek(SeekType::Absolute, Duration::from_secs(3600 + 120 + 1)),
422 "Seek: Absolute 01:02:01.00 (HH:MM:SS)"
423 )]
424 fn test_audio_command_display(#[case] command: AudioCommand, #[case] expected: &str) {
425 let actual = command.to_string();
426 assert_str_eq!(actual, expected);
427 }
428}