clean: add docstrings, move a few things around

This commit is contained in:
Oystein Kristoffer Tveit 2024-04-30 17:39:33 +02:00
parent 7e20ff9b56
commit cacee029af
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
7 changed files with 168 additions and 99 deletions

View File

@ -1,3 +1,5 @@
//! The core API for interacting with [`Mpv`].
use futures::StreamExt; use futures::StreamExt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@ -13,6 +15,16 @@ use crate::{
Error, ErrorCode, Event, 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 <https://mpv.io/manual/master/#list-of-input-commands> for
/// the upstream list of commands.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MpvCommand { pub enum MpvCommand {
LoadFile { LoadFile {
@ -50,10 +62,12 @@ pub enum MpvCommand {
Unobserve(isize), Unobserve(isize),
} }
/// Helper trait to keep track of the string literals that mpv expects.
pub(crate) trait IntoRawCommandPart { pub(crate) trait IntoRawCommandPart {
fn into_raw_command_part(self) -> String; fn into_raw_command_part(self) -> String;
} }
/// Generic data type representing all possible data types that mpv can return.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MpvDataType { pub enum MpvDataType {
Array(Vec<MpvDataType>), Array(Vec<MpvDataType>),
@ -66,6 +80,20 @@ pub enum MpvDataType {
Usize(usize), Usize(usize),
} }
/// A mpv playlist.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Playlist(pub Vec<PlaylistEntry>);
/// 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)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PlaylistAddOptions { pub enum PlaylistAddOptions {
Replace, Replace,
@ -81,12 +109,7 @@ impl IntoRawCommandPart for PlaylistAddOptions {
} }
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] /// Options for [`MpvCommand::Seek`].
pub enum PlaylistAddTypeOptions {
File,
Playlist,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum SeekOptions { pub enum SeekOptions {
Relative, Relative,
@ -106,17 +129,7 @@ impl IntoRawCommandPart for SeekOptions {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] /// A trait for specifying how to extract and parse a value returned through [`Mpv::get_property`].
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<PlaylistEntry>);
pub trait GetPropertyTypeHandler: Sized { pub trait GetPropertyTypeHandler: Sized {
// TODO: fix this // TODO: fix this
#[allow(async_fn_in_trait)] #[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<T> { pub trait SetPropertyTypeHandler<T> {
// TODO: fix this // TODO: fix this
#[allow(async_fn_in_trait)] #[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)] #[derive(Clone)]
pub struct Mpv { pub struct Mpv {
command_sender: mpsc::Sender<(MpvIpcCommand, oneshot::Sender<MpvIpcResponse>)>, command_sender: mpsc::Sender<(MpvIpcCommand, oneshot::Sender<MpvIpcResponse>)>,
@ -226,7 +248,7 @@ impl Mpv {
pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, Error>> { pub async fn get_event_stream(&self) -> impl futures::Stream<Item = Result<Event, Error>> {
tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map( tokio_stream::wrappers::BroadcastStream::new(self.broadcast_channel.subscribe()).map(
|event| match event { |event| match event {
Ok(event) => crate::event_parser::map_event(event), Ok(event) => crate::event_parser::parse_event(event),
Err(_) => Err(Error(ErrorCode::ConnectError( Err(_) => Err(Error(ErrorCode::ConnectError(
"Failed to receive event".to_string(), "Failed to receive event".to_string(),
))), ))),
@ -281,7 +303,7 @@ impl Mpv {
/// ## Input arguments /// ## Input arguments
/// ///
/// - **command** defines the mpv command that should be executed /// - **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 /// # Example
/// ``` /// ```
@ -405,12 +427,12 @@ impl Mpv {
/// Retrieves the property value from mpv. /// Retrieves the property value from mpv.
/// ///
/// ## Supported types /// ## Supported types
/// - String /// - `String`
/// - bool /// - `bool`
/// - HashMap<String, String> (e.g. for the 'metadata' property) /// - `HashMap<String, String>` (e.g. for the 'metadata' property)
/// - Vec<PlaylistEntry> (for the 'playlist' property) /// - `Vec<PlaylistEntry>` (for the 'playlist' property)
/// - usize /// - `usize`
/// - f64 /// - `f64`
/// ///
/// ## Input arguments /// ## Input arguments
/// ///
@ -472,18 +494,18 @@ impl Mpv {
/// # Description /// # Description
/// ///
/// Sets the mpv property _<property>_ to _<value>_. /// Sets the mpv property _`<property>`_ to _`<value>`_.
/// ///
/// ## Supported types /// ## Supported types
/// - String /// - `String`
/// - bool /// - `bool`
/// - f64 /// - `f64`
/// - usize /// - `usize`
/// ///
/// ## Input arguments /// ## Input arguments
/// ///
/// - **property** defines the mpv property that should be retrieved /// - **property** defines the mpv property that should be retrieved
/// - **value** defines the value of the given mpv property _<property>_ /// - **value** defines the value of the given mpv property _`<property>`_
/// ///
/// # Example /// # Example
/// ``` /// ```

View File

@ -1,8 +1,11 @@
//! Library specific error messages.
use core::fmt; use core::fmt;
use std::fmt::Display; use std::fmt::Display;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// All possible errors that can occur when interacting with mpv.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ErrorCode { pub enum ErrorCode {
MpvError(String), MpvError(String),
@ -21,6 +24,7 @@ pub enum ErrorCode {
ValueDoesNotContainUsize, ValueDoesNotContainUsize,
} }
/// Any error that can occur when interacting with mpv.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Error(pub ErrorCode); pub struct Error(pub ErrorCode);

View File

@ -1,9 +1,21 @@
//! JSON parsing logic for events from [`MpvIpc`](crate::ipc::MpvIpc).
use std::collections::HashMap; use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::{ipc::MpvIpcEvent, Error, ErrorCode, MpvDataType}; 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 <https://mpv.io/manual/master/#properties> for
/// the upstream list of properties.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Property { pub enum Property {
Path(Option<String>), Path(Option<String>),
@ -14,6 +26,15 @@ pub enum Property {
Unknown { name: String, data: MpvDataType }, 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 <https://mpv.io/manual/master/#list-of-events> for
/// the upstream list of events.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Event { pub enum Event {
Shutdown, Shutdown,
@ -37,7 +58,8 @@ pub enum Event {
Unimplemented, Unimplemented,
} }
pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result<Event, Error> { /// Parse a highlevel [`Event`] objects from json.
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
let MpvIpcEvent(event) = raw_event; let MpvIpcEvent(event) = raw_event;
event event
@ -66,59 +88,7 @@ pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
"metadata-update" => Ok(Event::MetadataUpdate), "metadata-update" => Ok(Event::MetadataUpdate),
"seek" => Ok(Event::Seek), "seek" => Ok(Event::Seek),
"playback-restart" => Ok(Event::PlaybackRestart), "playback-restart" => Ok(Event::PlaybackRestart),
"property-change" => { "property-change" => parse_event_property(event).map(|(id, property)| Event::PropertyChange { id, property }),
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)),
},
})
}
}
}
"chapter-change" => Ok(Event::ChapterChange), "chapter-change" => Ok(Event::ChapterChange),
"client-message" => { "client-message" => {
let args = event let args = event
@ -139,3 +109,51 @@ pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
} }
}) })
} }
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
fn parse_event_property(event: &Map<String, Value>) -> 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)),
},
))
}
}
}

