mpvipc_async/
highlevel_api_extension.rs

1//! High-level API extension for [`Mpv`].
2
3use crate::{
4    parse_property, IntoRawCommandPart, LoopProperty, Mpv, MpvCommand, MpvDataType, MpvError,
5    Playlist, PlaylistAddOptions, Property, SeekOptions,
6};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// Generic high-level command for changing a number property.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub enum NumberChangeOptions {
13    Absolute,
14    Increase,
15    Decrease,
16}
17
18impl 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)]
30pub enum Switch {
31    On,
32    Off,
33    Toggle,
34}
35
36/// Options for [`MpvExt::playlist_add`].
37#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
38pub 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)]
46pub 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
183impl 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    async fn observe_property(&self, id: u64, property: &str) -> Result<(), MpvError> {
263        self.run_command(MpvCommand::Observe {
264            id,
265            property: property.to_string(),
266        })
267        .await
268    }
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    async fn kill(&self) -> Result<(), MpvError> {
279        self.run_command(MpvCommand::Quit).await
280    }
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    async fn get_playlist(&self) -> Result<Playlist, MpvError> {
379        let data = self.get_property("playlist").await?;
380        match parse_property("playlist", data)? {
381            Property::Playlist(value) => Ok(Playlist(value)),
382            prop => Err(MpvError::UnexpectedProperty(prop)),
383        }
384    }
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}