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

            
3
use crate::{
4
    IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, MpvError, Playlist, PlaylistAddOptions,
5
    PlaylistEntry, 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
    /// Stop the player completely (as opposed to pausing),
48
    /// removing the pointer to the current video.
49
    async fn stop(&self) -> Result<(), MpvError>;
50

            
51
    /// Set the volume of the player.
52
    async fn set_volume(
53
        &self,
54
        input_volume: f64,
55
        option: NumberChangeOptions,
56
    ) -> Result<(), MpvError>;
57

            
58
    /// Set the playback speed of the player.
59
    async fn set_speed(
60
        &self,
61
        input_speed: f64,
62
        option: NumberChangeOptions,
63
    ) -> Result<(), MpvError>;
64

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

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

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

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

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

            
80
    /// Shuffle the current playlist.
81
    async fn playlist_shuffle(&self) -> Result<(), MpvError>;
82

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

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

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

            
92
    /// Move an entry in the playlist.
93
    ///
94
    /// The `from` parameter is the current position of the entry, and the `to` parameter is the new position.
95
    /// Mpv will then move the entry from the `from` position to the `to` position,
96
    /// shifting after `to` one number up. Paradoxically, that means that moving an entry further down the list
97
    /// will result in a final position that is one less than the `to` parameter.
98
    async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError>;
99

            
100
    /// Remove all entries from the playlist.
101
    async fn playlist_clear(&self) -> Result<(), MpvError>;
102

            
103
    /// Add a file or playlist to the playlist.
104
    async fn playlist_add(
105
        &self,
106
        file: &str,
107
        file_type: PlaylistAddTypeOptions,
108
        option: PlaylistAddOptions,
109
    ) -> Result<(), MpvError>;
110

            
111
    /// Start the current video from the beginning.
112
    async fn restart(&self) -> Result<(), MpvError>;
113

            
114
    /// Play the previous entry in the playlist.
115
    async fn prev(&self) -> Result<(), MpvError>;
116

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

            
121
    /// Stop observing a property.
122
    /// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information.
123
    async fn unobserve_property(&self, id: usize) -> Result<(), MpvError>;
124

            
125
    /// Skip to the next entry in the playlist.
126
    async fn next(&self) -> Result<(), MpvError>;
127

            
128
    /// Stop mpv completely, and kill the process.
129
    ///
130
    /// Note that this is different than forcefully killing the process using
131
    /// as handle to a subprocess, it will only send a command to mpv to ask
132
    /// it to exit itself. If mpv is stuck, it may not respond to this command.
133
    async fn kill(&self) -> Result<(), MpvError>;
134

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

            
138
    /// Get metadata about the current video.
139
    async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError>;
140
}
141

            
142
impl MpvExt for Mpv {
143
    async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError> {
144
        self.get_property("metadata").await
145
    }
146

            
147
4
    async fn get_playlist(&self) -> Result<Playlist, MpvError> {
148
4
        self.get_property::<Vec<PlaylistEntry>>("playlist")
149
4
            .await
150
4
            .map(Playlist)
151
4
    }
152

            
153
6
    async fn kill(&self) -> Result<(), MpvError> {
154
6
        self.run_command(MpvCommand::Quit).await
155
6
    }
156

            
157
    async fn next(&self) -> Result<(), MpvError> {
158
        self.run_command(MpvCommand::PlaylistNext).await
159
    }
160

            
161
10
    async fn observe_property(&self, id: usize, property: &str) -> Result<(), MpvError> {
162
5
        self.run_command(MpvCommand::Observe {
163
5
            id,
164
5
            property: property.to_string(),
165
5
        })
166
5
        .await
167
5
    }
168

            
169
    async fn unobserve_property(&self, id: usize) -> Result<(), MpvError> {
170
        self.run_command(MpvCommand::Unobserve(id)).await
171
    }
172

            
173
    async fn set_playback(&self, option: Switch) -> Result<(), MpvError> {
174
        let enabled = match option {
175
            Switch::On => "yes",
176
            Switch::Off => "no",
177
            Switch::Toggle => {
178
                self.get_property::<String>("pause")
179
                    .await
180
                    .map(|s| match s.as_str() {
181
                        "yes" => "no",
182
                        "no" => "yes",
183
                        _ => "no",
184
                    })?
185
            }
186
        };
187
        self.set_property("pause", enabled).await
188
    }
189

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

            
194
    async fn restart(&self) -> Result<(), MpvError> {
195
        self.run_command(MpvCommand::Seek {
196
            seconds: 0f64,
197
            option: SeekOptions::Absolute,
198
        })
199
        .await
200
    }
201

            
202
    async fn playlist_add(
203
        &self,
204
        file: &str,
205
        file_type: PlaylistAddTypeOptions,
206
        option: PlaylistAddOptions,
207
    ) -> Result<(), MpvError> {
208
        match file_type {
209
            PlaylistAddTypeOptions::File => {
210
                self.run_command(MpvCommand::LoadFile {
211
                    file: file.to_string(),
212
                    option,
213
                })
214
                .await
215
            }
216

            
217
            PlaylistAddTypeOptions::Playlist => {
218
                self.run_command(MpvCommand::LoadList {
219
                    file: file.to_string(),
220
                    option,
221
                })
222
                .await
223
            }
224
        }
225
    }
226

            
227
    async fn playlist_clear(&self) -> Result<(), MpvError> {
228
        self.run_command(MpvCommand::PlaylistClear).await
229
    }
230

            
231
    async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError> {
232
        self.run_command(MpvCommand::PlaylistMove { from, to })
233
            .await
234
    }
235

            
236
    async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError> {
237
        self.set_property("playlist-pos", id).await
238
    }
239

            
240
    async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError> {
241
        match self.get_property::<usize>("playlist-pos").await {
242
            Ok(current_id) => {
243
                self.run_command(MpvCommand::PlaylistMove {
244
                    from: id,
245
                    to: current_id + 1,
246
                })
247
                .await
248
            }
249
            Err(msg) => Err(msg),
250
        }
251
    }
252

            
253
    async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError> {
254
        self.run_command(MpvCommand::PlaylistRemove(id)).await
255
    }
256

            
257
    async fn playlist_shuffle(&self) -> Result<(), MpvError> {
258
        self.run_command(MpvCommand::PlaylistShuffle).await
259
    }
260

            
261
    async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError> {
262
        self.run_command(MpvCommand::Seek { seconds, option }).await
263
    }
264

            
265
    async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError> {
266
        let enabled = match option {
267
            Switch::On => "inf",
268
            Switch::Off => "no",
269
            Switch::Toggle => {
270
                self.get_property::<String>("loop-file")
271
                    .await
272
                    .map(|s| match s.as_str() {
273
                        "inf" => "no",
274
                        "no" => "inf",
275
                        _ => "no",
276
                    })?
277
            }
278
        };
279
        self.set_property("loop-file", enabled).await
280
    }
281

            
282
    async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError> {
283
        let enabled = match option {
284
            Switch::On => "inf",
285
            Switch::Off => "no",
286
            Switch::Toggle => {
287
                self.get_property::<String>("loop-playlist")
288
                    .await
289
                    .map(|s| match s.as_str() {
290
                        "inf" => "no",
291
                        "no" => "inf",
292
                        _ => "no",
293
                    })?
294
            }
295
        };
296
        self.set_property("loo-playlist", enabled).await
297
    }
298

            
299
    async fn set_mute(&self, option: Switch) -> Result<(), MpvError> {
300
        let enabled = match option {
301
            Switch::On => "yes",
302
            Switch::Off => "no",
303
            Switch::Toggle => {
304
                self.get_property::<String>("mute")
305
                    .await
306
                    .map(|s| match s.as_str() {
307
                        "yes" => "no",
308
                        "no" => "yes",
309
                        _ => "no",
310
                    })?
311
            }
312
        };
313
        self.set_property("mute", enabled).await
314
    }
315

            
316
    async fn set_speed(
317
        &self,
318
        input_speed: f64,
319
        option: NumberChangeOptions,
320
    ) -> Result<(), MpvError> {
321
        match self.get_property::<f64>("speed").await {
322
            Ok(speed) => match option {
323
                NumberChangeOptions::Increase => {
324
                    self.set_property("speed", speed + input_speed).await
325
                }
326

            
327
                NumberChangeOptions::Decrease => {
328
                    self.set_property("speed", speed - input_speed).await
329
                }
330

            
331
                NumberChangeOptions::Absolute => self.set_property("speed", input_speed).await,
332
            },
333
            Err(msg) => Err(msg),
334
        }
335
    }
336

            
337
    async fn set_volume(
338
        &self,
339
        input_volume: f64,
340
        option: NumberChangeOptions,
341
    ) -> Result<(), MpvError> {
342
        match self.get_property::<f64>("volume").await {
343
            Ok(volume) => match option {
344
                NumberChangeOptions::Increase => {
345
                    self.set_property("volume", volume + input_volume).await
346
                }
347

            
348
                NumberChangeOptions::Decrease => {
349
                    self.set_property("volume", volume - input_volume).await
350
                }
351

            
352
                NumberChangeOptions::Absolute => self.set_property("volume", input_volume).await,
353
            },
354
            Err(msg) => Err(msg),
355
        }
356
    }
357

            
358
    async fn stop(&self) -> Result<(), MpvError> {
359
        self.run_command(MpvCommand::Stop).await
360
    }
361
}