mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-03-11 17:15:49 +00:00
Improved player communication. Test for the messages
This commit is contained in:
parent
1705b7155c
commit
0105cf1898
6 changed files with 440 additions and 99 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
|
@ -496,12 +496,22 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_test"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de9e52f2f83e2608a121618b6d3885b514613aac702306232c4f035ff60fdb56"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stremio-shell-ng"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"embed-resource",
|
||||
"heck",
|
||||
"mpv",
|
||||
"native-windows-derive",
|
||||
"native-windows-gui",
|
||||
|
|
@ -509,7 +519,10 @@ dependencies = [
|
|||
"open",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_test",
|
||||
"structopt",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"urlencoding",
|
||||
"webview2",
|
||||
"webview2-sys",
|
||||
|
|
@ -556,6 +569,24 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.73"
|
||||
|
|
|
|||
|
|
@ -19,5 +19,10 @@ structopt = "0.3"
|
|||
open = "1"
|
||||
urlencoding = "2.1.0"
|
||||
bitflags = "1.2.1"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
heck = "0.3"
|
||||
[build-dependencies]
|
||||
embed-resource = "1.3"
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.*"
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
use core::convert::TryFrom;
|
||||
use heck::KebabCase;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum_macros::EnumString;
|
||||
|
||||
// Responses
|
||||
const JSON_RESPONSES: [&str; 3] = ["track-list", "video-params", "metadata"];
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct PlayerProprChange {
|
||||
name: String,
|
||||
data: serde_json::Value,
|
||||
|
|
@ -35,7 +41,7 @@ impl PlayerProprChange {
|
|||
}
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct PlayerEnded {
|
||||
reason: String,
|
||||
}
|
||||
|
|
@ -73,3 +79,180 @@ impl PlayerResponse<'_> {
|
|||
serde_json::to_value(self).ok()
|
||||
}
|
||||
}
|
||||
|
||||
// Player incoming messages from the web UI
|
||||
/*
|
||||
Message general case - ["function-name", ["arguments", ...]]
|
||||
The function could be either mpv-observe-prop, mpv-set-prop or mpv-command.
|
||||
|
||||
["mpv-observe-prop", "prop-name"]
|
||||
["mpv-set-prop", ["prop-name", prop-val]]
|
||||
["mpv-command", ["command-name"<, "arguments">]]
|
||||
|
||||
All the function and property names are in kebab-case.
|
||||
|
||||
MPV requires type for any prop-name when observing or setting it's value.
|
||||
The type for setting is not always the same as the type for observing the prop.
|
||||
|
||||
"mpv-observe-prop" function is the only one that accepts single string
|
||||
instead of array of arguments
|
||||
|
||||
"mpv-command" function always takes an array even if the command doesn't
|
||||
have any arguments. For example this are the commands we support:
|
||||
|
||||
["mpv-command", ["loadfile", "file name"]]
|
||||
["mpv-command", ["stop"]]
|
||||
*/
|
||||
macro_rules! stringable {
|
||||
($t:ident) => {
|
||||
impl fmt::Display for $t {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", format!("{:?}", self).to_kebab_case())
|
||||
}
|
||||
}
|
||||
impl From<$t> for String {
|
||||
fn from(s: $t) -> Self {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
impl TryFrom<String> for $t {
|
||||
type Error = strum::ParseError;
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
Self::from_str(s.as_str())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, EnumString, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum InMsgFn {
|
||||
MpvSetProp,
|
||||
MpvCommand,
|
||||
MpvObserveProp,
|
||||
}
|
||||
stringable!(InMsgFn);
|
||||
|
||||
// Bool
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, EnumString, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum BoolProp {
|
||||
Pause,
|
||||
PausedForCache,
|
||||
Seeking,
|
||||
EofReached,
|
||||
}
|
||||
stringable!(BoolProp);
|
||||
// Int
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, EnumString, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum IntProp {
|
||||
Aid,
|
||||
Vid,
|
||||
Sid,
|
||||
}
|
||||
stringable!(IntProp);
|
||||
// Fp
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, EnumString, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum FpProp {
|
||||
TimePos,
|
||||
Volume,
|
||||
Duration,
|
||||
SubScale,
|
||||
CacheBufferingState,
|
||||
SubPos,
|
||||
Speed,
|
||||
}
|
||||
stringable!(FpProp);
|
||||
// Str
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, EnumString, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
pub enum StrProp {
|
||||
FfmpegVersion,
|
||||
Hwdec,
|
||||
InputDefaltBindings,
|
||||
InputVoKeyboard,
|
||||
Metadata,
|
||||
MpvVersion,
|
||||
Osc,
|
||||
Path,
|
||||
SubAssOverride,
|
||||
SubBackColor,
|
||||
SubBorderColor,
|
||||
SubColor,
|
||||
TrackList,
|
||||
VideoParams,
|
||||
// Vo,
|
||||
}
|
||||
stringable!(StrProp);
|
||||
|
||||
// Any
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum PropKey {
|
||||
Bool(BoolProp),
|
||||
Int(IntProp),
|
||||
Fp(FpProp),
|
||||
Str(StrProp),
|
||||
}
|
||||
impl fmt::Display for PropKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Bool(v) => write!(f, "{}", v),
|
||||
Self::Int(v) => write!(f, "{}", v),
|
||||
Self::Fp(v) => write!(f, "{}", v),
|
||||
Self::Str(v) => write!(f, "{}", v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum PropVal {
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
Num(f64),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, EnumString, PartialEq)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[strum(serialize_all = "kebab-case")]
|
||||
#[serde(untagged)]
|
||||
pub enum MpvCmd {
|
||||
Loadfile,
|
||||
Stop,
|
||||
}
|
||||
stringable!(MpvCmd);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum CmdVal {
|
||||
Single((MpvCmd,)),
|
||||
Double(MpvCmd, String),
|
||||
}
|
||||
impl From<CmdVal> for Vec<String> {
|
||||
fn from(cmd: CmdVal) -> Vec<String> {
|
||||
match cmd {
|
||||
CmdVal::Single(cmd) => vec![cmd.0.to_string()],
|
||||
CmdVal::Double(cmd, arg) => vec![cmd.to_string(), arg],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum InMsgArgs {
|
||||
StProp(PropKey, PropVal),
|
||||
Cmd(CmdVal),
|
||||
ObProp(PropKey),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InMsg(pub InMsgFn, pub InMsgArgs);
|
||||
|
|
|
|||
166
src/stremio_app/stremio_player/communication_tests.rs
Normal file
166
src/stremio_app/stremio_player/communication_tests.rs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
use crate::stremio_app::stremio_player::communication::{
|
||||
BoolProp, CmdVal, InMsg, InMsgArgs, InMsgFn, MpvCmd, PlayerEnded, PlayerProprChange, PropKey,
|
||||
PropVal,
|
||||
};
|
||||
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
#[test]
|
||||
fn propr_change_tokens() {
|
||||
let prop = "test-prop";
|
||||
let tokens: [Token; 6] = [
|
||||
Token::Struct {
|
||||
name: "PlayerProprChange",
|
||||
len: 2,
|
||||
},
|
||||
Token::Str("name"),
|
||||
Token::None,
|
||||
Token::Str("data"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
];
|
||||
|
||||
fn tokens_by_type(tokens: &[Token; 6], name: &'static str, val: mpv::Format, token: Token) {
|
||||
let mut typed_tokens = tokens.clone();
|
||||
typed_tokens[2] = Token::Str(name);
|
||||
typed_tokens[4] = token;
|
||||
assert_tokens(
|
||||
&PlayerProprChange::from_name_value(name.to_string(), val),
|
||||
&typed_tokens,
|
||||
);
|
||||
}
|
||||
tokens_by_type(&tokens, prop, mpv::Format::Flag(true), Token::Bool(true));
|
||||
tokens_by_type(&tokens, prop, mpv::Format::Int(1), Token::F64(1.0));
|
||||
tokens_by_type(&tokens, prop, mpv::Format::Double(1.0), Token::F64(1.0));
|
||||
tokens_by_type(&tokens, prop, mpv::Format::OsdStr("ok"), Token::Str("ok"));
|
||||
tokens_by_type(&tokens, prop, mpv::Format::Str("ok"), Token::Str("ok"));
|
||||
|
||||
// JSON response
|
||||
tokens_by_type(
|
||||
&tokens,
|
||||
"track-list",
|
||||
mpv::Format::Str(r#""ok""#),
|
||||
Token::Str("ok"),
|
||||
);
|
||||
tokens_by_type(
|
||||
&tokens,
|
||||
"video-params",
|
||||
mpv::Format::Str(r#""ok""#),
|
||||
Token::Str("ok"),
|
||||
);
|
||||
tokens_by_type(
|
||||
&tokens,
|
||||
"metadata",
|
||||
mpv::Format::Str(r#""ok""#),
|
||||
Token::Str("ok"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ended_tokens() {
|
||||
let tokens: [Token; 4] = [
|
||||
Token::Struct {
|
||||
name: "PlayerEnded",
|
||||
len: 1,
|
||||
},
|
||||
Token::Str("reason"),
|
||||
Token::None,
|
||||
Token::StructEnd,
|
||||
];
|
||||
let mut typed_tokens = tokens.clone();
|
||||
typed_tokens[2] = Token::Str("error");
|
||||
assert_tokens(
|
||||
&PlayerEnded::from_end_reason(mpv::EndFileReason::MPV_END_FILE_REASON_ERROR),
|
||||
&typed_tokens,
|
||||
);
|
||||
let mut typed_tokens = tokens.clone();
|
||||
typed_tokens[2] = Token::Str("quit");
|
||||
assert_tokens(
|
||||
&PlayerEnded::from_end_reason(mpv::EndFileReason::MPV_END_FILE_REASON_QUIT),
|
||||
&typed_tokens,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ob_propr_tokens() {
|
||||
assert_tokens(
|
||||
&InMsg(
|
||||
InMsgFn::MpvObserveProp,
|
||||
InMsgArgs::ObProp(PropKey::Bool(BoolProp::Pause)),
|
||||
),
|
||||
&[
|
||||
Token::TupleStruct {
|
||||
name: "InMsg",
|
||||
len: 2,
|
||||
},
|
||||
Token::Str("mpv-observe-prop"),
|
||||
Token::Str("pause"),
|
||||
Token::TupleStructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_propr_tokens() {
|
||||
assert_tokens(
|
||||
&InMsg(
|
||||
InMsgFn::MpvSetProp,
|
||||
InMsgArgs::StProp(PropKey::Bool(BoolProp::Pause), PropVal::Bool(true)),
|
||||
),
|
||||
&[
|
||||
Token::TupleStruct {
|
||||
name: "InMsg",
|
||||
len: 2,
|
||||
},
|
||||
Token::Str("mpv-set-prop"),
|
||||
Token::Tuple { len: 2 },
|
||||
Token::Str("pause"),
|
||||
Token::Bool(true),
|
||||
Token::TupleEnd,
|
||||
Token::TupleStructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_stop_tokens() {
|
||||
assert_tokens(
|
||||
&InMsg(
|
||||
InMsgFn::MpvCommand,
|
||||
InMsgArgs::Cmd(CmdVal::Single((MpvCmd::Stop,))),
|
||||
),
|
||||
&[
|
||||
Token::TupleStruct {
|
||||
name: "InMsg",
|
||||
len: 2,
|
||||
},
|
||||
Token::Str("mpv-command"),
|
||||
Token::Tuple { len: 1 },
|
||||
Token::Str("stop"),
|
||||
Token::TupleEnd,
|
||||
Token::TupleStructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_loadfile_tokens() {
|
||||
assert_tokens(
|
||||
&InMsg(
|
||||
InMsgFn::MpvCommand,
|
||||
InMsgArgs::Cmd(CmdVal::Double(MpvCmd::Loadfile, "some_file".to_string())),
|
||||
),
|
||||
&[
|
||||
Token::TupleStruct {
|
||||
name: "InMsg",
|
||||
len: 2,
|
||||
},
|
||||
Token::Str("mpv-command"),
|
||||
Token::Tuple { len: 2 },
|
||||
Token::Str("loadfile"),
|
||||
Token::Str("some_file"),
|
||||
Token::TupleEnd,
|
||||
Token::TupleStructEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
pub mod player;
|
||||
pub use player::Player;
|
||||
pub mod communication;
|
||||
pub use communication::{PlayerEnded, PlayerError, PlayerEvent, PlayerProprChange, PlayerResponse};
|
||||
pub use communication::{
|
||||
BoolProp, CmdVal, FpProp, InMsg, InMsgArgs, InMsgFn, IntProp, MpvCmd, PlayerEnded, PlayerError,
|
||||
PlayerEvent, PlayerProprChange, PlayerResponse, PropKey, PropVal, StrProp,
|
||||
};
|
||||
#[cfg(test)]
|
||||
mod communication_tests;
|
||||
|
|
|
|||
|
|
@ -2,19 +2,18 @@ use crate::stremio_app::ipc;
|
|||
use crate::stremio_app::RPCResponse;
|
||||
use native_windows_gui::{self as nwg, PartialUi};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
use crate::stremio_app::stremio_player::communication::{
|
||||
PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
||||
use crate::stremio_app::stremio_player::{
|
||||
InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
||||
PropKey, PropVal,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Player {
|
||||
pub channel: ipc::Channel,
|
||||
message_queue: Arc<Mutex<VecDeque<String>>>,
|
||||
}
|
||||
|
||||
impl PartialUi for Player {
|
||||
|
|
@ -30,7 +29,6 @@ impl PartialUi for Player {
|
|||
.into()
|
||||
.hwnd()
|
||||
.expect("Cannot obtain window handle") as i64;
|
||||
let message = data.message_queue.clone();
|
||||
thread::spawn(move || {
|
||||
let mut mpv_builder =
|
||||
mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
|
||||
|
|
@ -50,29 +48,23 @@ impl PartialUi for Player {
|
|||
mpv_builder
|
||||
.try_hardware_decoding()
|
||||
.expect("failed setting hwdec");
|
||||
mpv_builder
|
||||
.set_option("title", "Stremio")
|
||||
.expect("failed setting title");
|
||||
mpv_builder
|
||||
.set_option("terminal", "yes")
|
||||
.expect("failed setting terminal");
|
||||
mpv_builder
|
||||
.set_option("msg-level", "all=v")
|
||||
.set_option("msg-level", "all=no,cplayer=debug")
|
||||
.expect("failed setting msg-level");
|
||||
mpv_builder
|
||||
.set_option("quiet", "yes")
|
||||
.expect("failed setting msg-level");
|
||||
let mut mpv = mpv_builder.build().expect("Cannot build MPV");
|
||||
|
||||
let thread_messages = Arc::clone(&message);
|
||||
|
||||
thread::spawn(move || loop {
|
||||
if let Ok(msg) = rx.recv() {
|
||||
let mut messages = thread_messages.lock().unwrap();
|
||||
messages.push_back(msg);
|
||||
}
|
||||
});
|
||||
|
||||
'main: loop {
|
||||
// wait up to X seconds for an event.
|
||||
while let Some(event) = mpv.wait_event(0.03) {
|
||||
while let Some(event) = mpv.wait_event(0.0) {
|
||||
// even if you don't do anything with the events, it is still necessary to empty
|
||||
// the event loop
|
||||
|
||||
|
|
@ -105,90 +97,49 @@ impl PartialUi for Player {
|
|||
} // event processing
|
||||
|
||||
thread::sleep(std::time::Duration::from_millis(30));
|
||||
let mut in_message = message.lock().unwrap();
|
||||
for msg in in_message.drain(..) {
|
||||
let (command, data): (String, serde_json::Value) =
|
||||
serde_json::from_str(msg.as_str()).unwrap();
|
||||
match command.as_str() {
|
||||
"mpv-observe-prop" => {
|
||||
let property = data.as_str().unwrap_or_default();
|
||||
match property {
|
||||
"pause" | "paused-for-cache" | "seeking" | "eof-reached" => {
|
||||
mpv.observe_property::<bool>(property, 0).ok();
|
||||
}
|
||||
"aid" | "vid" | "sid" => {
|
||||
mpv.observe_property::<i64>(property, 0).ok();
|
||||
}
|
||||
"time-pos"
|
||||
| "volume"
|
||||
| "duration"
|
||||
| "sub-scale"
|
||||
| "cache-buffering-state"
|
||||
| "sub-pos" => {
|
||||
mpv.observe_property::<f64>(property, 0).ok();
|
||||
}
|
||||
"path" | "mpv-version" | "ffmpeg-version" | "track-list"
|
||||
| "video-params" | "metadata" => {
|
||||
mpv.observe_property::<&str>(property, 0).ok();
|
||||
}
|
||||
other => {
|
||||
eprintln!("mpv-observe-prop: not implemented for `{}`", other);
|
||||
}
|
||||
};
|
||||
for msg in rx.try_iter() {
|
||||
match serde_json::from_str::<InMsg>(msg.as_str()) {
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvObserveProp,
|
||||
InMsgArgs::ObProp(PropKey::Bool(prop)),
|
||||
)) => mpv.observe_property::<bool>(prop.to_string().as_str(), 0),
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvObserveProp,
|
||||
InMsgArgs::ObProp(PropKey::Int(prop)),
|
||||
)) => mpv.observe_property::<i64>(prop.to_string().as_str(), 0),
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvObserveProp,
|
||||
InMsgArgs::ObProp(PropKey::Fp(prop)),
|
||||
)) => mpv.observe_property::<f64>(prop.to_string().as_str(), 0),
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvObserveProp,
|
||||
InMsgArgs::ObProp(PropKey::Str(prop)),
|
||||
)) => mpv.observe_property::<&str>(prop.to_string().as_str(), 0),
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvSetProp,
|
||||
InMsgArgs::StProp(prop, PropVal::Bool(val)),
|
||||
)) => mpv.set_property(prop.to_string().as_str(), val),
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvSetProp,
|
||||
InMsgArgs::StProp(prop, PropVal::Num(val)),
|
||||
)) => mpv.set_property(prop.to_string().as_str(), val),
|
||||
Ok(InMsg(
|
||||
InMsgFn::MpvSetProp,
|
||||
InMsgArgs::StProp(prop, PropVal::Str(val)),
|
||||
)) => mpv.set_property(prop.to_string().as_str(), val.as_str()),
|
||||
Ok(InMsg(InMsgFn::MpvCommand, InMsgArgs::Cmd(cmd))) => {
|
||||
let cmd: Vec<String> = cmd.into();
|
||||
mpv.command(&cmd.iter().map(|s| s.as_str()).collect::<Vec<_>>())
|
||||
}
|
||||
"mpv-set-prop" => {
|
||||
match serde_json::from_value::<Vec<serde_json::Value>>(data.clone()) {
|
||||
Ok(prop_vector) if prop_vector.len() == 2 => {
|
||||
let prop =
|
||||
prop_vector[0].as_str().expect("Property is not a string");
|
||||
let val = prop_vector[1].clone();
|
||||
// If we change vo MPV panics
|
||||
if prop != "vo" {
|
||||
match val {
|
||||
serde_json::Value::Bool(v) => {
|
||||
mpv.set_property(prop, v).ok();
|
||||
}
|
||||
serde_json::Value::Number(v) => {
|
||||
mpv.set_property(prop, v.as_f64().unwrap()).ok();
|
||||
}
|
||||
serde_json::Value::String(v) => {
|
||||
mpv.set_property(prop, v.as_str()).ok();
|
||||
}
|
||||
val => eprintln!(
|
||||
"mpv-set-prop unsupported value {:?} for: {}",
|
||||
val, prop
|
||||
),
|
||||
};
|
||||
};
|
||||
}
|
||||
Ok(prop_vector) => {
|
||||
eprintln!("mpv-set-prop not implemented for: {:?}", prop_vector)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("mpv-set-prop Error: {:?} for data {}", e, data)
|
||||
}
|
||||
};
|
||||
_ => {
|
||||
eprintln!("MPV unsupported message {}", msg);
|
||||
Ok(())
|
||||
}
|
||||
"mpv-command" => {
|
||||
match serde_json::from_value::<Vec<String>>(data.clone()) {
|
||||
Ok(data) if data.len() > 0 => {
|
||||
let data: Vec<_> = data.iter().map(|s| s.as_str()).collect();
|
||||
if data[0] != "run" {
|
||||
mpv.command(&data).ok();
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("mpv-command Error: {:?} for data {}", e, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
.ok();
|
||||
} // incoming message drain loop
|
||||
} // main loop
|
||||
});
|
||||
|
||||
}); // builder thread
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue