1
//! High-level API extension for [`Mpv`].
2

            
3
use crate::{
4
    parse_property, IntoRawCommandPart, LoopProperty, Mpv, MpvCommand, MpvDataType, MpvError,
5
    Playlist, PlaylistAddOptions, Property, SeekOptions,
6
};
7
use serde::{Deserialize, Serialize};
8
use std::collections::HashMap;
9

            
10
/// Generic high-level command for changing a number property.
11
#[derive(Debug, Clone, Serialize, Deserialize)]
12
pub enum NumberChangeOptions {
13
    Absolute,
14
    Increase,
15
    Decrease,
16
}
17

            
18
impl IntoRawCommandPart for NumberChangeOptions {
19
    fn into_raw_command_part(self) -> String {
20
        match self {
21
            NumberChangeOptions::Absolute => "absolute".to_string(),
22
            NumberChangeOptions::Increase => "increase".to_string(),
23
            NumberChangeOptions::Decrease => "decrease".to_string(),
24
        }
25
    }
26
}
27

            
28
/// Generic high-level switch for toggling boolean properties.
29
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
30
pub enum Switch {
31
    On,
32
    Off,
33
    Toggle,
34
}
35

            
36
/// Options for [`MpvExt::playlist_add`].
37
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
38
pub enum PlaylistAddTypeOptions {
39
    File,
40
    Playlist,
41
}
42

            
43
/// A set of typesafe high-level functions to interact with [`Mpv`].
44
// TODO: fix this
45
#[allow(async_fn_in_trait)]
46
pub trait MpvExt {
47
    // COMMANDS
48

            
49
    /// Seek to a specific position in the current video.
50
    async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError>;
51

            
52
    /// Shuffle the current playlist.
53
    async fn playlist_shuffle(&self) -> Result<(), MpvError>;
54

            
55
    /// Remove an entry from the playlist.
56
    async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError>;
57

            
58
    /// Play the next entry in the playlist.
59
    async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError>;
60

            
61
    /// Play a specific entry in the playlist.
62
    async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError>;
63

            
64
    /// Move an entry in the playlist.
65
    ///
66
    /// The `from` parameter is the current position of the entry, and the `to` parameter is the new position.
67
    /// Mpv will then move the entry from the `from` position to the `to` position,
68
    /// shifting after `to` one number up. Paradoxically, that means that moving an entry further down the list
69
    /// will result in a final position that is one less than the `to` parameter.
70
    async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError>;
71

            
72
    /// Remove all entries from the playlist.
73
    async fn playlist_clear(&self) -> Result<(), MpvError>;
74

            
75
    /// Add a file or playlist to the playlist.
76
    async fn playlist_add(
77
        &self,
78
        file: &str,
79
        file_type: PlaylistAddTypeOptions,
80
        option: PlaylistAddOptions,
81
    ) -> Result<(), MpvError>;
82

            
83
    /// Start the current video from the beginning.
84
    async fn restart(&self) -> Result<(), MpvError>;
85

            
86
    /// Play the previous entry in the playlist.
87
    async fn prev(&self) -> Result<(), MpvError>;
88

            
89
    /// Notify mpv to send events whenever a property changes.
90
    /// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information.
91
    async fn observe_property(&self, id: u64, property: &str) -> Result<(), MpvError>;
92

            
93
    /// Stop observing a property.
94
    /// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information.
95
    async fn unobserve_property(&self, id: u64) -> Result<(), MpvError>;
96

            
97
    /// Skip to the next entry in the playlist.
98
    async fn next(&self) -> Result<(), MpvError>;
99

            
100
    /// Stop mpv completely, and kill the process.
101
    ///
102
    /// Note that this is different than forcefully killing the process using
103
    /// as handle to a subprocess, it will only send a command to mpv to ask
104
    /// it to exit itself. If mpv is stuck, it may not respond to this command.
105
    async fn kill(&self) -> Result<(), MpvError>;
106

            
107
    /// Stop the player completely (as opposed to pausing),
108
    /// removing the pointer to the current video.
109
    async fn stop(&self) -> Result<(), MpvError>;
110

            
111
    // SETTERS
112

            
113
    /// Set the volume of the player.
114
    async fn set_volume(
115
        &self,
116
        input_volume: f64,
117
        option: NumberChangeOptions,
118
    ) -> Result<(), MpvError>;
119

            
120
    /// Set the playback speed of the player.
121
    async fn set_speed(
122
        &self,
123
        input_speed: f64,
124
        option: NumberChangeOptions,
125
    ) -> Result<(), MpvError>;
126

            
127
    /// Toggle/set the pause state of the player.
128
    async fn set_playback(&self, option: Switch) -> Result<(), MpvError>;
129

            
130
    /// Toggle/set the mute state of the player.
131
    async fn set_mute(&self, option: Switch) -> Result<(), MpvError>;
132

            
133
    /// Toggle/set whether the player should loop the current playlist.
134
    async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError>;
135

            
136
    /// Toggle/set whether the player should loop the current video.
137
    async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError>;
138

            
139
    // GETTERS
140

            
141
    /// Get a list of all entries in the playlist.
142
    async fn get_playlist(&self) -> Result<Playlist, MpvError>;
143

            
144
    /// Get metadata about the current video.
145
    async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError>;
146

            
147
    /// Get the path of the current video.
148
    async fn get_file_path(&self) -> Result<String, MpvError>;
149

            
150
    /// Get the current volume of the player.
151
    async fn get_volume(&self) -> Result<f64, MpvError>;
152

            
153
    /// Get the playback speed of the player.
154
    async fn get_speed(&self) -> Result<f64, MpvError>;
155

            
156
    /// Get the current position in the current video.
157
    async fn get_time_pos(&self) -> Result<Option<f64>, MpvError>;
158

            
159
    /// Get the amount of time remaining in the current video.
160
    async fn get_time_remaining(&self) -> Result<Option<f64>, MpvError>;
161

            
162
    /// Get the total duration of the current video.
163
    async fn get_duration(&self) -> Result<f64, MpvError>;
164

            
165
    /// Get the current position in the playlist.
166
    async fn get_playlist_pos(&self) -> Result<usize, MpvError>;
167

            
168
    // BOOLEAN GETTERS
169

            
170
    /// Check whether the player is muted.
171
    async fn is_muted(&self) -> Result<bool, MpvError>;
172

            
173
    /// Check whether the player is currently playing.
174
    async fn is_playing(&self) -> Result<bool, MpvError>;
175

            
176
    /// Check whether the player is looping the current playlist.
177
    async fn playlist_is_looping(&self) -> Result<LoopProperty, MpvError>;
178

            
179
    /// Check whether the player is looping the current video.
180
    async fn file_is_looping(&self) -> Result<LoopProperty, MpvError>;
181
}
182

            
183
impl MpvExt for Mpv {
184
    // COMMANDS
185

            
186
    async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError> {
187
        self.run_command(MpvCommand::Seek { seconds, option }).await
188
    }
189

            
190
    async fn playlist_shuffle(&self) -> Result<(), MpvError> {
191
        self.run_command(MpvCommand::PlaylistShuffle).await
192
    }
193

            
194
    async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError> {
195
        self.run_command(MpvCommand::PlaylistRemove(id)).await
196
    }
197

            
198
    async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError> {
199
        let data = self.get_property("playlist-pos").await?;
200
        let current_id = match parse_property("playlist-pos", data)? {
201
            Property::PlaylistPos(Some(current_id)) => Ok(current_id),
202
            prop => Err(MpvError::UnexpectedProperty(prop)),
203
        }?;
204

            
205
        self.run_command(MpvCommand::PlaylistMove {
206
            from: id,
207
            to: current_id + 1,
208
        })
209
        .await
210
    }
211

            
212
    async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError> {
213
        self.set_property("playlist-pos", id).await
214
    }
215

            
216
    async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError> {
217
        self.run_command(MpvCommand::PlaylistMove { from, to })
218
            .await
219
    }
220

            
221
    async fn playlist_clear(&self) -> Result<(), MpvError> {
222
        self.run_command(MpvCommand::PlaylistClear).await
223
    }
224

            
225
    async fn playlist_add(
226
        &self,
227
        file: &str,
228
        file_type: PlaylistAddTypeOptions,
229
        option: PlaylistAddOptions,
230
    ) -> Result<(), MpvError> {
231
        match file_type {
232
            PlaylistAddTypeOptions::File => {
233
                self.run_command(MpvCommand::LoadFile {
234
                    file: file.to_string(),
235
                    option,
236
                })
237
                .await
238
            }
239

            
240
            PlaylistAddTypeOptions::Playlist => {
241
                self.run_command(MpvCommand::LoadList {
242
                    file: file.to_string(),
243
                    option,
244
                })
245
                .await
246
            }
247
        }
248
    }
249

            
250
    async fn restart(&self) -> Result<(), MpvError> {
251
        self.run_command(MpvCommand::Seek {
252
            seconds: 0f64,
253
            option: SeekOptions::Absolute,
254
        })
255
        .await
256
    }
257

            
258
    async fn prev(&self) -> Result<(), MpvError> {
259
        self.run_command(MpvCommand::PlaylistPrev).await
260
    }
261

            
262
10
    async fn observe_property(&self, id: u64, property: &str) -> Result<(), MpvError> {
263
5
        self.run_command(MpvCommand::Observe {
264
5
            id,
265
5
            property: property.to_string(),
266
5
        })
267
5
        .await
268
5
    }
269

            
270
    async fn unobserve_property(&self, id: u64) -> Result<(), MpvError> {
271
        self.run_command(MpvCommand::Unobserve(id)).await
272
    }
273

            
274
    async fn next(&self) -> Result<(), MpvError> {
275
        self.run_command(MpvCommand::PlaylistNext).await
276
    }
277

            
278
8
    async fn kill(&self) -> Result<(), MpvError> {
279
8
        self.run_command(MpvCommand::Quit).await
280
8
    }
281

            
282
    async fn stop(&self) -> Result<(), MpvError> {
283
        self.run_command(MpvCommand::Stop).await
284
    }
285

            
286
    // SETTERS
287

            
288
    async fn set_volume(
289
        &self,
290
        input_volume: f64,
291
        option: NumberChangeOptions,
292
    ) -> Result<(), MpvError> {
293
        let volume = self.get_volume().await?;
294

            
295
        match option {
296
            NumberChangeOptions::Increase => {
297
                self.set_property("volume", volume + input_volume).await
298
            }
299
            NumberChangeOptions::Decrease => {
300
                self.set_property("volume", volume - input_volume).await
301
            }
302
            NumberChangeOptions::Absolute => self.set_property("volume", input_volume).await,
303
        }
304
    }
305

            
306
    async fn set_speed(
307
        &self,
308
        input_speed: f64,
309
        option: NumberChangeOptions,
310
    ) -> Result<(), MpvError> {
311
        let speed = self.get_speed().await?;
312

            
313
        match option {
314
            NumberChangeOptions::Increase => self.set_property("speed", speed + input_speed).await,
315
            NumberChangeOptions::Decrease => self.set_property("speed", speed - input_speed).await,
316
            NumberChangeOptions::Absolute => self.set_property("speed", input_speed).await,
317
        }
318
    }
319

            
320
    async fn set_playback(&self, option: Switch) -> Result<(), MpvError> {
321
        let enabled = match option {
322
            Switch::On => "no",
323
            Switch::Off => "yes",
324
            Switch::Toggle => {
325
                if self.is_playing().await? {
326
                    "yes"
327
                } else {
328
                    "no"
329
                }
330
            }
331
        };
332
        self.set_property("pause", enabled).await
333
    }
334

            
335
    async fn set_mute(&self, option: Switch) -> Result<(), MpvError> {
336
        let enabled = match option {
337
            Switch::On => "yes",
338
            Switch::Off => "no",
339
            Switch::Toggle => {
340
                if self.is_muted().await? {
341
                    "no"
342
                } else {
343
                    "yes"
344
                }
345
            }
346
        };
347
        self.set_property("mute", enabled).await
348
    }
349

            
350
    async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError> {
351
        let enabled = match option {
352
            Switch::On => "inf",
353
            Switch::Off => "no",
354
            Switch::Toggle => match self.playlist_is_looping().await? {
355
                LoopProperty::Inf => "no",
356
                LoopProperty::N(_) => "no",
357
                LoopProperty::No => "inf",
358
            },
359
        };
360
        self.set_property("loop-playlist", enabled).await
361
    }
362

            
363
    async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError> {
364
        let enabled = match option {
365
            Switch::On => "inf",
366
            Switch::Off => "no",
367
            Switch::Toggle => match self.file_is_looping().await? {
368
                LoopProperty::Inf => "no",
369
                LoopProperty::N(_) => "no",
370
                LoopProperty::No => "inf",
371
            },
372
        };
373
        self.set_property("loop-file", enabled).await
374
    }
375

            
376
    // GETTERS
377

            
378
2
    async fn get_playlist(&self) -> Result<Playlist, MpvError> {
379
2
        let data = self.get_property("playlist").await?;
380
2
        match parse_property("playlist", data)? {
381
2
            Property::Playlist(value) => Ok(Playlist(value)),
382
            prop => Err(MpvError::UnexpectedProperty(prop)),
383
        }
384
2
    }
385

            
386
    async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError> {
387
        let data = self.get_property("metadata").await?;
388
        match parse_property("metadata", data)? {
389
            Property::Metadata(Some(value)) => Ok(value),
390
            prop => Err(MpvError::UnexpectedProperty(prop)),
391
        }
392
    }
393

            
394
    async fn get_file_path(&self) -> Result<String, MpvError> {
395
        let data = self.get_property("path").await?;
396
        match parse_property("path", data)? {
397
            Property::Path(Some(value)) => Ok(value),
398
            prop => Err(MpvError::UnexpectedProperty(prop)),
399
        }
400
    }
401

            
402
    async fn get_volume(&self) -> Result<f64, MpvError> {
403
        let data = self.get_property("volume").await?;
404
        match parse_property("volume", data)? {
405
            Property::Volume(value) => Ok(value),
406
            prop => Err(MpvError::UnexpectedProperty(prop)),
407
        }
408
    }
409

            
410
    async fn get_speed(&self) -> Result<f64, MpvError> {
411
        let data = self.get_property("speed").await?;
412
        match parse_property("speed", data)? {
413
            Property::Speed(value) => Ok(value),
414
            prop => Err(MpvError::UnexpectedProperty(prop)),
415
        }
416
    }
417

            
418
    async fn get_time_pos(&self) -> Result<Option<f64>, MpvError> {
419
        let data = self.get_property("time-pos").await?;
420
        match parse_property("time-pos", data)? {
421
            Property::TimePos(value) => Ok(value),
422
            prop => Err(MpvError::UnexpectedProperty(prop)),
423
        }
424
    }
425

            
426
    async fn get_time_remaining(&self) -> Result<Option<f64>, MpvError> {
427
        let data = self.get_property("time-remaining").await?;
428
        match parse_property("time-remaining", data)? {
429
            Property::TimeRemaining(value) => Ok(value),
430
            prop => Err(MpvError::UnexpectedProperty(prop)),
431
        }
432
    }
433

            
434
    async fn get_duration(&self) -> Result<f64, MpvError> {
435
        let data = self.get_property("duration").await?;
436
        match parse_property("duration", data)? {
437
            Property::Duration(Some(value)) => Ok(value),
438
            prop => Err(MpvError::UnexpectedProperty(prop)),
439
        }
440
    }
441

            
442
    async fn get_playlist_pos(&self) -> Result<usize, MpvError> {
443
        let data = self.get_property("playlist-pos").await?;
444
        match parse_property("playlist-pos", data)? {
445
            Property::PlaylistPos(Some(value)) => Ok(value),
446
            prop => Err(MpvError::UnexpectedProperty(prop)),
447
        }
448
    }
449

            
450
    // BOOLEAN GETTERS
451

            
452
    async fn is_muted(&self) -> Result<bool, MpvError> {
453
        let data = self.get_property("mute").await?;
454
        match parse_property("mute", data)? {
455
            Property::Mute(value) => Ok(value),
456
            prop => Err(MpvError::UnexpectedProperty(prop)),
457
        }
458
    }
459

            
460
    async fn is_playing(&self) -> Result<bool, MpvError> {
461
        let data = self.get_property("pause").await?;
462
        match parse_property("pause", data)? {
463
            Property::Pause(value) => Ok(!value),
464
            prop => Err(MpvError::UnexpectedProperty(prop)),
465
        }
466
    }
467

            
468
    async fn playlist_is_looping(&self) -> Result<LoopProperty, MpvError> {
469
        let data = self.get_property("loop-playlist").await?;
470
        match parse_property("loop-playlist", data)? {
471
            Property::LoopPlaylist(value) => Ok(value),
472
            prop => Err(MpvError::UnexpectedProperty(prop)),
473
        }
474
    }
475

            
476
    async fn file_is_looping(&self) -> Result<LoopProperty, MpvError> {
477
        let data = self.get_property("loop-file").await?;
478
        match parse_property("loop-file", data)? {
479
            Property::LoopFile(value) => Ok(value),
480
            prop => Err(MpvError::UnexpectedProperty(prop)),
481
        }
482
    }
483
}