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
    Playlist(Playlist),
79
    String(String),
80
    Usize(usize),
81
}
82

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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