1
//! The core API for interacting with [`Mpv`].
2

            
3
use futures::StreamExt;
4
use serde::{Deserialize, Serialize};
5
use serde_json::Value;
6
use std::{collections::HashMap, fmt};
7
use tokio::{
8
    net::UnixStream,
9
    sync::{broadcast, mpsc, oneshot},
10
};
11

            
12
use crate::{
13
    ipc::{MpvIpc, MpvIpcCommand, MpvIpcEvent, MpvIpcResponse},
14
    message_parser::TypeHandler,
15
    Event, MpvError,
16
};
17

            
18
/// All possible commands that can be sent to mpv.
19
///
20
/// Not all commands are guaranteed to be implemented.
21
/// If something is missing, please open an issue.
22
///
23
/// You can also use the `run_command_raw` function to run commands
24
/// that are not implemented here.
25
///
26
/// See <https://mpv.io/manual/master/#list-of-input-commands> for
27
/// the upstream list of commands.
28
#[derive(Debug, Clone, Serialize, Deserialize)]
29
pub enum MpvCommand {
30
    /// Load the given file or URL and play it.
31
    LoadFile {
32
        file: String,
33
        option: PlaylistAddOptions,
34
    },
35

            
36
    /// Load the given playlist file or URL.
37
    LoadList {
38
        file: String,
39
        option: PlaylistAddOptions,
40
    },
41

            
42
    /// Clear the playlist, except for the currently playing file.
43
    PlaylistClear,
44

            
45
    ///Move the playlist entry at `from`, so that it takes the place of the entry `to`.
46
    /// (Paradoxically, the moved playlist entry will not have the index value `to` after moving
47
    /// if `from` was lower than `to`, because `to` refers to the target entry, not the index
48
    /// the entry will have after moving.)
49
    PlaylistMove { from: usize, to: usize },
50

            
51
    /// Observe a property. This will start triggering [`Event::PropertyChange`] events
52
    /// in the event stream whenever the specific property changes.
53
    /// You can use [`Mpv::get_event_stream`] to get the stream.
54
    Observe { id: u64, property: String },
55

            
56
    /// Skip to the next entry in the playlist.
57
    PlaylistNext,
58

            
59
    /// Skip to the previous entry in the playlist.
60
    PlaylistPrev,
61

            
62
    /// Remove an entry from the playlist by its position in the playlist.
63
    PlaylistRemove(usize),
64

            
65
    /// Shuffle the playlist
66
    PlaylistShuffle,
67

            
68
    /// Exit the player
69
    Quit,
70

            
71
    /// Send a message to all clients, and pass it the following list of arguments.
72
    /// What this message means, how many arguments it takes, and what the arguments
73
    /// mean is fully up to the receiver and the sender.
74
    ScriptMessage(Vec<String>),
75

            
76
    /// Same as [`MpvCommand::ScriptMessage`], but send the message to a specific target.
77
    ScriptMessageTo { target: String, args: Vec<String> },
78

            
79
    /// Change the playback position.
80
    Seek { seconds: f64, option: SeekOptions },
81

            
82
    /// Stop the current playback, and clear the playlist.
83
    /// This esentially resets the entire player state without exiting mpv.
84
    Stop,
85

            
86
    /// Unobserve all properties registered with the given id.
87
    /// See [`MpvCommand::Observe`] for more context.
88
    Unobserve(u64),
89
}
90

            
91
/// Helper trait to keep track of the string literals that mpv expects.
92
pub(crate) trait IntoRawCommandPart {
93
    fn into_raw_command_part(self) -> String;
94
}
95

            
96
/// Generic data type representing all possible data types that mpv can return.
97
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98
#[serde(untagged)]
99
pub enum MpvDataType {
100
    Array(Vec<MpvDataType>),
101
    Bool(bool),
102
    Double(f64),
103
    HashMap(HashMap<String, MpvDataType>),
104
    Null,
105
    MinusOne,
106
    Playlist(Playlist),
107
    String(String),
108
    Usize(usize),
109
}
110

            
111
/// A mpv playlist.
112
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
113
pub struct Playlist(pub Vec<PlaylistEntry>);
114

            
115
/// A single entry in the mpv playlist.
116
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117
pub struct PlaylistEntry {
118
    pub id: usize,
119
    pub filename: String,
120
    pub title: Option<String>,
121
    pub current: bool,
122
}
123

            
124
/// Options for [`MpvCommand::LoadFile`] and [`MpvCommand::LoadList`].
125
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
126
pub enum PlaylistAddOptions {
127
    Replace,
128
    Append,
129
}
130

            
131
impl IntoRawCommandPart for PlaylistAddOptions {
132
    fn into_raw_command_part(self) -> String {
133
        match self {
134
            PlaylistAddOptions::Replace => "replace".to_string(),
135
            PlaylistAddOptions::Append => "append".to_string(),
136
        }
137
    }
138
}
139

            
140
/// Options for [`MpvCommand::Seek`].
141
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
142
pub enum SeekOptions {
143
    Relative,
144
    Absolute,
145
    RelativePercent,
146
    AbsolutePercent,
147
}
148

            
149
impl IntoRawCommandPart for SeekOptions {
150
    fn into_raw_command_part(self) -> String {
151
        match self {
152
            SeekOptions::Relative => "relative".to_string(),
153
            SeekOptions::Absolute => "absolute".to_string(),
154
            SeekOptions::RelativePercent => "relative-percent".to_string(),
155
            SeekOptions::AbsolutePercent => "absolute-percent".to_string(),
156
        }
157
    }
158
}
159

            
160
/// A trait for specifying how to extract and parse a value returned through [`Mpv::get_property`].
161
pub trait GetPropertyTypeHandler: Sized {
162
    // TODO: fix this
163
    #[allow(async_fn_in_trait)]
164
    async fn get_property_generic(instance: &Mpv, property: &str)
165
        -> Result<Option<Self>, MpvError>;
166
}
167

            
168
impl<T> GetPropertyTypeHandler for T
169
where
170
    T: TypeHandler,
