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
    Error, ErrorCode, Event,
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: isize,
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(isize),
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: 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) -> Result<Self, Error>;
138
}
139

            
140
impl<T> GetPropertyTypeHandler for T
141
where
142
    T: TypeHandler,
143
{
144
3422
    async fn get_property_generic(instance: &Mpv, property: &str) -> Result<T, Error> {
145
3422
        instance
146
3422
            .get_property_value(property)
147
3421
            .await
148
3421
            .and_then(T::get_value)
149
3421
    }
150
}
151

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

            
159
impl<T> SetPropertyTypeHandler<T> for T
160
where
161
    T: Serialize,
162
{
163
3731
    async fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error> {
164
3731
        let (res_tx, res_rx) = oneshot::channel();
165
3731
        let value = serde_json::to_value(value)
166
3731
            .map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
167
3731
        instance
168
3731
            .command_sender
169
3731
            .send((
170
3731
                MpvIpcCommand::SetProperty(property.to_owned(), value),
171
3731
                res_tx,
172
3731
            ))
173
            .await
174
3731
            .map_err(|_| {
175
                Error(ErrorCode::ConnectError(
176
                    "Failed to send command".to_string(),
177
                ))
178
3731
            })?;
179

            
180
3731
        match res_rx.await {
181
3730
            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
182
            Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
183
        }
184
3730
    }
185
}
186

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

            
201
impl fmt::Debug for Mpv {
202
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
203
        fmt.debug_struct("Mpv").finish()
204
    }
