diff --git a/src/core_api.rs b/src/core_api.rs index 69e8686..ff2612a 100644 --- a/src/core_api.rs +++ b/src/core_api.rs @@ -199,6 +199,7 @@ pub struct Mpv { broadcast_channel: broadcast::Sender, } +// TODO: Can we somehow provide a more useful Debug implementation? impl fmt::Debug for Mpv { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Mpv").finish() @@ -206,6 +207,8 @@ impl fmt::Debug for Mpv { } impl Mpv { + /// Connect to a unix socket, hosted by mpv, at the given path. + /// This is the inteded way of creating a new [`Mpv`] instance. pub async fn connect(socket_path: &str) -> Result { log::debug!("Connecting to mpv socket at {}", socket_path); @@ -217,6 +220,10 @@ impl Mpv { Self::connect_socket(socket).await } + /// Connect to an existing [`UnixStream`]. + /// This is an alternative to [`Mpv::connect`], if you already have a [`UnixStream`] available. + /// + /// Internally, this is used for testing purposes. pub async fn connect_socket(socket: UnixStream) -> Result { let (com_tx, com_rx) = mpsc::channel(100); let (ev_tx, _) = broadcast::channel(100); @@ -231,6 +238,11 @@ impl Mpv { }) } + /// Disconnect from the mpv socket. + /// + /// Note that this will also kill communication for all other clones of this instance. + /// It will not kill the mpv process itself - for that you should use [`MpvCommand::Quit`] + /// or run [`MpvExt::kill`](crate::MpvExt::kill). pub async fn disconnect(&self) -> Result<(), MpvError> { let (res_tx, res_rx) = oneshot::channel(); self.command_sender @@ -244,6 +256,10 @@ impl Mpv { } } + /// Create a new stream, providing [`Event`]s from mpv. + /// + /// This is intended to be used with [`MpvCommand::Observe`] and [`MpvCommand::Unobserve`] + /// (or [`MpvExt::observe_property`] and [`MpvExt::unobserve_property`] respectively). pub async fn get_event_stream(&self) -> impl futures::Stream> { tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map( |event| match event { @@ -255,7 +271,7 @@ impl Mpv { /// Run a custom command. /// This should only be used if the desired command is not implemented - /// with [MpvCommand]. + /// with [`MpvCommand`]. pub async fn run_command_raw( &self, command: &str, @@ -281,6 +297,7 @@ impl Mpv { } } + /// Helper function to ignore the return value of a command, and only check for errors. async fn run_command_raw_ignore_value( &self, command: &str, @@ -300,18 +317,20 @@ impl Mpv { /// /// # Example /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// use mpvipc::{Mpv, MpvError}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), MpvError> { + /// let mpv = Mpv::connect("/tmp/mpvsocket").await?; /// /// //Run command 'playlist-shuffle' which takes no arguments - /// mpv.run_command(MpvCommand::PlaylistShuffle)?; + /// mpv.run_command(MpvCommand::PlaylistShuffle).await?; /// /// //Run command 'seek' which in this case takes two arguments /// mpv.run_command(MpvCommand::Seek { /// seconds: 0f64, /// option: SeekOptions::Absolute, - /// })?; + /// }).await?; /// Ok(()) /// } /// ``` @@ -430,9 +449,11 @@ impl Mpv { /// /// # Example /// ``` - /// use mpvipc::{Mpv, Error}; - /// async fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// use mpvipc::{Mpv, MpvError}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), MpvError> { + /// let mpv = Mpv::connect("/tmp/mpvsocket").await?; /// let paused: bool = mpv.get_property("pause").await?; /// let title: String = mpv.get_property("media-title").await?; /// Ok(()) @@ -457,10 +478,12 @@ impl Mpv { /// # Example /// /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; - /// let title = mpv.get_property_string("media-title")?; + /// use mpvipc::{Mpv, MpvError}; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), MpvError> { + /// let mpv = Mpv::connect("/tmp/mpvsocket").await?; + /// let title = mpv.get_property_string("media-title").await?; /// Ok(()) /// } /// ``` @@ -496,9 +519,9 @@ impl Mpv { /// /// # Example /// ``` - /// use mpvipc::{Mpv, Error}; - /// fn async main() -> Result<(), Error> { - /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// use mpvipc::{Mpv, MpvError}; + /// async fn main() -> Result<(), MpvError> { + /// let mpv = Mpv::connect("/tmp/mpvsocket").await?; /// mpv.set_property("pause", true).await?; /// Ok(()) /// } diff --git a/src/highlevel_api_extension.rs b/src/highlevel_api_extension.rs index 61056b7..8f4b75e 100644 --- a/src/highlevel_api_extension.rs +++ b/src/highlevel_api_extension.rs @@ -44,35 +44,91 @@ pub enum PlaylistAddTypeOptions { // TODO: fix this #[allow(async_fn_in_trait)] pub trait MpvExt { - async fn toggle(&self) -> Result<(), MpvError>; + /// Stop the player completely (as opposed to pausing), + /// removing the pointer to the current video. async fn stop(&self) -> Result<(), MpvError>; + + /// Set the volume of the player. async fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), MpvError>; + + /// Set the playback speed of the player. async fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), MpvError>; + + /// Toggle/set the pause state of the player. + async fn set_playback(&self, option: Switch) -> Result<(), MpvError>; + + /// Toggle/set the mute state of the player. async fn set_mute(&self, option: Switch) -> Result<(), MpvError>; + + /// Toggle/set whether the player should loop the current playlist. async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError>; + + /// Toggle/set whether the player should loop the current video. async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError>; + + /// Seek to a specific position in the current video. async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError>; + + /// Shuffle the current playlist. async fn playlist_shuffle(&self) -> Result<(), MpvError>; + + /// Remove an entry from the playlist. async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError>; + + /// Play the next entry in the playlist. async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError>; + + /// Play a specific entry in the playlist. async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError>; + + /// Move an entry in the playlist. + /// + /// The `from` parameter is the current position of the entry, and the `to` parameter is the new position. + /// Mpv will then move the entry from the `from` position to the `to` position, + /// shifting after `to` one number up. Paradoxically, that means that moving an entry further down the list + /// will result in a final position that is one less than the `to` parameter. async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError>; + + /// Remove all entries from the playlist. async fn playlist_clear(&self) -> Result<(), MpvError>; + + /// Add a file or playlist to the playlist. async fn playlist_add( &self, file: &str, file_type: PlaylistAddTypeOptions, option: PlaylistAddOptions, ) -> Result<(), MpvError>; + + /// Start the current video from the beginning. async fn restart(&self) -> Result<(), MpvError>; + + /// Play the previous entry in the playlist. async fn prev(&self) -> Result<(), MpvError>; - async fn pause(&self) -> Result<(), MpvError>; - async fn unobserve_property(&self, id: usize) -> Result<(), MpvError>; + + /// Notify mpv to send events whenever a property changes. + /// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information. async fn observe_property(&self, id: usize, property: &str) -> Result<(), MpvError>; + + /// Stop observing a property. + /// See [`Mpv::get_event_stream`] and [`Property`](crate::Property) for more information. + async fn unobserve_property(&self, id: usize) -> Result<(), MpvError>; + + /// Skip to the next entry in the playlist. async fn next(&self) -> Result<(), MpvError>; + + /// Stop mpv completely, and kill the process. + /// + /// Note that this is different than forcefully killing the process using + /// as handle to a subprocess, it will only send a command to mpv to ask + /// it to exit itself. If mpv is stuck, it may not respond to this command. async fn kill(&self) -> Result<(), MpvError>; + + /// Get a list of all entries in the playlist. async fn get_playlist(&self) -> Result; + + /// Get metadata about the current video. async fn get_metadata(&self) -> Result, MpvError>; } @@ -107,8 +163,21 @@ impl MpvExt for Mpv { self.run_command(MpvCommand::Unobserve(id)).await } - async fn pause(&self) -> Result<(), MpvError> { - self.set_property("pause", true).await + async fn set_playback(&self, option: Switch) -> Result<(), MpvError> { + let enabled = match option { + Switch::On => "yes", + Switch::Off => "no", + Switch::Toggle => { + self.get_property::("pause") + .await + .map(|s| match s.as_str() { + "yes" => "no", + "no" => "yes", + _ => "no", + })? + } + }; + self.set_property("pause", enabled).await } async fn prev(&self) -> Result<(), MpvError> { @@ -278,8 +347,4 @@ impl MpvExt for Mpv { async fn stop(&self) -> Result<(), MpvError> { self.run_command(MpvCommand::Stop).await } - - async fn toggle(&self) -> Result<(), MpvError> { - self.run_command_raw("cycle", &["pause"]).await.map(|_| ()) - } }