171
{
172
1316
    async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Option<T>, MpvError> {
173
1316
        instance
174
1316
            .get_property_value(property)
175
1316
            .await
176
1315
            .and_then(|value| match value {
177
1245
                Some(v) => T::get_value(v).map(|v| Some(v)),
178
2
                None => Ok(None),
179
1315
            })
180
1315
    }
181
}
182

            
183
/// A trait for specifying how to serialize and set a value through [`Mpv::set_property`].
184
pub trait SetPropertyTypeHandler<T> {
185
    // TODO: fix this
186
    #[allow(async_fn_in_trait)]
187
    async fn set_property_generic(instance: &Mpv, property: &str, value: T)
188
        -> Result<(), MpvError>;
189
}
190

            
191
impl<T> SetPropertyTypeHandler<T> for T
192
where
193
    T: Serialize,
194
{
195
1287
    async fn set_property_generic(
196
1287
        instance: &Mpv,
197
1287
        property: &str,
198
1287
        value: T,
199
1287
    ) -> Result<(), MpvError> {
200
1287
        let (res_tx, res_rx) = oneshot::channel();
201
1287
        let value = serde_json::to_value(value).map_err(MpvError::JsonParseError)?;
202

            
203
1287
        instance
204
1287
            .command_sender
205
1287
            .send((
206
1287
                MpvIpcCommand::SetProperty(property.to_owned(), value.to_owned()),
207
1287
                res_tx,
208
1287
            ))
209
1287
            .await
210
1287
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
211

            
212
1287
        match res_rx.await {
213
1286
            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
214
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
215
        }
216
1286
    }
217
}
218

            
219
/// The main struct for interacting with mpv.
220
///
221
/// This struct provides the core API for interacting with mpv.
222
/// These functions are the building blocks for the higher-level API provided by the `MpvExt` trait.
223
/// They can also be used directly to interact with mpv in a more flexible way, mostly returning JSON values.
224
///
225
/// The `Mpv` struct can be cloned freely, and shared anywhere.
226
/// It only contains a message passing channel to the tokio task that handles the IPC communication with mpv.
227
#[derive(Clone)]
228
pub struct Mpv {
229
    command_sender: mpsc::Sender<(MpvIpcCommand, oneshot::Sender<MpvIpcResponse>)>,
230
    broadcast_channel: broadcast::Sender<MpvIpcEvent>,
231
}
232

            
233
// TODO: Can we somehow provide a more useful Debug implementation?
234
impl fmt::Debug for Mpv {
235
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
236
        fmt.debug_struct("Mpv").finish()
237
    }