205
}
206

            
207
impl Mpv {
208
3
    pub async fn connect(socket_path: &str) -> Result<Mpv, Error> {
209
3
        log::debug!("Connecting to mpv socket at {}", socket_path);
210

            
211
3
        let socket = match UnixStream::connect(socket_path).await {
212
3
            Ok(stream) => Ok(stream),
213
            Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
214
        }?;
215

            
216
3
        Self::connect_socket(socket).await
217
3
    }
218

            
219
72
    pub async fn connect_socket(socket: UnixStream) -> Result<Mpv, Error> {
220
18
        let (com_tx, com_rx) = mpsc::channel(100);
221
18
        let (ev_tx, _) = broadcast::channel(100);
222
18
        let ipc = MpvIpc::new(socket, com_rx, ev_tx.clone());
223
18

            
224
18
        log::debug!("Starting IPC handler");
225
18
        tokio::spawn(ipc.run());
226
18

            
227
18
        Ok(Mpv {
228
18
            command_sender: com_tx,
229
18
            broadcast_channel: ev_tx,
230
18
        })
231
18
    }
232

            
233
    pub async fn disconnect(&self) -> Result<(), Error> {
234
        let (res_tx, res_rx) = oneshot::channel();
235
        self.command_sender
236
            .send((MpvIpcCommand::Exit, res_tx))
237
            .await
238
            .map_err(|_| {
239
                Error(ErrorCode::ConnectError(
240
                    "Failed to send command".to_string(),
241
                ))
242
            })?;
243
        match res_rx.await {
244
            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
245
            Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
246
        }
247
    }
248

            
249
4
    pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, Error>> {
250
2
        tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map(
251
2
            |event| match event {
252
2
                Ok(event) => crate::event_parser::parse_event(event),
253
                Err(_) => Err(Error(ErrorCode::ConnectError(
254
                    "Failed to receive event".to_string(),
255
                ))),
256
2
            },
257
2
        )
258
2
    }
259

            
260
    /// Run a custom command.
261
    /// This should only be used if the desired command is not implemented
262
    /// with [MpvCommand].
263
6
    pub async fn run_command_raw(
264
6
        &self,
265
6
        command: &str,
266
6
        args: &[&str],
267
6
    ) -> Result<Option<Value>, Error> {
268
3
        let command = Vec::from(
269
3
            [command]
270
3
                .iter()
271
3
                .chain(args.iter())
272
3
                .map(|s| s.to_string())
273
3
                .collect::<Vec<String>>()
274
3
                .as_slice(),
275
3
        );
276
3
        let (res_tx, res_rx) = oneshot::channel();
277
3
        self.command_sender
278
3
            .send((MpvIpcCommand::Command(command), res_tx))
279
            .await
280
3
            .map_err(|_| {
281
                Error(ErrorCode::ConnectError(
282
                    "Failed to send command".to_string(),
283
                ))
284
3
            })?;
285

            
286
3
        match res_rx.await {
287
3
            Ok(MpvIpcResponse(response)) => response,
288
            Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
289
        }
290
3
    }
291

            
292
6
    async fn run_command_raw_ignore_value(
293
6
        &self,
294
6
        command: &str,
295
6
        args: &[&str],
296
6
    ) -> Result<(), Error> {
297
3
        self.run_command_raw(command, args).await.map(|_| ())
298
3
    }
299

            
300
    /// # Description
301
    ///
302
    /// Runs mpv commands. The arguments are passed as a String-Vector reference:
303
    ///
304
    /// ## Input arguments
305
    ///
306
    /// - **command**   defines the mpv command that should be executed
307
    /// - **args**      a slice of `&str`'s which define the arguments
308
    ///
309
    /// # Example
310
    /// ```
311
    /// use mpvipc::{Mpv, Error};
312
    /// fn main() -> Result<(), Error> {
313
    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
314
    ///
315
    ///     //Run command 'playlist-shuffle' which takes no arguments
316
    ///     mpv.run_command(MpvCommand::PlaylistShuffle)?;
317
    ///
318
    ///     //Run command 'seek' which in this case takes two arguments
319
    ///     mpv.run_command(MpvCommand::Seek {
320
    ///         seconds: 0f64,
321
    ///         option: SeekOptions::Absolute,
322
    ///     })?;
323
    ///     Ok(())
324
    /// }
325
    /// ```
326
10
    pub async fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
327
5
        log::trace!("Running command: {:?}", command);
328
5
        let result = match command {
329
            MpvCommand::LoadFile { file, option } => {
330
                self.run_command_raw_ignore_value(
331
                    "loadfile",
332
                    &[file.as_ref(), option.into_raw_command_part().as_str()],
333
                )
334
                .await
335
            }
336
            MpvCommand::LoadList { file, option } => {
337
                self.run_command_raw_ignore_value(
338
                    "loadlist",
339
                    &[file.as_ref(), option.into_raw_command_part().as_str()],
340
                )
341
                .await
342
            }
343
2
            MpvCommand::Observe { id, property } => {
344
2
                let (res_tx, res_rx) = oneshot::channel();
345
2
                self.command_sender
346
2
                    .send((MpvIpcCommand::ObserveProperty(id, property), res_tx))
347
                    .await
348
2
                    .map_err(|_| {
349
                        Error(ErrorCode::ConnectError(
350
                            "Failed to send command".to_string(),
351
                        ))
352
2
                    })?;
353

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

            
426
    /// # Description
427
    ///
428
    /// Retrieves the property value from mpv.
429
    ///
430
    /// ## Supported types
431
    /// - `String`
432
    /// - `bool`
433
    /// - `HashMap<String, String>` (e.g. for the 'metadata' property)
434
    /// - `Vec<PlaylistEntry>` (for the 'playlist' property)
435
    /// - `usize`
436
    /// - `f64`
437
    ///
438
    /// ## Input arguments
439
    ///
440
    /// - **property** defines the mpv property that should be retrieved
441
    ///
442
    /// # Example
443
    /// ```
444
    /// use mpvipc::{Mpv, Error};
445
    /// async fn main() -> Result<(), Error> {
446
    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
447
    ///     let paused: bool = mpv.get_property("pause").await?;
448
    ///     let title: String = mpv.get_property("media-title").await?;
449
    ///     Ok(())
450
    /// }
451
    /// ```
452
3422
    pub async fn get_property<T: GetPropertyTypeHandler>(
453
3422
        &self,
454
3422
        property: &str,
455
3422
    ) -> Result<T, Error> {
456
3422
        T::get_property_generic(self, property).await
457
3421
    }
458

            
459
    /// # Description
460
    ///
461
    /// Retrieves the property value from mpv.
462
    /// The result is always of type String, regardless of the type of the value of the mpv property
463
    ///
464
    /// ## Input arguments
465
    ///
466
    /// - **property** defines the mpv property that should be retrieved
467
    ///
468
    /// # Example
469
    ///
470
    /// ```
471
    /// use mpvipc::{Mpv, Error};
472
    /// fn main() -> Result<(), Error> {
473
    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
474
    ///     let title = mpv.get_property_string("media-title")?;
475
    ///     Ok(())
476
    /// }
477
    /// ```
478
10266
    pub async fn get_property_value(&self, property: &str) -> Result<Value, Error> {
479
3422
        let (res_tx, res_rx) = oneshot::channel();
480
3422
        self.command_sender
481
3422
            .send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
482
            .await
483
3422
            .map_err(|_| {
484
                Error(ErrorCode::ConnectError(
485
                    "Failed to send command".to_string(),
486
                ))
487
3422
            })?;
488
3422
        match res_rx.await {
489
3421
            Ok(MpvIpcResponse(response)) => {
490
3421
                response.and_then(|value| value.ok_or(Error(ErrorCode::MissingValue)))
491
            }
492
            Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
493
        }
494
3421
    }
495

            
496
    /// # Description
497
    ///
498
    /// Sets the mpv property _`<property>`_ to _`<value>`_.
499
    ///
500
    /// ## Supported types
501
    /// - `String`
502
    /// - `bool`
503
    /// - `f64`
504
    /// - `usize`
505
    ///
506
    /// ## Input arguments
507
    ///
508
    /// - **property** defines the mpv property that should be retrieved
509
    /// - **value** defines the value of the given mpv property _`<property>`_
510
    ///
511
    /// # Example
512
    /// ```
513
    /// use mpvipc::{Mpv, Error};
514
    /// fn async main() -> Result<(), Error> {
515
    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
516
    ///     mpv.set_property("pause", true).await?;
517
    ///     Ok(())
518
    /// }
519
    /// ```
520
3731
    pub async fn set_property<T: SetPropertyTypeHandler<T>>(
521
3731
        &self,
522
3731
        property: &str,
523
3731
        value: T,
524
3731
    ) -> Result<(), Error> {
525
3731
        T::set_property_generic(self, property, value).await
526
3730
    }
527
}