mpvipc_async/
core_api.rs

1//! The core API for interacting with [`Mpv`].
2
3use futures::StreamExt;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::{collections::HashMap, fmt};
7use tokio::{
8    net::UnixStream,
9    sync::{broadcast, mpsc, oneshot},
10};
11
12use 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)]
29pub 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.
92pub(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)]
99pub 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)]
113pub struct Playlist(pub Vec<PlaylistEntry>);
114
115/// A single entry in the mpv playlist.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub 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)]
126pub enum PlaylistAddOptions {
127    Replace,
128    Append,
129}
130
131impl 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)]
142pub enum SeekOptions {
143    Relative,
144    Absolute,
145    RelativePercent,
146    AbsolutePercent,
147}
148
149impl 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`].
161pub 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
168impl<T> GetPropertyTypeHandler for T
169where
170    T: TypeHandler,
171{
172    async fn get_property_generic(instance: &Mpv, property: &str) -> Result<Option<T>, MpvError> {
173        instance
174            .get_property_value(property)
175            .await
176            .and_then(|value| match value {
177                Some(v) => T::get_value(v).map(|v| Some(v)),
178                None => Ok(None),
179            })
180    }
181}
182
183/// A trait for specifying how to serialize and set a value through [`Mpv::set_property`].
184pub 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
191impl<T> SetPropertyTypeHandler<T> for T
192where
193    T: Serialize,
194{
195    async fn set_property_generic(
196        instance: &Mpv,
197        property: &str,
198        value: T,
199    ) -> Result<(), MpvError> {
200        let (res_tx, res_rx) = oneshot::channel();
201        let value = serde_json::to_value(value).map_err(MpvError::JsonParseError)?;
202
203        instance
204            .command_sender
205            .send((
206                MpvIpcCommand::SetProperty(property.to_owned(), value.to_owned()),
207                res_tx,
208            ))
209            .await
210            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
211
212        match res_rx.await {
213            Ok(MpvIpcResponse(response)) => response.map(|_| ()),
214            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
215        }
216    }
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)]
228pub 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?
234impl fmt::Debug for Mpv {
235    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
236        fmt.debug_struct("Mpv").finish()
237    }
238}
239
240impl 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    pub async fn connect(socket_path: &str) -> Result<Mpv, MpvError> {
244        log::debug!("Connecting to mpv socket at {}", socket_path);
245
246        let socket = match UnixStream::connect(socket_path).await {
247            Ok(stream) => Ok(stream),
248            Err(err) => Err(MpvError::MpvSocketConnectionError(err.to_string())),
249        }?;
250
251        Self::connect_socket(socket).await
252    }
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    pub async fn connect_socket(socket: UnixStream) -> Result<Mpv, MpvError> {
259        let (com_tx, com_rx) = mpsc::channel(100);
260        let (ev_tx, _) = broadcast::channel(100);
261        let ipc = MpvIpc::new(socket, com_rx, ev_tx.clone());
262
263        log::debug!("Starting IPC handler");
264        tokio::spawn(ipc.run());
265
266        Ok(Mpv {
267            command_sender: com_tx,
268            broadcast_channel: ev_tx,
269        })
270    }
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    pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, MpvError>> {
295        tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map(
296            |event| match event {
297                Ok(event) => crate::event_parser::parse_event(event),
298                Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
299            },
300        )
301    }
302
303    /// Run a custom command.
304    /// This should only be used if the desired command is not implemented
305    /// with [`MpvCommand`].
306    pub async fn run_command_raw(
307        &self,
308        command: &str,
309        args: &[&str],
310    ) -> Result<Option<Value>, MpvError> {
311        let command_vec = Vec::from(
312            [command]
313                .iter()
314                .chain(args.iter())
315                .map(|s| s.to_string())
316                .collect::<Vec<String>>()
317                .as_slice(),
318        );
319        let (res_tx, res_rx) = oneshot::channel();
320        self.command_sender
321            .send((MpvIpcCommand::Command(command_vec.clone()), res_tx))
322            .await
323            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
324
325        match res_rx.await {
326            Ok(MpvIpcResponse(response)) => response,
327            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
328        }
329    }
330
331    /// Helper function to ignore the return value of a command, and only check for errors.
332    async fn run_command_raw_ignore_value(
333        &self,
334        command: &str,
335        args: &[&str],
336    ) -> Result<(), MpvError> {
337        self.run_command_raw(command, args).await.map(|_| ())
338    }
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    pub async fn run_command(&self, command: MpvCommand) -> Result<(), MpvError> {
369        log::trace!("Running command: {:?}", command);
370        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            MpvCommand::Observe { id, property } => {
386                let (res_tx, res_rx) = oneshot::channel();
387                self.command_sender
388                    .send((MpvIpcCommand::ObserveProperty(id, property), res_tx))
389                    .await
390                    .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
391
392                match res_rx.await {
393                    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            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        log::trace!("Command result: {:?}", result);
462        result
463    }
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    pub async fn get_property<T: GetPropertyTypeHandler>(
494        &self,
495        property: &str,
496    ) -> Result<Option<T>, MpvError> {
497        T::get_property_generic(self, property).await
498    }
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    pub async fn get_property_value(&self, property: &str) -> Result<Option<Value>, MpvError> {
522        let (res_tx, res_rx) = oneshot::channel();
523        self.command_sender
524            .send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
525            .await
526            .map_err(|err| MpvError::InternalConnectionError(err.to_string()))?;
527
528        match res_rx.await {
529            Ok(MpvIpcResponse(response)) => response,
530            Err(err) => Err(MpvError::InternalConnectionError(err.to_string())),
531        }
532    }
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    pub async fn set_property<T>(&self, property: &str, value: T) -> Result<(), MpvError>
559    where
560        T: SetPropertyTypeHandler<T> + Clone + fmt::Debug,
561    {
562        T::set_property_generic(self, property, value.clone()).await
563    }
564}