238
}
239

            
240
impl Mpv {
241
    /// Connect to a unix socket, hosted by mpv, at the given path.
242
    /// This is the inteded way of creating a new [`Mpv`] instance.
243
8
    pub async fn connect(socket_path: &str) -> Result<Mpv, MpvError> {
244
8
        log::debug!("Connecting to mpv socket at {}", socket_path);
245

            
246
8
        let socket = match UnixStream::connect(socket_path).await {
247
8
            Ok(stream) => Ok(stream),
248
            Err(err) => Err(MpvError::MpvSocketConnectionError(err.to_string())),
249
        }?;
250

            
251
8
        Self::connect_socket(socket).await
252
8
    }
253

            
254
    /// Connect to an existing [`UnixStream`].
255
    /// This is an alternative to [`Mpv::connect`], if you already have a [`UnixStream`] available.
256
    ///
257
    /// Internally, this is used for testing purposes.
258
42
    pub async fn connect_socket(socket: UnixStream) -> Result<Mpv, MpvError> {
259
21
        let (com_tx, com_rx) = mpsc::channel(100);
260
21
        let (ev_tx, _) = broadcast::channel(100);
261
21
        let ipc = MpvIpc::new(socket, com_rx, ev_tx.clone());
262
21

            
263
21
        log::debug!("Starting IPC handler");
264
21
        tokio::spawn(ipc.run());
265
21

            
266
21
        Ok(Mpv {
267
21
            command_sender: com_tx,
268
21
            broadcast_channel: ev_tx,
269
21
        })
270
21
    }
271

            
272
    /// Disconnect from the mpv socket.
273
    ///
274
    /// Note that this will also kill communication for all other clones of this instance.
275
    /// It will not kill the mpv process itself - for that you should use [`MpvCommand::Quit`]
276
    /// or run [`MpvExt::kill`](crate::MpvExt::kill).
277
    pub async fn disconnect(&self) -> Result<(), MpvError> {
278
        let (res_tx, res_rx) = oneshot::channel();
279
        self.command_sender
280
            .send((MpvIpcCommand::Exit, res_tx))
281
            .await
282
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
283

            
284
        match res_rx.await {
285
            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
286
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
287
        }
288
    }
289

            
290
    /// Create a new stream, providing [`Event`]s from mpv.
291
    ///
292
    /// This is intended to be used with [`MpvCommand::Observe`] and [`MpvCommand::Unobserve`]
293
    /// (or [`MpvExt::observe_property`] and [`MpvExt::unobserve_property`] respectively).
294
10
    pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, MpvError>> {
295
5
        tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map(
296
14
            |event| match event {
297
14
                Ok(event) => crate::event_parser::parse_event(event),
298
                Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
299
14
            },
300
5
        )
301
5
    }
302

            
303
    /// Run a custom command.
304
    /// This should only be used if the desired command is not implemented
305
    /// with [`MpvCommand`].
306
16
    pub async fn run_command_raw(
307
16
        &self,
308
16
        command: &str,
309
16
        args: &[&str],
310
16
    ) -> Result<Option<Value>, MpvError> {
311
8
        let command_vec = Vec::from(
312
8
            [command]
313
8
                .iter()
314
8
                .chain(args.iter())
315
8
                .map(|s| s.to_string())
316
8
                .collect::<Vec<String>>()
317
8
                .as_slice(),
318
8
        );
319
8
        let (res_tx, res_rx) = oneshot::channel();
320
8
        self.command_sender
321
8
            .send((MpvIpcCommand::Command(command_vec.clone()), res_tx))
322
8
            .await
323
8
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
324

            
325
8
        match res_rx.await {
326
8
            Ok(MpvIpcResponse(response)) => response,
327
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
328
        }
329
8
    }
330

            
331
    /// Helper function to ignore the return value of a command, and only check for errors.
332
16
    async fn run_command_raw_ignore_value(
333
16
        &self,
334
16
        command: &str,
335
16
        args: &[&str],
336
16
    ) -> Result<(), MpvError> {
337
8
        self.run_command_raw(command, args).await.map(|_| ())
338
8
    }
339

            
340
    /// # Description
341
    ///
342
    /// Runs mpv commands. The arguments are passed as a String-Vector reference:
343
    ///
344
    /// ## Input arguments
345
    ///
346
    /// - **command**   defines the mpv command that should be executed
347
    /// - **args**      a slice of `&str`'s which define the arguments
348
    ///
349
    /// # Example
350
    /// ```
351
    /// use mpvipc_async::{Mpv, MpvError};
352
    ///
353
    /// #[tokio::main]
354
    /// async fn main() -> Result<(), MpvError> {
355
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
356
    ///
357
    ///     //Run command 'playlist-shuffle' which takes no arguments
358
    ///     mpv.run_command(MpvCommand::PlaylistShuffle).await?;
359
    ///
360
    ///     //Run command 'seek' which in this case takes two arguments
361
    ///     mpv.run_command(MpvCommand::Seek {
362
    ///         seconds: 0f64,
363
    ///         option: SeekOptions::Absolute,
364
    ///     }).await?;
365
    ///     Ok(())
366
    /// }
367
    /// ```
368
26
    pub async fn run_command(&self, command: MpvCommand) -> Result<(), MpvError> {
369
13
        log::trace!("Running command: {:?}", command);
370
13
        let result = match command {
371
            MpvCommand::LoadFile { file, option } => {
372
                self.run_command_raw_ignore_value(
373
                    "loadfile",
374
                    &[file.as_ref(), option.into_raw_command_part().as_str()],
375
                )
376
                .await
377
            }
378
            MpvCommand::LoadList { file, option } => {
379
                self.run_command_raw_ignore_value(
380
                    "loadlist",
381
                    &[file.as_ref(), option.into_raw_command_part().as_str()],
382
                )
383
                .await
384
            }
385
5
            MpvCommand::Observe { id, property } => {
386
5
                let (res_tx, res_rx) = oneshot::channel();
387
5
                self.command_sender
388
5
                    .send((MpvIpcCommand::ObserveProperty(id, property), res_tx))
389
5
                    .await
390
5
                    .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
391

            
392
5
                match res_rx.await {
393
5
                    Ok(MpvIpcResponse(response)) => response.map(|_| ()),
394
                    Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
395
                }
396
            }
397
            MpvCommand::PlaylistClear => {
398
                self.run_command_raw_ignore_value("playlist-clear", &[])
399
                    .await
400
            }
401
            MpvCommand::PlaylistMove { from, to } => {
402
                self.run_command_raw_ignore_value(
403
                    "playlist-move",
404
                    &[&from.to_string(), &to.to_string()],
405
                )
406
                .await
407
            }
408
            MpvCommand::PlaylistNext => {
409
                self.run_command_raw_ignore_value("playlist-next", &[])
410
                    .await
411
            }
412
            MpvCommand::PlaylistPrev => {
413
                self.run_command_raw_ignore_value("playlist-prev", &[])
414
                    .await
415
            }
416
            MpvCommand::PlaylistRemove(id) => {
417
                self.run_command_raw_ignore_value("playlist-remove", &[&id.to_string()])
418
                    .await
419
            }
420
            MpvCommand::PlaylistShuffle => {
421
                self.run_command_raw_ignore_value("playlist-shuffle", &[])
422
                    .await
423
            }
424
8
            MpvCommand::Quit => self.run_command_raw_ignore_value("quit", &[]).await,
425
            MpvCommand::ScriptMessage(args) => {
426
                let str_args: Vec<_> = args.iter().map(String::as_str).collect();
427
                self.run_command_raw_ignore_value("script-message", &str_args)
428
                    .await
429
            }
430
            MpvCommand::ScriptMessageTo { target, args } => {
431
                let mut cmd_args: Vec<_> = vec![target.as_str()];
432
                let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
433
                cmd_args.append(&mut str_args);
434
                self.run_command_raw_ignore_value("script-message-to", &cmd_args)
435
                    .await
436
            }
437
            MpvCommand::Seek { seconds, option } => {
438
                self.run_command_raw_ignore_value(
439
                    "seek",
440
                    &[
441
                        &seconds.to_string(),
442
                        option.into_raw_command_part().as_str(),
443
                    ],
444
                )
445
                .await
446
            }
447
            MpvCommand::Stop => self.run_command_raw_ignore_value("stop", &[]).await,
448
            MpvCommand::Unobserve(id) => {
449
                let (res_tx, res_rx) = oneshot::channel();
450
                self.command_sender
451
                    .send((MpvIpcCommand::UnobserveProperty(id), res_tx))
452
                    .await
453
                    .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
454

            
455
                match res_rx.await {
456
                    Ok(MpvIpcResponse(response)) => response.map(|_| ()),
457
                    Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
458
                }
459
            }
460
        };
461
13
        log::trace!("Command result: {:?}", result);
462
13
        result
463
13
    }
464

            
465
    /// # Description
466
    ///
467
    /// Retrieves the property value from mpv.
468
    ///
469
    /// ## Supported types
470
    /// - `String`
471
    /// - `bool`
472
    /// - `HashMap<String, String>` (e.g. for the 'metadata' property)
473
    /// - `Vec<PlaylistEntry>` (for the 'playlist' property)
474
    /// - `usize`
475
    /// - `f64`
476
    ///
477
    /// ## Input arguments
478
    ///
479
    /// - **property** defines the mpv property that should be retrieved
480
    ///
481
    /// # Example
482
    /// ```
483
    /// use mpvipc_async::{Mpv, MpvError};
484
    ///
485
    /// #[tokio::main]
486
    /// async fn main() -> Result<(), MpvError> {
487
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
488
    ///     let paused: bool = mpv.get_property("pause").await?;
489
    ///     let title: String = mpv.get_property("media-title").await?;
490
    ///     Ok(())
491
    /// }
492
    /// ```
493
1316
    pub async fn get_property<T: GetPropertyTypeHandler>(
494
1316
        &self,
495
1316
        property: &str,
496
1316
    ) -> Result<Option<T>, MpvError> {
497
1316
        T::get_property_generic(self, property).await
498
1315
    }
499

            
500
    /// # Description
501
    ///
502
    /// Retrieves the property value from mpv.
503
    /// The result is always of type String, regardless of the type of the value of the mpv property
504
    ///
505
    /// ## Input arguments
506
    ///
507
    /// - **property** defines the mpv property that should be retrieved
508
    ///
509
    /// # Example
510
    ///
511
    /// ```
512
    /// use mpvipc_async::{Mpv, MpvError};
513
    ///
514
    /// #[tokio::main]
515
    /// async fn main() -> Result<(), MpvError> {
516
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
517
    ///     let title = mpv.get_property_string("media-title").await?;
518
    ///     Ok(())
519
    /// }
520
    /// ```
521
2632
    pub async fn get_property_value(&self, property: &str) -> Result<Option<Value>, MpvError> {
522
1316
        let (res_tx, res_rx) = oneshot::channel();
523
1316
        self.command_sender
524
1316
            .send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
525
1316
            .await
526
1316
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
527

            
528
1316
        match res_rx.await {
529
1315
            Ok(MpvIpcResponse(response)) => response,
530
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
531
        }
532
1315
    }
533

            
534
    /// # Description
535
    ///
536
    /// Sets the mpv property _`<property>`_ to _`<value>`_.
537
    ///
538
    /// ## Supported types
539
    /// - `String`
540
    /// - `bool`
541
    /// - `f64`
542
    /// - `usize`
543
    ///
544
    /// ## Input arguments
545
    ///
546
    /// - **property** defines the mpv property that should be retrieved
547
    /// - **value** defines the value of the given mpv property _`<property>`_
548
    ///
549
    /// # Example
550
    /// ```
551
    /// use mpvipc_async::{Mpv, MpvError};
552
    /// async fn main() -> Result<(), MpvError> {
553
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
554
    ///     mpv.set_property("pause", true).await?;
555
    ///     Ok(())
556
    /// }
557
    /// ```
558
1287
    pub async fn set_property<T>(&self, property: &str, value: T) -> Result<(), MpvError>
559
1287
    where
560
1287
        T: SetPropertyTypeHandler<T> + Clone + fmt::Debug,
561
1287
    {
562
1287
        T::set_property_generic(self, property, value.clone()).await
563
1286
    }
564
}