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
    LoadFile {
31
        file: String,
32
        option: PlaylistAddOptions,
33
    },
34
    LoadList {
35
        file: String,
36
        option: PlaylistAddOptions,
37
    },
38
    PlaylistClear,
39
    PlaylistMove {
40
        from: usize,
41
        to: usize,
42
    },
43
    Observe {
44
        id: usize,
45
        property: String,
46
    },
47
    PlaylistNext,
48
    PlaylistPrev,
49
    PlaylistRemove(usize),
50
    PlaylistShuffle,
51
    Quit,
52
    ScriptMessage(Vec<String>),
53
    ScriptMessageTo {
54
        target: String,
55
        args: Vec<String>,
56
    },
57
    Seek {
58
        seconds: f64,
59
        option: SeekOptions,
60
    },
61
    Stop,
62
    Unobserve(usize),
63
}
64

            
65
/// Helper trait to keep track of the string literals that mpv expects.
66
pub(crate) trait IntoRawCommandPart {
67
    fn into_raw_command_part(self) -> String;
68
}
69

            
70
/// Generic data type representing all possible data types that mpv can return.
71
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72
pub enum MpvDataType {
73
    Array(Vec<MpvDataType>),
74
    Bool(bool),
75
    Double(f64),
76
    HashMap(HashMap<String, MpvDataType>),
77
    Null,
78
    MinusOne,
79
    Playlist(Playlist),
80
    String(String),
81
    Usize(usize),
82
}
83

            
84
/// A mpv playlist.
85
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
86
pub struct Playlist(pub Vec<PlaylistEntry>);
87

            
88
/// A single entry in the mpv playlist.
89
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90
pub struct PlaylistEntry {
91
    pub id: usize,
92
    pub filename: String,
93
    pub title: Option<String>,
94
    pub current: bool,
95
}
96

            
97
/// Options for [`MpvCommand::LoadFile`] and [`MpvCommand::LoadList`].
98
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
99
pub enum PlaylistAddOptions {
100
    Replace,
101
    Append,
102
}
103

            
104
impl IntoRawCommandPart for PlaylistAddOptions {
105
    fn into_raw_command_part(self) -> String {
106
        match self {
107
            PlaylistAddOptions::Replace => "replace".to_string(),
108
            PlaylistAddOptions::Append => "append".to_string(),
109
        }
110
    }
111
}
112

            
113
/// Options for [`MpvCommand::Seek`].
114
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
115
pub enum SeekOptions {
116
    Relative,
117
    Absolute,
118
    RelativePercent,
119
    AbsolutePercent,
120
}
121

            
122
impl IntoRawCommandPart for SeekOptions {
123
    fn into_raw_command_part(self) -> String {
124
        match self {
125
            SeekOptions::Relative => "relative".to_string(),
126
            SeekOptions::Absolute => "absolute".to_string(),
127
            SeekOptions::RelativePercent => "relative-percent".to_string(),
128
            SeekOptions::AbsolutePercent => "absolute-percent".to_string(),
129
        }
130
    }
131
}
132

            
133
/// A trait for specifying how to extract and parse a value returned through [`Mpv::get_property`].
134
pub trait GetPropertyTypeHandler: Sized {
135
    // TODO: fix this
136
    #[allow(async_fn_in_trait)]
137
    async fn get_property_generic(instance: &Mpv, property: &str)
138
        -> Result<Option<Self>, MpvError>;
139
}
140

            
141
impl<T> GetPropertyTypeHandler for T
142
where
143
    T: TypeHandler,
144
{
145
971
    async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Option<T>, MpvError> {
146
971
        instance
147
971
            .get_property_value(property)
148
971
            .await
149
970
            .and_then(|value| match value {
150
900
                Some(v) => T::get_value(v).map(|v| Some(v)),
151
2
                None => Ok(None),
152
970
            })
153
970
    }
154
}
155

            
156
/// A trait for specifying how to serialize and set a value through [`Mpv::set_property`].
157
pub trait SetPropertyTypeHandler<T> {
158
    // TODO: fix this
159
    #[allow(async_fn_in_trait)]
160
    async fn set_property_generic(instance: &Mpv, property: &str, value: T)
161
        -> Result<(), MpvError>;
162
}
163

            
164
impl<T> SetPropertyTypeHandler<T> for T
165
where
166
    T: Serialize,
