diff --git a/src/core_api.rs b/src/core_api.rs index ea153de..e816f8c 100644 --- a/src/core_api.rs +++ b/src/core_api.rs @@ -1,3 +1,5 @@ +//! The core API for interacting with [`Mpv`]. + use futures::StreamExt; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -13,6 +15,16 @@ use crate::{ Error, ErrorCode, Event, }; +/// All possible commands that can be sent to mpv. +/// +/// Not all commands are guaranteed to be implemented. +/// If something is missing, please open an issue. +/// +/// You can also use the `run_command_raw` function to run commands +/// that are not implemented here. +/// +/// See for +/// the upstream list of commands. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MpvCommand { LoadFile { @@ -50,10 +62,12 @@ pub enum MpvCommand { Unobserve(isize), } +/// Helper trait to keep track of the string literals that mpv expects. pub(crate) trait IntoRawCommandPart { fn into_raw_command_part(self) -> String; } +/// Generic data type representing all possible data types that mpv can return. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum MpvDataType { Array(Vec), @@ -66,6 +80,20 @@ pub enum MpvDataType { Usize(usize), } +/// A mpv playlist. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Playlist(pub Vec); + +/// A single entry in the mpv playlist. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PlaylistEntry { + pub id: usize, + pub filename: String, + pub title: String, + pub current: bool, +} + +/// Options for [`MpvCommand::LoadFile`] and [`MpvCommand::LoadList`]. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum PlaylistAddOptions { Replace, @@ -81,12 +109,7 @@ impl IntoRawCommandPart for PlaylistAddOptions { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum PlaylistAddTypeOptions { - File, - Playlist, -} - +/// Options for [`MpvCommand::Seek`]. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum SeekOptions { Relative, @@ -106,17 +129,7 @@ impl IntoRawCommandPart for SeekOptions { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PlaylistEntry { - pub id: usize, - pub filename: String, - pub title: String, - pub current: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Playlist(pub Vec); - +/// A trait for specifying how to extract and parse a value returned through [`Mpv::get_property`]. pub trait GetPropertyTypeHandler: Sized { // TODO: fix this #[allow(async_fn_in_trait)] @@ -135,6 +148,7 @@ where } } +/// A trait for specifying how to serialize and set a value through [`Mpv::set_property`]. pub trait SetPropertyTypeHandler { // TODO: fix this #[allow(async_fn_in_trait)] @@ -169,6 +183,14 @@ where } } +/// The main struct for interacting with mpv. +/// +/// This struct provides the core API for interacting with mpv. +/// These functions are the building blocks for the higher-level API provided by the `MpvExt` trait. +/// They can also be used directly to interact with mpv in a more flexible way, mostly returning JSON values. +/// +/// The `Mpv` struct can be cloned freely, and shared anywhere. +/// It only contains a message passing channel to the tokio task that handles the IPC communication with mpv. #[derive(Clone)] pub struct Mpv { command_sender: mpsc::Sender<(MpvIpcCommand, oneshot::Sender)>, @@ -226,7 +248,7 @@ impl Mpv { pub async fn get_event_stream(&self) -> impl futures::Stream> { tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map( |event| match event { - Ok(event) => crate::event_parser::map_event(event), + Ok(event) => crate::event_parser::parse_event(event), Err(_) => Err(Error(ErrorCode::ConnectError( "Failed to receive event".to_string(), ))), @@ -281,7 +303,7 @@ impl Mpv { /// ## Input arguments /// /// - **command** defines the mpv command that should be executed - /// - **args** a slice of &str's which define the arguments + /// - **args** a slice of `&str`'s which define the arguments /// /// # Example /// ``` @@ -405,12 +427,12 @@ impl Mpv { /// Retrieves the property value from mpv. /// /// ## Supported types - /// - String - /// - bool - /// - HashMap (e.g. for the 'metadata' property) - /// - Vec (for the 'playlist' property) - /// - usize - /// - f64 + /// - `String` + /// - `bool` + /// - `HashMap` (e.g. for the 'metadata' property) + /// - `Vec` (for the 'playlist' property) + /// - `usize` + /// - `f64` /// /// ## Input arguments /// @@ -472,18 +494,18 @@ impl Mpv { /// # Description /// - /// Sets the mpv property __ to __. + /// Sets the mpv property _``_ to _``_. /// /// ## Supported types - /// - String - /// - bool - /// - f64 - /// - usize + /// - `String` + /// - `bool` + /// - `f64` + /// - `usize` /// /// ## Input arguments /// /// - **property** defines the mpv property that should be retrieved - /// - **value** defines the value of the given mpv property __ + /// - **value** defines the value of the given mpv property _``_ /// /// # Example /// ``` diff --git a/src/error.rs b/src/error.rs index b6b7c03..4d11189 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,11 @@ +//! Library specific error messages. + use core::fmt; use std::fmt::Display; use serde::{Deserialize, Serialize}; +/// All possible errors that can occur when interacting with mpv. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ErrorCode { MpvError(String), @@ -21,6 +24,7 @@ pub enum ErrorCode { ValueDoesNotContainUsize, } +/// Any error that can occur when interacting with mpv. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Error(pub ErrorCode); @@ -65,4 +69,4 @@ impl Display for ErrorCode { } } } -} \ No newline at end of file +} diff --git a/src/event_parser.rs b/src/event_parser.rs index b02c9cf..e5ff359 100644 --- a/src/event_parser.rs +++ b/src/event_parser.rs @@ -1,9 +1,21 @@ +//! JSON parsing logic for events from [`MpvIpc`](crate::ipc::MpvIpc). + use std::collections::HashMap; use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; use crate::{ipc::MpvIpcEvent, Error, ErrorCode, MpvDataType}; +/// All possible properties that can be observed through the event system. +/// +/// Not all properties are guaranteed to be implemented. +/// If something is missing, please open an issue. +/// +/// Otherwise, the property will be returned as a `Property::Unknown` variant. +/// +/// See for +/// the upstream list of properties. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Property { Path(Option), @@ -14,6 +26,15 @@ pub enum Property { Unknown { name: String, data: MpvDataType }, } +/// All possible events that can be sent by mpv. +/// +/// Not all event types are guaranteed to be implemented. +/// If something is missing, please open an issue. +/// +/// Otherwise, the event will be returned as an `Event::Unimplemented` variant. +/// +/// See for +/// the upstream list of events. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Event { Shutdown, @@ -37,7 +58,8 @@ pub enum Event { Unimplemented, } -pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result { +/// Parse a highlevel [`Event`] objects from json. +pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result { let MpvIpcEvent(event) = raw_event; event @@ -66,59 +88,8 @@ pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result { "metadata-update" => Ok(Event::MetadataUpdate), "seek" => Ok(Event::Seek), "playback-restart" => Ok(Event::PlaybackRestart), - "property-change" => { - let id = event - .get("id") - .ok_or(Error(ErrorCode::MissingValue))? - .as_u64() - .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? - as usize; - let property_name = event - .get("name") - .ok_or(Error(ErrorCode::MissingValue))? - .as_str() - .ok_or(Error(ErrorCode::ValueDoesNotContainString))?; - - match property_name { - "path" => { - let path = event - .get("data") - .ok_or(Error(ErrorCode::MissingValue))? - .as_str() - .map(|s| s.to_string()); - Ok(Event::PropertyChange { - id, - property: Property::Path(path), - }) - } - "pause" => { - let pause = event - .get("data") - .ok_or(Error(ErrorCode::MissingValue))? - .as_bool() - .ok_or(Error(ErrorCode::ValueDoesNotContainBool))?; - Ok(Event::PropertyChange { - id, - property: Property::Pause(pause), - }) - } - // TODO: missing cases - _ => { - let data = event - .get("data") - .ok_or(Error(ErrorCode::MissingValue))? - .clone(); - Ok(Event::PropertyChange { - id, - property: Property::Unknown { - name: property_name.to_string(), - // TODO: fix - data: MpvDataType::Double(data.as_f64().unwrap_or(0.0)), - }, - }) - } - } - } + "property-change" => parse_event_property(event) + .and_then(|(id, property)| Ok(Event::PropertyChange { id, property })), "chapter-change" => Ok(Event::ChapterChange), "client-message" => { let args = event @@ -139,3 +110,51 @@ pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result { } }) } + +/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`]. +fn parse_event_property(event: &Map) -> Result<(usize, Property), Error> { + let id = event + .get("id") + .ok_or(Error(ErrorCode::MissingValue))? + .as_u64() + .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize; + let property_name = event + .get("name") + .ok_or(Error(ErrorCode::MissingValue))? + .as_str() + .ok_or(Error(ErrorCode::ValueDoesNotContainString))?; + + match property_name { + "path" => { + let path = event + .get("data") + .ok_or(Error(ErrorCode::MissingValue))? + .as_str() + .map(|s| s.to_string()); + Ok((id, Property::Path(path))) + } + "pause" => { + let pause = event + .get("data") + .ok_or(Error(ErrorCode::MissingValue))? + .as_bool() + .ok_or(Error(ErrorCode::ValueDoesNotContainBool))?; + Ok((id, Property::Pause(pause))) + } + // TODO: missing cases + _ => { + let data = event + .get("data") + .ok_or(Error(ErrorCode::MissingValue))? + .clone(); + Ok(( + id, + Property::Unknown { + name: property_name.to_string(), + // TODO: fix + data: MpvDataType::Double(data.as_f64().unwrap_or(0.0)), + }, + )) + } + } +} diff --git a/src/highlevel_api_extension.rs b/src/highlevel_api_extension.rs index 1810d56..5cf7404 100644 --- a/src/highlevel_api_extension.rs +++ b/src/highlevel_api_extension.rs @@ -1,10 +1,13 @@ +//! High-level API extension for [`Mpv`]. + use crate::{ Error, IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, Playlist, PlaylistAddOptions, - PlaylistAddTypeOptions, PlaylistEntry, SeekOptions, + PlaylistEntry, SeekOptions, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +/// Generic high-level command for changing a number property. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum NumberChangeOptions { Absolute, @@ -22,6 +25,7 @@ impl IntoRawCommandPart for NumberChangeOptions { } } +/// Generic high-level switch for toggling boolean properties. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum Switch { On, @@ -29,7 +33,14 @@ pub enum Switch { Toggle, } -/// The `MpvExt` trait provides a set of typesafe high-level functions to interact with mpv. +/// Options for [`MpvExt::playlist_add`]. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum PlaylistAddTypeOptions { + File, + Playlist, +} + +/// A set of typesafe high-level functions to interact with [`Mpv`]. // TODO: fix this #[allow(async_fn_in_trait)] pub trait MpvExt { diff --git a/src/ipc.rs b/src/ipc.rs index 03e635e..3159be9 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,19 +1,28 @@ -use super::*; +//! IPC handling thread/task. Handles communication between [`Mpv`](crate::Mpv) instances and mpv's unix socket + use futures::{SinkExt, StreamExt}; use serde_json::{json, Value}; use std::mem; -use tokio::net::UnixStream; -use tokio::sync::mpsc; -use tokio::sync::{broadcast, oneshot, Mutex}; +use tokio::{ + net::UnixStream, + sync::{broadcast, mpsc, oneshot, Mutex}, +}; use tokio_util::codec::{Framed, LinesCodec, LinesCodecError}; +use crate::{Error, ErrorCode}; + +/// Container for all state that regards communication with the mpv IPC socket +/// and message passing with [`Mpv`](crate::Mpv) controllers. pub(crate) struct MpvIpc { socket: Framed, - command_channel: mpsc::Receiver<(MpvIpcCommand, oneshot::Sender)>, + // I had trouble with reading and writing to the socket when it was wrapped + // in a MutexGuard, so I'm using a separate Mutex to lock the socket when needed. socket_lock: Mutex<()>, + command_channel: mpsc::Receiver<(MpvIpcCommand, oneshot::Sender)>, event_channel: broadcast::Sender, } +/// Commands that can be sent to [`MpvIpc`] #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum MpvIpcCommand { Command(Vec), @@ -24,9 +33,11 @@ pub(crate) enum MpvIpcCommand { Exit, } +/// [`MpvIpc`]'s response to a [`MpvIpcCommand`]. #[derive(Debug, Clone)] pub(crate) struct MpvIpcResponse(pub(crate) Result, Error>); +/// A deserialized and partially parsed event from mpv. #[derive(Debug, Clone)] pub(crate) struct MpvIpcEvent(pub(crate) Value); @@ -88,10 +99,6 @@ impl MpvIpc { property: &str, value: Value, ) -> Result, Error> { - // let str_value = match &value { - // Value::String(s) => s, - // v => &serde_json::to_string(&v).unwrap(), - // }; self.send_command(&[json!("set_property"), json!(property), value]) .await } @@ -177,6 +184,9 @@ impl MpvIpc { } } +/// This function does the most basic JSON parsing and error handling +/// for status codes and errors that all responses from mpv are +/// expected to contain. fn parse_mpv_response_data(value: Value) -> Result, Error> { log::trace!("Parsing mpv response data: {:?}", value); let result = value diff --git a/src/lib.rs b/src/lib.rs index 20b214e..714ede0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,13 @@ -mod highlevel_api_extension; +#![doc = include_str!("../README.md")] + mod core_api; mod error; mod event_parser; +mod highlevel_api_extension; mod ipc; mod message_parser; -pub use highlevel_api_extension::*; pub use core_api::*; pub use error::*; pub use event_parser::*; +pub use highlevel_api_extension::*; diff --git a/src/message_parser.rs b/src/message_parser.rs index 8555400..7c038ec 100644 --- a/src/message_parser.rs +++ b/src/message_parser.rs @@ -1,3 +1,5 @@ +//! JSON parsing logic for command responses from [`MpvIpc`](crate::ipc::MpvIpc). + use std::collections::HashMap; use serde_json::Value;