split property parsing from event parsing:
Build and test / build (pull_request) Successful in 1m55s
Details
Build and test / check (pull_request) Successful in 1m52s
Details
Build and test / docs (pull_request) Successful in 2m21s
Details
Build and test / test (pull_request) Successful in 4m9s
Details
Build and test / check (push) Successful in 1m55s
Details
Build and test / build (push) Successful in 1m57s
Details
Build and test / docs (push) Successful in 2m43s
Details
Build and test / test (push) Successful in 5m33s
Details
Build and test / build (pull_request) Successful in 1m55s
Details
Build and test / check (pull_request) Successful in 1m52s
Details
Build and test / docs (pull_request) Successful in 2m21s
Details
Build and test / test (pull_request) Successful in 4m9s
Details
Build and test / check (push) Successful in 1m55s
Details
Build and test / build (push) Successful in 1m57s
Details
Build and test / docs (push) Successful in 2m43s
Details
Build and test / test (push) Successful in 5m33s
Details
High-level properties are now optional, considering there are about a thousand of them to parse. The high-level properties are a few chosen ones that I suspect might be useful for most people, with catch-all enum variants for the less common ones.
This commit is contained in:
parent
f5ca2ebde9
commit
7eec34ce00
|
@ -75,6 +75,7 @@ pub enum MpvDataType {
|
|||
Double(f64),
|
||||
HashMap(HashMap<String, MpvDataType>),
|
||||
Null,
|
||||
MinusOne,
|
||||
Playlist(Playlist),
|
||||
String(String),
|
||||
Usize(usize),
|
||||
|
|
|
@ -1,29 +1,67 @@
|
|||
//! JSON parsing logic for events from [`MpvIpc`](crate::ipc::MpvIpc).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::{ipc::MpvIpcEvent, Error, ErrorCode, MpvDataType};
|
||||
use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, 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, PartialEq, 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 },
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum EventEndFileReason {
|
||||
Eof,
|
||||
Stop,
|
||||
Quit,
|
||||
Error,
|
||||
Redirect,
|
||||
Unknown,
|
||||
Unimplemented(String),
|
||||
}
|
||||
|
||||
impl FromStr for EventEndFileReason {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"eof" => Ok(EventEndFileReason::Eof),
|
||||
"stop" => Ok(EventEndFileReason::Stop),
|
||||
"quit" => Ok(EventEndFileReason::Quit),
|
||||
"error" => Ok(EventEndFileReason::Error),
|
||||
"redirect" => Ok(EventEndFileReason::Redirect),
|
||||
reason => Ok(EventEndFileReason::Unimplemented(reason.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum EventLogMessageLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Fatal,
|
||||
Verbose,
|
||||
Debug,
|
||||
Trace,
|
||||
Unimplemented(String),
|
||||
}
|
||||
|
||||
impl FromStr for EventLogMessageLevel {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"info" => Ok(EventLogMessageLevel::Info),
|
||||
"warn" => Ok(EventLogMessageLevel::Warn),
|
||||
"error" => Ok(EventLogMessageLevel::Error),
|
||||
"fatal" => Ok(EventLogMessageLevel::Fatal),
|
||||
"verbose" => Ok(EventLogMessageLevel::Verbose),
|
||||
"debug" => Ok(EventLogMessageLevel::Debug),
|
||||
"trace" => Ok(EventLogMessageLevel::Trace),
|
||||
level => Ok(EventLogMessageLevel::Unimplemented(level.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All possible events that can be sent by mpv.
|
||||
|
@ -36,29 +74,90 @@ pub enum Property {
|
|||
/// See <https://mpv.io/manual/master/#list-of-events> for
|
||||
/// the upstream list of events.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Event {
|
||||
Shutdown,
|
||||
StartFile,
|
||||
EndFile,
|
||||
StartFile {
|
||||
playlist_entry_id: usize,
|
||||
},
|
||||
EndFile {
|
||||
reason: EventEndFileReason,
|
||||
playlist_entry_id: usize,
|
||||
file_error: Option<String>,
|
||||
playlist_insert_id: Option<usize>,
|
||||
playlist_insert_num_entries: Option<usize>,
|
||||
},
|
||||
FileLoaded,
|
||||
TracksChanged,
|
||||
TrackSwitched,
|
||||
Idle,
|
||||
Pause,
|
||||
Unpause,
|
||||
Tick,
|
||||
VideoReconfig,
|
||||
AudioReconfig,
|
||||
MetadataUpdate,
|
||||
Seek,
|
||||
PlaybackRestart,
|
||||
PropertyChange { id: usize, property: Property },
|
||||
Shutdown,
|
||||
LogMessage {
|
||||
prefix: String,
|
||||
level: EventLogMessageLevel,
|
||||
text: String,
|
||||
},
|
||||
Hook {
|
||||
hook_id: usize,
|
||||
},
|
||||
GetPropertyReply,
|
||||
SetPropertyReply,
|
||||
CommandReply {
|
||||
result: String,
|
||||
},
|
||||
ClientMessage {
|
||||
args: Vec<String>,
|
||||
},
|
||||
VideoReconfig,
|
||||
AudioReconfig,
|
||||
PropertyChange {
|
||||
id: usize,
|
||||
name: String,
|
||||
data: MpvDataType,
|
||||
},
|
||||
EventQueueOverflow,
|
||||
None,
|
||||
|
||||
/// Deprecated since mpv v0.33.0
|
||||
Idle,
|
||||
|
||||
/// Deprecated since mpv v0.31.0
|
||||
Tick,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
TracksChanged,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
TrackSwitched,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
Pause,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
Unpause,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
MetadataUpdate,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
ChapterChange,
|
||||
ClientMessage { args: Vec<String> },
|
||||
Unimplemented,
|
||||
|
||||
/// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
|
||||
ScriptInputDispatch,
|
||||
|
||||
/// Catch-all for unimplemented events
|
||||
Unimplemented(Map<String, Value>),
|
||||
}
|
||||
|
||||
// NOTE: I have not been able to test all of these events,
|
||||
// so some of the parsing logic might be incorrect.
|
||||
// In particular, I have not been able to make mpv
|
||||
// produce any of the commented out events, and since
|
||||
// the documentation for the most part just says
|
||||
// "See C API", I have not pursued this further.
|
||||
//
|
||||
// If you need this, please open an issue or a PR.
|
||||
|
||||
/// Parse a highlevel [`Event`] objects from json.
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
||||
let MpvIpcEvent(event) = raw_event;
|
||||
|
||||
|
@ -73,25 +172,113 @@ pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
|||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
|
||||
match event_name {
|
||||
"shutdown" => Ok(Event::Shutdown),
|
||||
"start-file" => Ok(Event::StartFile),
|
||||
"end-file" => Ok(Event::EndFile),
|
||||
"start-file" => parse_start_file(event),
|
||||
"end-file" => parse_end_file(event),
|
||||
"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" => parse_event_property(event)
|
||||
.map(|(id, property)| Event::PropertyChange { id, property }),
|
||||
"shutdown" => Ok(Event::Shutdown),
|
||||
"log-message" => parse_log_message(event),
|
||||
"hook" => parse_hook(event),
|
||||
// "get-property-reply" =>
|
||||
// "set-property-reply" =>
|
||||
// "command-reply" =>
|
||||
"client-message" => parse_client_message(event),
|
||||
"video-reconfig" => Ok(Event::VideoReconfig),
|
||||
"audio-reconfig" => Ok(Event::AudioReconfig),
|
||||
"property-change" => parse_property_change(event),
|
||||
"tick" => Ok(Event::Tick),
|
||||
"idle" => Ok(Event::Idle),
|
||||
"tracks-changed" => Ok(Event::TracksChanged),
|
||||
"track-switched" => Ok(Event::TrackSwitched),
|
||||
"pause" => Ok(Event::Pause),
|
||||
"unpause" => Ok(Event::Unpause),
|
||||
"metadata-update" => Ok(Event::MetadataUpdate),
|
||||
"chapter-change" => Ok(Event::ChapterChange),
|
||||
"client-message" => {
|
||||
_ => Ok(Event::Unimplemented(event.to_owned())),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_start_file(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let playlist_entry_id = event
|
||||
.get("playlist_entry_id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
Ok(Event::StartFile { playlist_entry_id })
|
||||
}
|
||||
|
||||
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let reason = event
|
||||
.get("reason")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
let playlist_entry_id = event
|
||||
.get("playlist_entry_id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
let file_error = event
|
||||
.get("file_error")
|
||||
.and_then(|v| v.as_str().map(|s| s.to_string()));
|
||||
let playlist_insert_id = event
|
||||
.get("playlist_insert_id")
|
||||
.and_then(|v| v.as_u64().map(|u| u as usize));
|
||||
let playlist_insert_num_entries = event
|
||||
.get("playlist_insert_num_entries")
|
||||
.and_then(|v| v.as_u64().map(|u| u as usize));
|
||||
|
||||
Ok(Event::EndFile {
|
||||
reason: reason
|
||||
.parse()
|
||||
.unwrap_or(EventEndFileReason::Unimplemented(reason.to_string())),
|
||||
playlist_entry_id,
|
||||
file_error,
|
||||
playlist_insert_id,
|
||||
playlist_insert_num_entries,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let prefix = event
|
||||
.get("prefix")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
||||
.to_string();
|
||||
let level = event
|
||||
.get("level")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
|
||||
let text = event
|
||||
.get("text")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_str()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainString))?
|
||||
.to_string();
|
||||
|
||||
Ok(Event::LogMessage {
|
||||
prefix,
|
||||
level: level
|
||||
.parse()
|
||||
.unwrap_or(EventLogMessageLevel::Unimplemented(level.to_string())),
|
||||
text,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_hook(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let hook_id = event
|
||||
.get("hook_id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
.as_u64()
|
||||
.ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
|
||||
Ok(Event::Hook { hook_id })
|
||||
}
|
||||
|
||||
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let args = event
|
||||
.get("args")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
|
@ -106,13 +293,8 @@ pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
|
|||
.collect::<Result<Vec<String>, Error>>()?;
|
||||
Ok(Event::ClientMessage { args })
|
||||
}
|
||||
_ => Ok(Event::Unimplemented),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
|
||||
fn parse_event_property(event: &Map<String, Value>) -> Result<(usize, Property), Error> {
|
||||
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, Error> {
|
||||
let id = event
|
||||
.get("id")
|
||||
.ok_or(Error(ErrorCode::MissingValue))?
|
||||
|
@ -123,38 +305,14 @@ fn parse_event_property(event: &Map<String, Value>) -> Result<(usize, Property),
|
|||
.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((
|
||||
|
||||
Ok(Event::PropertyChange {
|
||||
id,
|
||||
Property::Unknown {
|
||||
name: property_name.to_string(),
|
||||
// TODO: fix
|
||||
data: MpvDataType::Double(data.as_f64().unwrap_or(0.0)),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
data: json_to_value(&data)?,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
//! JSON parsing logic for properties returned in [`Event::PropertyChange`]
|
||||
//!
|
||||
//! This module is used to parse the json data from the `data` field of the
|
||||
//! [`Event::PropertyChange`] variant. Mpv has about 1000 different properties
|
||||
//! as of `v0.38.0`, so this module will only implement the most common ones.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Error, ErrorCode, Event, MpvDataType, PlaylistEntry};
|
||||
|
||||
/// 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, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum Property {
|
||||
Path(Option<String>),
|
||||
Pause(bool),
|
||||
PlaybackTime(Option<f64>),
|
||||
Duration(Option<f64>),
|
||||
Metadata(Option<HashMap<String, MpvDataType>>),
|
||||
Playlist(Vec<PlaylistEntry>),
|
||||
PlaylistPos(Option<usize>),
|
||||
LoopFile(LoopProperty),
|
||||
LoopPlaylist(LoopProperty),
|
||||
Speed(f64),
|
||||
Volume(f64),
|
||||
Mute(bool),
|
||||
Unknown { name: String, data: MpvDataType },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LoopProperty {
|
||||
N(usize),
|
||||
Inf,
|
||||
No,
|
||||
}
|
||||
|
||||
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
|
||||
pub fn parse_event_property(event: Event) -> Result<(usize, Property), Error> {
|
||||
let (id, name, data) = match event {
|
||||
Event::PropertyChange { id, name, data } => (id, name, data),
|
||||
// TODO: return proper error
|
||||
_ => {
|
||||
panic!("Event is not a PropertyChange event")
|
||||
}
|
||||
};
|
||||
|
||||
match name.as_str() {
|
||||
"path" => {
|
||||
let path = match data {
|
||||
MpvDataType::String(s) => Some(s),
|
||||
MpvDataType::Null => None,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
|
||||
};
|
||||
Ok((id, Property::Path(path)))
|
||||
}
|
||||
"pause" => {
|
||||
let pause = match data {
|
||||
MpvDataType::Bool(b) => b,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainBool)),
|
||||
};
|
||||
Ok((id, Property::Pause(pause)))
|
||||
}
|
||||
"playback-time" => {
|
||||
let playback_time = match data {
|
||||
MpvDataType::Double(d) => Some(d),
|
||||
MpvDataType::Null => None,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
|
||||
};
|
||||
Ok((id, Property::PlaybackTime(playback_time)))
|
||||
}
|
||||
"duration" => {
|
||||
let duration = match data {
|
||||
MpvDataType::Double(d) => Some(d),
|
||||
MpvDataType::Null => None,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
|
||||
};
|
||||
Ok((id, Property::Duration(duration)))
|
||||
}
|
||||
"metadata" => {
|
||||
let metadata = match data {
|
||||
MpvDataType::HashMap(m) => Some(m),
|
||||
MpvDataType::Null => None,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainHashMap)),
|
||||
};
|
||||
Ok((id, Property::Metadata(metadata)))
|
||||
}
|
||||
// "playlist" => {
|
||||
// let playlist = match data {
|
||||
// MpvDataType::Array(a) => json_array_to_playlist(&a),
|
||||
// MpvDataType::Null => Vec::new(),
|
||||
// _ => return Err(Error(ErrorCode::ValueDoesNotContainPlaylist)),
|
||||
// };
|
||||
// Ok((id, Property::Playlist(playlist)))
|
||||
// }
|
||||
"playlist-pos" => {
|
||||
let playlist_pos = match data {
|
||||
MpvDataType::Usize(u) => Some(u),
|
||||
MpvDataType::MinusOne => None,
|
||||
MpvDataType::Null => None,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainUsize)),
|
||||
};
|
||||
Ok((id, Property::PlaylistPos(playlist_pos)))
|
||||
}
|
||||
"loop-file" => {
|
||||
let loop_file = match data {
|
||||
MpvDataType::Usize(n) => LoopProperty::N(n),
|
||||
MpvDataType::Bool(b) => match b {
|
||||
true => LoopProperty::Inf,
|
||||
false => LoopProperty::No,
|
||||
},
|
||||
MpvDataType::String(s) => match s.as_str() {
|
||||
"inf" => LoopProperty::Inf,
|
||||
"no" => LoopProperty::No,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
|
||||
},
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
|
||||
};
|
||||
Ok((id, Property::LoopFile(loop_file)))
|
||||
}
|
||||
"loop-playlist" => {
|
||||
let loop_playlist = match data {
|
||||
MpvDataType::Usize(n) => LoopProperty::N(n),
|
||||
MpvDataType::Bool(b) => match b {
|
||||
true => LoopProperty::Inf,
|
||||
false => LoopProperty::No,
|
||||
},
|
||||
MpvDataType::String(s) => match s.as_str() {
|
||||
"inf" => LoopProperty::Inf,
|
||||
"no" => LoopProperty::No,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
|
||||
},
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainString)),
|
||||
};
|
||||
Ok((id, Property::LoopPlaylist(loop_playlist)))
|
||||
}
|
||||
"speed" => {
|
||||
let speed = match data {
|
||||
MpvDataType::Double(d) => d,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
|
||||
};
|
||||
Ok((id, Property::Speed(speed)))
|
||||
}
|
||||
"volume" => {
|
||||
let volume = match data {
|
||||
MpvDataType::Double(d) => d,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainF64)),
|
||||
};
|
||||
Ok((id, Property::Volume(volume)))
|
||||
}
|
||||
"mute" => {
|
||||
let mute = match data {
|
||||
MpvDataType::Bool(b) => b,
|
||||
_ => return Err(Error(ErrorCode::ValueDoesNotContainBool)),
|
||||
};
|
||||
Ok((id, Property::Mute(mute)))
|
||||
}
|
||||
// TODO: add missing cases
|
||||
_ => Ok((id, Property::Unknown { name, data })),
|
||||
}
|
||||
}
|
|
@ -51,9 +51,6 @@ impl MpvIpc {
|
|||
}
|
||||
|
||||
pub(crate) async fn send_command(&mut self, command: &[Value]) -> Result<Option<Value>, Error> {
|
||||
// let lock = self.socket_lock.lock().await;
|
||||
// START CRITICAL SECTION
|
||||
|
||||
let ipc_command = json!({ "command": command });
|
||||
let ipc_command_str = serde_json::to_string(&ipc_command)
|
||||
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
|
||||
|
@ -87,8 +84,6 @@ impl MpvIpc {
|
|||
break parsed_response;
|
||||
}
|
||||
};
|
||||
// END CRITICAL SECTION
|
||||
// mem::drop(lock);
|
||||
|
||||
log::trace!("Received response: {:?}", response);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
mod core_api;
|
||||
mod error;
|
||||
mod event_parser;
|
||||
mod event_property_parser;
|
||||
mod highlevel_api_extension;
|
||||
mod ipc;
|
||||
mod message_parser;
|
||||
|
@ -10,4 +11,5 @@ mod message_parser;
|
|||
pub use core_api::*;
|
||||
pub use error::*;
|
||||
pub use event_parser::*;
|
||||
pub use event_property_parser::*;
|
||||
pub use highlevel_api_extension::*;
|
||||
|
|
|
@ -91,45 +91,36 @@ impl TypeHandler for Vec<PlaylistEntry> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn json_to_value(value: &Value) -> Result<MpvDataType, Error> {
|
||||
match value {
|
||||
Value::Array(array) => Ok(MpvDataType::Array(json_array_to_vec(array))),
|
||||
Value::Bool(b) => Ok(MpvDataType::Bool(*b)),
|
||||
Value::Number(n) => {
|
||||
if n.is_i64() && n.as_i64().unwrap() == -1 {
|
||||
Ok(MpvDataType::MinusOne)
|
||||
} else if n.is_u64() {
|
||||
Ok(MpvDataType::Usize(n.as_u64().unwrap() as usize))
|
||||
} else if n.is_f64() {
|
||||
Ok(MpvDataType::Double(n.as_f64().unwrap()))
|
||||
} else {
|
||||
// TODO: proper error handling
|
||||
panic!("Unexpected number type");
|
||||
}
|
||||
}
|
||||
Value::Object(map) => Ok(MpvDataType::HashMap(json_map_to_hashmap(map))),
|
||||
Value::String(s) => Ok(MpvDataType::String(s.to_string())),
|
||||
Value::Null => Ok(MpvDataType::Null),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn json_map_to_hashmap(
|
||||
map: &serde_json::map::Map<String, Value>,
|
||||
) -> HashMap<String, MpvDataType> {
|
||||
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
|
||||
for (ref key, value) in map.iter() {
|
||||
match *value {
|
||||
Value::Array(ref array) => {
|
||||
output_map.insert(
|
||||
key.to_string(),
|
||||
MpvDataType::Array(json_array_to_vec(array)),
|
||||
);
|
||||
}
|
||||
Value::Bool(ref b) => {
|
||||
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
|
||||
}
|
||||
Value::Number(ref n) => {
|
||||
if n.is_u64() {
|
||||
output_map.insert(
|
||||
key.to_string(),
|
||||
MpvDataType::Usize(n.as_u64().unwrap() as usize),
|
||||
);
|
||||
} else if n.is_f64() {
|
||||
output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
|
||||
} else {
|
||||
panic!("unimplemented number");
|
||||
}
|
||||
}
|
||||
Value::String(ref s) => {
|
||||
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
|
||||
}
|
||||
Value::Object(ref m) => {
|
||||
output_map.insert(
|
||||
key.to_string(),
|
||||
MpvDataType::HashMap(json_map_to_hashmap(m)),
|
||||
);
|
||||
}
|
||||
Value::Null => {
|
||||
unimplemented!();
|
||||
}
|
||||
// TODO: proper error handling
|
||||
if let Ok(value) = json_to_value(value) {
|
||||
output_map.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
output_map
|
||||
|
@ -138,24 +129,8 @@ pub(crate) fn json_map_to_hashmap(
|
|||
pub(crate) fn json_array_to_vec(array: &[Value]) -> Vec<MpvDataType> {
|
||||
array
|
||||
.iter()
|
||||
.map(|entry| match entry {
|
||||
Value::Array(a) => MpvDataType::Array(json_array_to_vec(a)),
|
||||
Value::Bool(b) => MpvDataType::Bool(*b),
|
||||
Value::Number(n) => {
|
||||
if n.is_u64() {
|
||||
MpvDataType::Usize(n.as_u64().unwrap() as usize)
|
||||
} else if n.is_f64() {
|
||||
MpvDataType::Double(n.as_f64().unwrap())
|
||||
} else {
|
||||
panic!("unimplemented number");
|
||||
}
|
||||
}
|
||||
Value::Object(ref o) => MpvDataType::HashMap(json_map_to_hashmap(o)),
|
||||
Value::String(s) => MpvDataType::String(s.to_owned()),
|
||||
Value::Null => {
|
||||
unimplemented!();
|
||||
}
|
||||
})
|
||||
// TODO: proper error handling
|
||||
.filter_map(|entry| json_to_value(entry).ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -198,6 +173,8 @@ mod test {
|
|||
"bool": true,
|
||||
"double": 1.0,
|
||||
"usize": 1,
|
||||
"minus_one": -1,
|
||||
"null": null,
|
||||
"string": "string",
|
||||
"object": {
|
||||
"key": "value"
|
||||
|
@ -216,6 +193,8 @@ mod test {
|
|||
expected.insert("bool".to_string(), MpvDataType::Bool(true));
|
||||
expected.insert("double".to_string(), MpvDataType::Double(1.0));
|
||||
expected.insert("usize".to_string(), MpvDataType::Usize(1));
|
||||
expected.insert("minus_one".to_string(), MpvDataType::MinusOne);
|
||||
expected.insert("null".to_string(), MpvDataType::Null);
|
||||
expected.insert(
|
||||
"string".to_string(),
|
||||
MpvDataType::String("string".to_string()),
|
||||
|
@ -231,18 +210,6 @@ mod test {
|
|||
assert_eq!(json_map_to_hashmap(json.as_object().unwrap()), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_json_map_to_hashmap_fail_on_null() {
|
||||
json_map_to_hashmap(
|
||||
json!({
|
||||
"null": null
|
||||
})
|
||||
.as_object()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_array_to_vec() {
|
||||
let json = json!([
|
||||
|
@ -250,6 +217,8 @@ mod test {
|
|||
true,
|
||||
1.0,
|
||||
1,
|
||||
-1,
|
||||
null,
|
||||
"string",
|
||||
{
|
||||
"key": "value"
|
||||
|
@ -268,6 +237,8 @@ mod test {
|
|||
MpvDataType::Bool(true),
|
||||
MpvDataType::Double(1.0),
|
||||
MpvDataType::Usize(1),
|
||||
MpvDataType::MinusOne,
|
||||
MpvDataType::Null,
|
||||
MpvDataType::String("string".to_string()),
|
||||
MpvDataType::HashMap(HashMap::from([(
|
||||
"key".to_string(),
|
||||
|
@ -278,12 +249,6 @@ mod test {
|
|||
assert_eq!(json_array_to_vec(json.as_array().unwrap()), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_json_array_to_vec_fail_on_null() {
|
||||
json_array_to_vec(json!([null]).as_array().unwrap().as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_array_to_playlist() {
|
||||
let json = json!([
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
use std::panic;
|
||||
|
||||
use futures::{stream::StreamExt, SinkExt};
|
||||
use mpvipc::{Mpv, MpvDataType, MpvExt, Property};
|
||||
use mpvipc::{parse_event_property, Mpv, MpvDataType, MpvExt, Property};
|
||||
use serde_json::json;
|
||||
use test_log::test;
|
||||
use tokio::{net::UnixStream, task::JoinHandle};
|
||||
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
|
||||
|
||||
use mpvipc::Event;
|
||||
|
||||
fn test_socket(
|
||||
answers: Vec<(bool, String)>,
|
||||
) -> (UnixStream, JoinHandle<Result<(), LinesCodecError>>) {
|
||||
|
@ -53,19 +51,13 @@ async fn test_observe_event_successful() {
|
|||
tokio::spawn(async move {
|
||||
let event = mpv2.get_event_stream().await.next().await.unwrap().unwrap();
|
||||
|
||||
let property = match event {
|
||||
Event::PropertyChange { id, property } => {
|
||||
assert_eq!(id, 1);
|
||||
property
|
||||
}
|
||||
err => panic!("{:?}", err),
|
||||
};
|
||||
let data = match property {
|
||||
Property::Unknown { name, data } => {
|
||||
let data = match parse_event_property(event) {
|
||||
Ok((_, Property::Unknown { name, data })) => {
|
||||
assert_eq!(name, "volume");
|
||||
data
|
||||
}
|
||||
err => panic!("{:?}", err),
|
||||
Ok((_, property)) => panic!("{:?}", property),
|
||||
Err(err) => panic!("{:?}", err),
|
||||
};
|
||||
match data {
|
||||
MpvDataType::Double(data) => assert_eq!(data, 64.0),
|
||||
|
|
|
@ -73,24 +73,23 @@ async fn test_events() {
|
|||
let event_checking_thread = tokio::spawn(async move {
|
||||
loop {
|
||||
let event = events.next().await.unwrap().unwrap();
|
||||
if let mpvipc::Event::PropertyChange { id, property } = event {
|
||||
if id == 1337 {
|
||||
if let (1337, property) = mpvipc::parse_event_property(event).unwrap() {
|
||||
assert_eq!(property, mpvipc::Property::Pause(true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
|
||||
mpv.set_property("pause", true).await.unwrap();
|
||||
|
||||
if let Err(_) = tokio::time::timeout(
|
||||
if tokio::time::timeout(
|
||||
tokio::time::Duration::from_millis(500),
|
||||
event_checking_thread,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
panic!("Event checking thread timed out");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue