api: split into several files
This commit is contained in:
parent
3be7b2bda6
commit
60802e1158
@ -1,50 +1,17 @@
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{
|
use std::{collections::HashMap, fmt};
|
||||||
collections::HashMap,
|
|
||||||
fmt::{self, Display},
|
|
||||||
};
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::UnixStream,
|
net::UnixStream,
|
||||||
sync::{broadcast, mpsc, oneshot},
|
sync::{broadcast, mpsc, oneshot},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::ipc::{MpvIpc, MpvIpcCommand, MpvIpcEvent, MpvIpcResponse};
|
use crate::{
|
||||||
use crate::message_parser::TypeHandler;
|
ipc::{MpvIpc, MpvIpcCommand, MpvIpcEvent, MpvIpcResponse},
|
||||||
|
message_parser::TypeHandler,
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
Error, ErrorCode, Event,
|
||||||
pub enum Event {
|
};
|
||||||
Shutdown,
|
|
||||||
StartFile,
|
|
||||||
EndFile,
|
|
||||||
FileLoaded,
|
|
||||||
TracksChanged,
|
|
||||||
TrackSwitched,
|
|
||||||
Idle,
|
|
||||||
Pause,
|
|
||||||
Unpause,
|
|
||||||
Tick,
|
|
||||||
VideoReconfig,
|
|
||||||
AudioReconfig,
|
|
||||||
MetadataUpdate,
|
|
||||||
Seek,
|
|
||||||
PlaybackRestart,
|
|
||||||
PropertyChange { id: usize, property: Property },
|
|
||||||
ChapterChange,
|
|
||||||
ClientMessage { args: Vec<String> },
|
|
||||||
Unimplemented,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum Property {
|
|
||||||
Path(Option<String>),
|
|
||||||
Pause(bool),
|
|
||||||
PlaybackTime(Option<f64>),
|
|
||||||
Duration(Option<f64>),
|
|
||||||
Metadata(Option<HashMap<String, MpvDataType>>),
|
|
||||||
Unknown { name: String, data: MpvDataType },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum MpvCommand {
|
pub enum MpvCommand {
|
||||||
@ -139,24 +106,6 @@ impl IntoRawCommandPart for SeekOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub enum ErrorCode {
|
|
||||||
MpvError(String),
|
|
||||||
JsonParseError(String),
|
|
||||||
ConnectError(String),
|
|
||||||
JsonContainsUnexptectedType,
|
|
||||||
UnexpectedResult,
|
|
||||||
UnexpectedValue,
|
|
||||||
MissingValue,
|
|
||||||
UnsupportedType,
|
|
||||||
ValueDoesNotContainBool,
|
|
||||||
ValueDoesNotContainF64,
|
|
||||||
ValueDoesNotContainHashMap,
|
|
||||||
ValueDoesNotContainPlaylist,
|
|
||||||
ValueDoesNotContainString,
|
|
||||||
ValueDoesNotContainUsize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct PlaylistEntry {
|
pub struct PlaylistEntry {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
@ -220,52 +169,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct Error(pub ErrorCode);
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
Display::fmt(&self.0, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
impl Display for ErrorCode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
|
|
||||||
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
|
|
||||||
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
|
|
||||||
ErrorCode::JsonContainsUnexptectedType => {
|
|
||||||
f.write_str("Mpv sent a value with an unexpected type")
|
|
||||||
}
|
|
||||||
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
|
|
||||||
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
|
|
||||||
ErrorCode::MissingValue => f.write_str("Missing value"),
|
|
||||||
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
|
|
||||||
ErrorCode::ValueDoesNotContainBool => {
|
|
||||||
f.write_str("The received value is not of type \'std::bool\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainF64 => {
|
|
||||||
f.write_str("The received value is not of type \'std::f64\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainHashMap => {
|
|
||||||
f.write_str("The received value is not of type \'std::collections::HashMap\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainPlaylist => {
|
|
||||||
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainString => {
|
|
||||||
f.write_str("The received value is not of type \'std::string::String\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainUsize => {
|
|
||||||
f.write_str("The received value is not of type \'std::usize\'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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>)>,
|
||||||
@ -323,7 +226,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) => Mpv::map_event(event),
|
Ok(event) => crate::event_parser::map_event(event),
|
||||||
Err(_) => Err(Error(ErrorCode::ConnectError(
|
Err(_) => Err(Error(ErrorCode::ConnectError(
|
||||||
"Failed to receive event".to_string(),
|
"Failed to receive event".to_string(),
|
||||||
))),
|
))),
|
||||||
@ -331,179 +234,46 @@ impl Mpv {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
/// Run a custom command.
|
||||||
let MpvIpcEvent(event) = raw_event;
|
/// This should only be used if the desired command is not implemented
|
||||||
|
/// with [MpvCommand].
|
||||||
event
|
pub async fn run_command_raw(
|
||||||
.as_object()
|
|
||||||
.ok_or(Error(ErrorCode::JsonContainsUnexptectedType))
|
|
||||||
.and_then(|event| {
|
|
||||||
let event_name = event
|
|
||||||
.get("event")
|
|
||||||
.ok_or(Error(ErrorCode::MissingValue))?
|
|
||||||
.as_str()
|
|
||||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
|
||||||
|
|
||||||
match event_name {
|
|
||||||
"shutdown" => Ok(Event::Shutdown),
|
|
||||||
"start-file" => Ok(Event::StartFile),
|
|
||||||
"end-file" => Ok(Event::EndFile),
|
|
||||||
"file-loaded" => Ok(Event::FileLoaded),
|
|
||||||
"tracks-changed" => Ok(Event::TracksChanged),
|
|
||||||
"track-switched" => Ok(Event::TrackSwitched),
|
|
||||||
"idle" => Ok(Event::Idle),
|
|
||||||
"pause" => Ok(Event::Pause),
|
|
||||||
"unpause" => Ok(Event::Unpause),
|
|
||||||
"tick" => Ok(Event::Tick),
|
|
||||||
"video-reconfig" => Ok(Event::VideoReconfig),
|
|
||||||
"audio-reconfig" => Ok(Event::AudioReconfig),
|
|
||||||
"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)),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"chapter-change" => Ok(Event::ChapterChange),
|
|
||||||
"client-message" => {
|
|
||||||
let args = event
|
|
||||||
.get("args")
|
|
||||||
.ok_or(Error(ErrorCode::MissingValue))?
|
|
||||||
.as_array()
|
|
||||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
|
||||||
.iter()
|
|
||||||
.map(|arg| {
|
|
||||||
arg.as_str()
|
|
||||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<String>, Error>>()?;
|
|
||||||
Ok(Event::ClientMessage { args })
|
|
||||||
}
|
|
||||||
_ => Ok(Event::Unimplemented),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
|
||||||
///
|
|
||||||
/// Retrieves the property value from mpv.
|
|
||||||
///
|
|
||||||
/// ## Supported types
|
|
||||||
/// - String
|
|
||||||
/// - bool
|
|
||||||
/// - HashMap<String, String> (e.g. for the 'metadata' property)
|
|
||||||
/// - Vec<PlaylistEntry> (for the 'playlist' property)
|
|
||||||
/// - usize
|
|
||||||
/// - f64
|
|
||||||
///
|
|
||||||
/// ## Input arguments
|
|
||||||
///
|
|
||||||
/// - **property** defines the mpv property that should be retrieved
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use mpvipc::{Mpv, Error};
|
|
||||||
/// async fn main() -> Result<(), Error> {
|
|
||||||
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
|
|
||||||
/// let paused: bool = mpv.get_property("pause").await?;
|
|
||||||
/// let title: String = mpv.get_property("media-title").await?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub async fn get_property<T: GetPropertyTypeHandler>(
|
|
||||||
&self,
|
&self,
|
||||||
property: &str,
|
command: &str,
|
||||||
) -> Result<T, Error> {
|
args: &[&str],
|
||||||
T::get_property_generic(self, property).await
|
) -> Result<Option<Value>, Error> {
|
||||||
}
|
let command = Vec::from(
|
||||||
|
[command]
|
||||||
/// # Description
|
.iter()
|
||||||
///
|
.chain(args.iter())
|
||||||
/// Retrieves the property value from mpv.
|
.map(|s| s.to_string())
|
||||||
/// The result is always of type String, regardless of the type of the value of the mpv property
|
.collect::<Vec<String>>()
|
||||||
///
|
.as_slice(),
|
||||||
/// ## Input arguments
|
);
|
||||||
///
|
|
||||||
/// - **property** defines the mpv property that should be retrieved
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use mpvipc::{Mpv, Error};
|
|
||||||
/// fn main() -> Result<(), Error> {
|
|
||||||
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
|
|
||||||
/// let title = mpv.get_property_string("media-title")?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub async fn get_property_value(&self, property: &str) -> Result<Value, Error> {
|
|
||||||
let (res_tx, res_rx) = oneshot::channel();
|
let (res_tx, res_rx) = oneshot::channel();
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
|
.send((MpvIpcCommand::Command(command), res_tx))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
Error(ErrorCode::ConnectError(
|
Error(ErrorCode::ConnectError(
|
||||||
"Failed to send command".to_string(),
|
"Failed to send command".to_string(),
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match res_rx.await {
|
match res_rx.await {
|
||||||
Ok(MpvIpcResponse(response)) => {
|
Ok(MpvIpcResponse(response)) => response,
|
||||||
response.and_then(|value| value.ok_or(Error(ErrorCode::MissingValue)))
|
|
||||||
}
|
|
||||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run_command_raw_ignore_value(
|
||||||
|
&self,
|
||||||
|
command: &str,
|
||||||
|
args: &[&str],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.run_command_raw(command, args).await.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
/// # Description
|
/// # Description
|
||||||
///
|
///
|
||||||
/// Runs mpv commands. The arguments are passed as a String-Vector reference:
|
/// Runs mpv commands. The arguments are passed as a String-Vector reference:
|
||||||
@ -630,46 +400,76 @@ impl Mpv {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a custom command.
|
/// # Description
|
||||||
/// This should only be used if the desired command is not implemented
|
///
|
||||||
/// with [MpvCommand].
|
/// Retrieves the property value from mpv.
|
||||||
pub async fn run_command_raw(
|
///
|
||||||
|
/// ## Supported types
|
||||||
|
/// - String
|
||||||
|
/// - bool
|
||||||
|
/// - HashMap<String, String> (e.g. for the 'metadata' property)
|
||||||
|
/// - Vec<PlaylistEntry> (for the 'playlist' property)
|
||||||
|
/// - usize
|
||||||
|
/// - f64
|
||||||
|
///
|
||||||
|
/// ## Input arguments
|
||||||
|
///
|
||||||
|
/// - **property** defines the mpv property that should be retrieved
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use mpvipc::{Mpv, Error};
|
||||||
|
/// async fn main() -> Result<(), Error> {
|
||||||
|
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
|
||||||
|
/// let paused: bool = mpv.get_property("pause").await?;
|
||||||
|
/// let title: String = mpv.get_property("media-title").await?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn get_property<T: GetPropertyTypeHandler>(
|
||||||
&self,
|
&self,
|
||||||
command: &str,
|
property: &str,
|
||||||
args: &[&str],
|
) -> Result<T, Error> {
|
||||||
) -> Result<Option<Value>, Error> {
|
T::get_property_generic(self, property).await
|
||||||
let command = Vec::from(
|
}
|
||||||
[command]
|
|
||||||
.iter()
|
/// # Description
|
||||||
.chain(args.iter())
|
///
|
||||||
.map(|s| s.to_string())
|
/// Retrieves the property value from mpv.
|
||||||
.collect::<Vec<String>>()
|
/// The result is always of type String, regardless of the type of the value of the mpv property
|
||||||
.as_slice(),
|
///
|
||||||
);
|
/// ## Input arguments
|
||||||
|
///
|
||||||
|
/// - **property** defines the mpv property that should be retrieved
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use mpvipc::{Mpv, Error};
|
||||||
|
/// fn main() -> Result<(), Error> {
|
||||||
|
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
|
||||||
|
/// let title = mpv.get_property_string("media-title")?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn get_property_value(&self, property: &str) -> Result<Value, Error> {
|
||||||
let (res_tx, res_rx) = oneshot::channel();
|
let (res_tx, res_rx) = oneshot::channel();
|
||||||
self.command_sender
|
self.command_sender
|
||||||
.send((MpvIpcCommand::Command(command), res_tx))
|
.send((MpvIpcCommand::GetProperty(property.to_owned()), res_tx))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
Error(ErrorCode::ConnectError(
|
Error(ErrorCode::ConnectError(
|
||||||
"Failed to send command".to_string(),
|
"Failed to send command".to_string(),
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match res_rx.await {
|
match res_rx.await {
|
||||||
Ok(MpvIpcResponse(response)) => response,
|
Ok(MpvIpcResponse(response)) => {
|
||||||
|
response.and_then(|value| value.ok_or(Error(ErrorCode::MissingValue)))
|
||||||
|
}
|
||||||
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
Err(err) => Err(Error(ErrorCode::ConnectError(err.to_string()))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_command_raw_ignore_value(
|
|
||||||
&self,
|
|
||||||
command: &str,
|
|
||||||
args: &[&str],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.run_command_raw(command, args).await.map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
/// # Description
|
||||||
///
|
///
|
||||||
/// Sets the mpv property _<property>_ to _<value>_.
|
/// Sets the mpv property _<property>_ to _<value>_.
|
68
src/error.rs
Normal file
68
src/error.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
use core::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
MpvError(String),
|
||||||
|
JsonParseError(String),
|
||||||
|
ConnectError(String),
|
||||||
|
JsonContainsUnexptectedType,
|
||||||
|
UnexpectedResult,
|
||||||
|
UnexpectedValue,
|
||||||
|
MissingValue,
|
||||||
|
UnsupportedType,
|
||||||
|
ValueDoesNotContainBool,
|
||||||
|
ValueDoesNotContainF64,
|
||||||
|
ValueDoesNotContainHashMap,
|
||||||
|
ValueDoesNotContainPlaylist,
|
||||||
|
ValueDoesNotContainString,
|
||||||
|
ValueDoesNotContainUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Error(pub ErrorCode);
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for ErrorCode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
|
||||||
|
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
|
||||||
|
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
|
||||||
|
ErrorCode::JsonContainsUnexptectedType => {
|
||||||
|
f.write_str("Mpv sent a value with an unexpected type")
|
||||||
|
}
|
||||||
|
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
|
||||||
|
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
|
||||||
|
ErrorCode::MissingValue => f.write_str("Missing value"),
|
||||||
|
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
|
||||||
|
ErrorCode::ValueDoesNotContainBool => {
|
||||||
|
f.write_str("The received value is not of type \'std::bool\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainF64 => {
|
||||||
|
f.write_str("The received value is not of type \'std::f64\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainHashMap => {
|
||||||
|
f.write_str("The received value is not of type \'std::collections::HashMap\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainPlaylist => {
|
||||||
|
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainString => {
|
||||||
|
f.write_str("The received value is not of type \'std::string::String\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainUsize => {
|
||||||
|
f.write_str("The received value is not of type \'std::usize\'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
src/event_parser.rs
Normal file
141
src/event_parser.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{ipc::MpvIpcEvent, Error, ErrorCode, MpvDataType};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Property {
|
||||||
|
Path(Option<String>),
|
||||||
|
Pause(bool),
|
||||||
|
PlaybackTime(Option<f64>),
|
||||||
|
Duration(Option<f64>),
|
||||||
|
Metadata(Option<HashMap<String, MpvDataType>>),
|
||||||
|
Unknown { name: String, data: MpvDataType },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
Shutdown,
|
||||||
|
StartFile,
|
||||||
|
EndFile,
|
||||||
|
FileLoaded,
|
||||||
|
TracksChanged,
|
||||||
|
TrackSwitched,
|
||||||
|
Idle,
|
||||||
|
Pause,
|
||||||
|
Unpause,
|
||||||
|
Tick,
|
||||||
|
VideoReconfig,
|
||||||
|
AudioReconfig,
|
||||||
|
MetadataUpdate,
|
||||||
|
Seek,
|
||||||
|
PlaybackRestart,
|
||||||
|
PropertyChange { id: usize, property: Property },
|
||||||
|
ChapterChange,
|
||||||
|
ClientMessage { args: Vec<String> },
|
||||||
|
Unimplemented,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn map_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
||||||
|
let MpvIpcEvent(event) = raw_event;
|
||||||
|
|
||||||
|
event
|
||||||
|
.as_object()
|
||||||
|
.ok_or(Error(ErrorCode::JsonContainsUnexptectedType))
|
||||||
|
.and_then(|event| {
|
||||||
|
let event_name = event
|
||||||
|
.get("event")
|
||||||
|
.ok_or(Error(ErrorCode::MissingValue))?
|
||||||
|
.as_str()
|
||||||
|
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||||
|
|
||||||
|
match event_name {
|
||||||
|
"shutdown" => Ok(Event::Shutdown),
|
||||||
|
"start-file" => Ok(Event::StartFile),
|
||||||
|
"end-file" => Ok(Event::EndFile),
|
||||||
|
"file-loaded" => Ok(Event::FileLoaded),
|
||||||
|
"tracks-changed" => Ok(Event::TracksChanged),
|
||||||
|
"track-switched" => Ok(Event::TrackSwitched),
|
||||||
|
"idle" => Ok(Event::Idle),
|
||||||
|
"pause" => Ok(Event::Pause),
|
||||||
|
"unpause" => Ok(Event::Unpause),
|
||||||
|
"tick" => Ok(Event::Tick),
|
||||||
|
"video-reconfig" => Ok(Event::VideoReconfig),
|
||||||
|
"audio-reconfig" => Ok(Event::AudioReconfig),
|
||||||
|
"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)),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"chapter-change" => Ok(Event::ChapterChange),
|
||||||
|
"client-message" => {
|
||||||
|
let args = event
|
||||||
|
.get("args")
|
||||||
|
.ok_or(Error(ErrorCode::MissingValue))?
|
||||||
|
.as_array()
|
||||||
|
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
||||||
|
.iter()
|
||||||
|
.map(|arg| {
|
||||||
|
arg.as_str()
|
||||||
|
.ok_or(Error(ErrorCode::ValueDoesNotContainString))
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<String>, Error>>()?;
|
||||||
|
Ok(Event::ClientMessage { args })
|
||||||
|
}
|
||||||
|
_ => Ok(Event::Unimplemented),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -29,6 +29,7 @@ pub enum Switch {
|
|||||||
Toggle,
|
Toggle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `MpvExt` trait provides 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 {
|
12
src/lib.rs
12
src/lib.rs
@ -1,7 +1,11 @@
|
|||||||
mod api;
|
mod highlevel_api_extension;
|
||||||
mod api_extension;
|
mod core_api;
|
||||||
|
mod error;
|
||||||
|
mod event_parser;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod message_parser;
|
mod message_parser;
|
||||||
|
|
||||||
pub use api::*;
|
pub use highlevel_api_extension::*;
|
||||||
pub use api_extension::*;
|
pub use core_api::*;
|
||||||
|
pub use error::*;
|
||||||
|
pub use event_parser::*;
|
||||||
|
Loading…
Reference in New Issue
Block a user