167
{
168
1179
    async fn set_property_generic(
169
1179
        instance: &Mpv,
170
1179
        property: &str,
171
1179
        value: T,
172
1179
    ) -> Result<(), MpvError> {
173
1179
        let (res_tx, res_rx) = oneshot::channel();
174
1179
        let value = serde_json::to_value(value).map_err(MpvError::JsonParseError)?;
175

            
176
1179
        instance
177
1179
            .command_sender
178
1179
            .send((
179
1179
                MpvIpcCommand::SetProperty(property.to_owned(), value.to_owned()),
180
1179
                res_tx,
181
1179
            ))
182
1179
            .await
183
1179
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
184

            
185
1179
        match res_rx.await {
186
1178
            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
187
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
188
        }
189
1178
    }
190
}
191

            
192
/// The main struct for interacting with mpv.
193
///
194
/// This struct provides the core API for interacting with mpv.
195
/// These functions are the building blocks for the higher-level API provided by the `MpvExt` trait.
196
/// They can also be used directly to interact with mpv in a more flexible way, mostly returning JSON values.
197
///
198
/// The `Mpv` struct can be cloned freely, and shared anywhere.
199
/// It only contains a message passing channel to the tokio task that handles the IPC communication with mpv.
200
#[derive(Clone)]
201
pub struct Mpv {
202
    command_sender: mpsc::Sender<(MpvIpcCommand, oneshot::Sender<MpvIpcResponse>)>,
203
    broadcast_channel: broadcast::Sender<MpvIpcEvent>,
204
}
205

            
206
// TODO: Can we somehow provide a more useful Debug implementation?
207
impl fmt::Debug for Mpv {
208
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
209
        fmt.debug_struct("Mpv").finish()
210
    }
211
}
212

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

            
219
8
        let socket = match UnixStream::connect(socket_path).await {
220
8
            Ok(stream) => Ok(stream),
221
            Err(err) => Err(MpvError::MpvSocketConnectionError(err.to_string())),
222
        }?;
223

            
224
8
        Self::connect_socket(socket).await
225
8
    }
226

            
227
    /// Connect to an existing [`UnixStream`].
228
    /// This is an alternative to [`Mpv::connect`], if you already have a [`UnixStream`] available.
229
    ///
230
    /// Internally, this is used for testing purposes.
231
42
    pub async fn connect_socket(socket: UnixStream) -> Result<Mpv, MpvError> {
232
21
        let (com_tx, com_rx) = mpsc::channel(100);
233
21
        let (ev_tx, _) = broadcast::channel(100);
234
21
        let ipc = MpvIpc::new(socket, com_rx, ev_tx.clone());
235
21

            
236
21
        log::debug!("Starting IPC handler");
237
21
        tokio::spawn(ipc.run());
238
21

            
239
21
        Ok(Mpv {
240
21
            command_sender: com_tx,
241
21
            broadcast_channel: ev_tx,
242
21
        })
243
21
    }
244

            
245
    /// Disconnect from the mpv socket.
246
    ///
247
    /// Note that this will also kill communication for all other clones of this instance.
248
    /// It will not kill the mpv process itself - for that you should use [`MpvCommand::Quit`]
249
    /// or run [`MpvExt::kill`](crate::MpvExt::kill).
250
    pub async fn disconnect(&self) -> Result<(), MpvError> {
251
        let (res_tx, res_rx) = oneshot::channel();
252
        self.command_sender
253
            .send((MpvIpcCommand::Exit, res_tx))
254
            .await
255
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
256

            
257
        match res_rx.await {
258
            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
259
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
260
        }
261
    }
262

            
263
    /// Create a new stream, providing [`Event`]s from mpv.
264
    ///
265
    /// This is intended to be used with [`MpvCommand::Observe`] and [`MpvCommand::Unobserve`]
266
    /// (or [`MpvExt::observe_property`] and [`MpvExt::unobserve_property`] respectively).
267
10
    pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, MpvError>> {
268
5
        tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map(
269
14
            |event| match event {
270
14
                Ok(event) => crate::event_parser::parse_event(event),
271
                Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
272
14
            },
273
5
        )
274
5
    }
275

            
276
    /// Run a custom command.
277
    /// This should only be used if the desired command is not implemented
278
    /// with [`MpvCommand`].
279
16
    pub async fn run_command_raw(
280
16
        &self,
281
16
        command: &str,
282
16
        args: &[&str],
283
16
    ) -> Result<Option<Value>, MpvError> {
284
8
        let command_vec = Vec::from(
285
8
            [command]
286
8
                .iter()
287
8
                .chain(args.iter())
288
8
                .map(|s| s.to_string())
289
8
                .collect::<Vec<String>>()
290
8
                .as_slice(),
291
8
        );
292
8
        let (res_tx, res_rx) = oneshot::channel();
293
8
        self.command_sender
294
8
            .send((MpvIpcCommand::Command(command_vec.clone()), res_tx))
295
8
            .await
296
8
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
297

            
298
8
        match res_rx.await {
299
8
            Ok(MpvIpcResponse(response)) => response,
300
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
301
        }
302
8
    }