View File

@ -1,10 +1,13 @@
//! High-level API extension for [`Mpv`].
use crate::{ use crate::{
Error, IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, Playlist, PlaylistAddOptions, Error, IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, Playlist, PlaylistAddOptions,
PlaylistAddTypeOptions, PlaylistEntry, SeekOptions, PlaylistEntry, SeekOptions,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
/// Generic high-level command for changing a number property.
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NumberChangeOptions { pub enum NumberChangeOptions {
Absolute, Absolute,
@ -22,6 +25,7 @@ impl IntoRawCommandPart for NumberChangeOptions {
} }
} }
/// Generic high-level switch for toggling boolean properties.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Switch { pub enum Switch {
On, On,
@ -29,7 +33,14 @@ pub enum Switch {
Toggle, 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 // TODO: fix this
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
pub trait MpvExt { pub trait MpvExt {

View File

@ -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 futures::{SinkExt, StreamExt};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::mem; use std::mem;
use tokio::net::UnixStream; use tokio::{
use tokio::sync::mpsc; net::UnixStream,
use tokio::sync::{broadcast, oneshot, Mutex}; sync::{broadcast, mpsc, oneshot, Mutex},
};
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError}; 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 { pub(crate) struct MpvIpc {
socket: Framed<UnixStream, LinesCodec>, socket: Framed<UnixStream, LinesCodec>,
command_channel: mpsc::Receiver<(MpvIpcCommand, oneshot::Sender<MpvIpcResponse>)>, // 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<()>, socket_lock: Mutex<()>,
command_channel: mpsc::Receiver<(MpvIpcCommand, oneshot::Sender<MpvIpcResponse>)>,
event_channel: broadcast::Sender<MpvIpcEvent>, event_channel: broadcast::Sender<MpvIpcEvent>,
} }
/// Commands that can be sent to [`MpvIpc`]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum MpvIpcCommand { pub(crate) enum MpvIpcCommand {
Command(Vec<String>), Command(Vec<String>),
@ -24,9 +33,11 @@ pub(crate) enum MpvIpcCommand {
Exit, Exit,
} }
/// [`MpvIpc`]'s response to a [`MpvIpcCommand`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct MpvIpcResponse(pub(crate) Result<Option<Value>, Error>); pub(crate) struct MpvIpcResponse(pub(crate) Result<Option<Value>, Error>);
/// A deserialized and partially parsed event from mpv.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct MpvIpcEvent(pub(crate) Value); pub(crate) struct MpvIpcEvent(pub(crate) Value);
@ -88,10 +99,6 @@ impl MpvIpc {
property: &str, property: &str,
value: Value, value: Value,
) -> Result<Option<Value>, Error> { ) -> Result<Option<Value>, 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]) self.send_command(&[json!("set_property"), json!(property), value])
.await .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<Option<Value>, Error> { fn parse_mpv_response_data(value: Value) -> Result<Option<Value>, Error> {
log::trace!("Parsing mpv response data: {:?}", value); log::trace!("Parsing mpv response data: {:?}", value);
let result = value let result = value

View File

@ -1,11 +1,13 @@
mod highlevel_api_extension; #![doc = include_str!("../README.md")]
mod core_api; mod core_api;
mod error; mod error;
mod event_parser; mod event_parser;
mod highlevel_api_extension;
mod ipc; mod ipc;
mod message_parser; mod message_parser;
pub use highlevel_api_extension::*;
pub use core_api::*; pub use core_api::*;
pub use error::*; pub use error::*;
pub use event_parser::*; pub use event_parser::*;
pub use highlevel_api_extension::*;

View File

@ -1,3 +1,5 @@
//! JSON parsing logic for command responses from [`MpvIpc`](crate::ipc::MpvIpc).
use std::collections::HashMap; use std::collections::HashMap;
use serde_json::Value; use serde_json::Value;