303

            
304
    /// Helper function to ignore the return value of a command, and only check for errors.
305
16
    async fn run_command_raw_ignore_value(
306
16
        &self,
307
16
        command: &str,
308
16
        args: &[&str],
309
16
    ) -> Result<(), MpvError> {
310
8
        self.run_command_raw(command, args).await.map(|_| ())
311
8
    }
312

            
313
    /// # Description
314
    ///
315
    /// Runs mpv commands. The arguments are passed as a String-Vector reference:
316
    ///
317
    /// ## Input arguments
318
    ///
319
    /// - **command**   defines the mpv command that should be executed
320
    /// - **args**      a slice of `&str`'s which define the arguments
321
    ///
322
    /// # Example
323
    /// ```
324
    /// use mpvipc_async::{Mpv, MpvError};
325
    ///
326
    /// #[tokio::main]
327
    /// async fn main() -> Result<(), MpvError> {
328
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
329
    ///
330
    ///     //Run command 'playlist-shuffle' which takes no arguments
331
    ///     mpv.run_command(MpvCommand::PlaylistShuffle).await?;
332
    ///
333
    ///     //Run command 'seek' which in this case takes two arguments
334
    ///     mpv.run_command(MpvCommand::Seek {
335
    ///         seconds: 0f64,
336
    ///         option: SeekOptions::Absolute,
337
    ///     }).await?;
338
    ///     Ok(())
339
    /// }
340
    /// ```
341
26
    pub async fn run_command(&self, command: MpvCommand) -> Result<(), MpvError> {
342
13
        log::trace!("Running command: {:?}", command);
343
13
        let result = match command {
344
            MpvCommand::LoadFile { file, option } => {
345
                self.run_command_raw_ignore_value(
346
                    "loadfile",
347
                    &[file.as_ref(), option.into_raw_command_part().as_str()],
348
                )
349
                .await
350
            }
351
            MpvCommand::LoadList { file, option } => {
352
                self.run_command_raw_ignore_value(
353
                    "loadlist",
354
                    &[file.as_ref(), option.into_raw_command_part().as_str()],
355
                )
356
                .await
357
            }
358
5
            MpvCommand::Observe { id, property } => {
359
5
                let (res_tx, res_rx) = oneshot::channel();
360
5
                self.command_sender
361
5
                    .send((MpvIpcCommand::ObserveProperty(id, property), res_tx))
362
5
                    .await
363
5
                    .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
364

            
365
5
                match res_rx.await {
366
5
                    Ok(MpvIpcResponse(response)) => response.map(|_| ()),
367
                    Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
368
                }
369
            }
370
            MpvCommand::PlaylistClear => {
371
                self.run_command_raw_ignore_value("playlist-clear", &[])
372
                    .await
373
            }
374
            MpvCommand::PlaylistMove { from, to } => {
375
                self.run_command_raw_ignore_value(
376
                    "playlist-move",
377
                    &[&from.to_string(), &to.to_string()],
378
                )
379
                .await
380
            }
381
            MpvCommand::PlaylistNext => {
382
                self.run_command_raw_ignore_value("playlist-next", &[])
383
                    .await
384
            }
385
            MpvCommand::PlaylistPrev => {
386
                self.run_command_raw_ignore_value("playlist-prev", &[])
387
                    .await
388
            }
389
            MpvCommand::PlaylistRemove(id) => {
390
                self.run_command_raw_ignore_value("playlist-remove", &[&id.to_string()])
391
                    .await
392
            }
393
            MpvCommand::PlaylistShuffle => {
394
                self.run_command_raw_ignore_value("playlist-shuffle", &[])
395
                    .await
396
            }
397
8
            MpvCommand::Quit => self.run_command_raw_ignore_value("quit", &[]).await,
398
            MpvCommand::ScriptMessage(args) => {
399
                let str_args: Vec<_> = args.iter().map(String::as_str).collect();
400
                self.run_command_raw_ignore_value("script-message", &str_args)
401
                    .await
402
            }
403
            MpvCommand::ScriptMessageTo { target, args } => {
404
                let mut cmd_args: Vec<_> = vec![target.as_str()];
405
                let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
406
                cmd_args.append(&mut str_args);
407
                self.run_command_raw_ignore_value("script-message-to", &cmd_args)
408
                    .await
409
            }
410
            MpvCommand::Seek { seconds, option } => {
411
                self.run_command_raw_ignore_value(
412
                    "seek",
413
                    &[
414
                        &seconds.to_string(),
415
                        option.into_raw_command_part().as_str(),
416
                    ],
417
                )
418
                .await
419
            }
420
            MpvCommand::Stop => self.run_command_raw_ignore_value("stop", &[]).await,
421
            MpvCommand::Unobserve(id) => {
422
                let (res_tx, res_rx) = oneshot::channel();
423
                self.command_sender
424
                    .send((MpvIpcCommand::UnobserveProperty(id), res_tx))
425
                    .await
426
                    .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
427

            
428
                match res_rx.await {
429
                    Ok(MpvIpcResponse(response)) => response.map(|_| ()),
430
                    Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
431
                }
432
            }
433
        };
434
13
        log::trace!("Command result: {:?}", result);
435
13
        result
436
13
    }
437

            
438
    /// # Description
439
    ///
440
    /// Retrieves the property value from mpv.
441
    ///
442
    /// ## Supported types
443
    /// - `String`
444
    /// - `bool`
445
    /// - `HashMap<String, String>` (e.g. for the 'metadata' property)
446
    /// - `Vec<PlaylistEntry>` (for the 'playlist' property)
447
    /// - `usize`
448
    /// - `f64`
449
    ///
450
    /// ## Input arguments
451
    ///
452
    /// - **property** defines the mpv property that should be retrieved
453
    ///
454
    /// # Example
455
    /// ```
456
    /// use mpvipc_async::{Mpv, MpvError};
457
    ///
458
    /// #[tokio::main]
459
    /// async fn main() -> Result<(), MpvError> {
460
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
461
    ///     let paused: bool = mpv.get_property("pause").await?;
462
    ///     let title: String = mpv.get_property("media-title").await?;
463
    ///     Ok(())
464
    /// }
465
    /// ```
466
971
    pub async fn get_property<T: GetPropertyTypeHandler>(
467
971
        &self,
468
971
        property: &str,
469
971
    ) -> Result<Option<T>, MpvError> {
470
971
        T::get_property_generic(self, property).await
471
970
    }
472

            
473
    /// # Description
474
    ///
475
    /// Retrieves the property value from mpv.
476
    /// The result is always of type String, regardless of the type of the value of the mpv property
477
    ///
478
    /// ## Input arguments
479
    ///
480
    /// - **property** defines the mpv property that should be retrieved
481
    ///
482
    /// # Example
483
    ///
484
    /// ```
485
    /// use mpvipc_async::{Mpv, MpvError};
486
    ///
487
    /// #[tokio::main]
488
    /// async fn main() -> Result<(), MpvError> {
489
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
490
    ///     let title = mpv.get_property_string("media-title").await?;
491
    ///     Ok(())
492
    /// }
493
    /// ```
494
1942
    pub async fn get_property_value(&self, property: &str) -> Result<Option<Value>, MpvError> {
495
971
        let (res_tx, res_rx) = oneshot::channel();
496
971
        self.command_sender
497
971
            .send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
498
971
            .await
499
971
            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
500

            
501
971
        match res_rx.await {
502
970
            Ok(MpvIpcResponse(response)) => response,
503
            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
504
        }
505
970
    }
506

            
507
    /// # Description
508
    ///
509
    /// Sets the mpv property _`<property>`_ to _`<value>`_.
510
    ///
511
    /// ## Supported types
512
    /// - `String`
513
    /// - `bool`
514
    /// - `f64`
515
    /// - `usize`
516
    ///
517
    /// ## Input arguments
518
    ///
519
    /// - **property** defines the mpv property that should be retrieved
520
    /// - **value** defines the value of the given mpv property _`<property>`_
521
    ///
522
    /// # Example
523
    /// ```
524
    /// use mpvipc_async::{Mpv, MpvError};
525
    /// async fn main() -> Result<(), MpvError> {
526
    ///     let mpv = Mpv::connect("/tmp/mpvsocket").await?;
527
    ///     mpv.set_property("pause", true).await?;
528
    ///     Ok(())
529
    /// }
530
    /// ```
531
1179
    pub async fn set_property<T>(&self, property: &str, value: T) -> Result<(), MpvError>
532
1179
    where
533
1179
        T: SetPropertyTypeHandler<T> + Clone + fmt::Debug,
534
1179
    {
535
1179
        T::set_property_generic(self, property, value.clone()).await
536
1178
    }
537
}