mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-04-21 16:01:56 +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",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_test"
|
||||||
|
version = "1.0.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de9e52f2f83e2608a121618b6d3885b514613aac702306232c4f035ff60fdb56"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stremio-shell-ng"
|
name = "stremio-shell-ng"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
|
"heck",
|
||||||
"mpv",
|
"mpv",
|
||||||
"native-windows-derive",
|
"native-windows-derive",
|
||||||
"native-windows-gui",
|
"native-windows-gui",
|
||||||
|
|
@ -509,7 +519,10 @@ dependencies = [
|
||||||
"open",
|
"open",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_test",
|
||||||
"structopt",
|
"structopt",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"webview2",
|
"webview2",
|
||||||
"webview2-sys",
|
"webview2-sys",
|
||||||
|
|
@ -556,6 +569,24 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.73"
|
version = "1.0.73"
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,10 @@ structopt = "0.3"
|
||||||
open = "1"
|
open = "1"
|
||||||
urlencoding = "2.1.0"
|
urlencoding = "2.1.0"
|
||||||
bitflags = "1.2.1"
|
bitflags = "1.2.1"
|
||||||
|
strum = "0.21"
|
||||||
|
strum_macros = "0.21"
|
||||||
|
heck = "0.3"
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
embed-resource = "1.3"
|
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 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"];
|
const JSON_RESPONSES: [&str; 3] = ["track-list", "video-params", "metadata"];
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct PlayerProprChange {
|
pub struct PlayerProprChange {
|
||||||
name: String,
|
name: String,
|
||||||
data: serde_json::Value,
|
data: serde_json::Value,
|
||||||
|
|
@ -35,7 +41,7 @@ impl PlayerProprChange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub struct PlayerEnded {
|
pub struct PlayerEnded {
|
||||||
reason: String,
|
reason: String,
|
||||||
}
|
}
|
||||||
|
|
@ -73,3 +79,180 @@ impl PlayerResponse<'_> {
|
||||||
serde_json::to_value(self).ok()
|
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 mod player;
|
||||||
pub use player::Player;
|
pub use player::Player;
|
||||||
pub mod communication;
|
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 crate::stremio_app::RPCResponse;
|
||||||
use native_windows_gui::{self as nwg, PartialUi};
|
use native_windows_gui::{self as nwg, PartialUi};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use crate::stremio_app::stremio_player::communication::{
|
use crate::stremio_app::stremio_player::{
|
||||||
PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
||||||
|
PropKey, PropVal,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub channel: ipc::Channel,
|
pub channel: ipc::Channel,
|
||||||
message_queue: Arc<Mutex<VecDeque<String>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialUi for Player {
|
impl PartialUi for Player {
|
||||||
|
|
@ -30,7 +29,6 @@ impl PartialUi for Player {
|
||||||
.into()
|
.into()
|
||||||
.hwnd()
|
.hwnd()
|
||||||
.expect("Cannot obtain window handle") as i64;
|
.expect("Cannot obtain window handle") as i64;
|
||||||
let message = data.message_queue.clone();
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let mut mpv_builder =
|
let mut mpv_builder =
|
||||||
mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
|
mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
|
||||||
|
|
@ -50,29 +48,23 @@ impl PartialUi for Player {
|
||||||
mpv_builder
|
mpv_builder
|
||||||
.try_hardware_decoding()
|
.try_hardware_decoding()
|
||||||
.expect("failed setting hwdec");
|
.expect("failed setting hwdec");
|
||||||
|
mpv_builder
|
||||||
|
.set_option("title", "Stremio")
|
||||||
|
.expect("failed setting title");
|
||||||
mpv_builder
|
mpv_builder
|
||||||
.set_option("terminal", "yes")
|
.set_option("terminal", "yes")
|
||||||
.expect("failed setting terminal");
|
.expect("failed setting terminal");
|
||||||
mpv_builder
|
mpv_builder
|
||||||
.set_option("msg-level", "all=v")
|
.set_option("msg-level", "all=no,cplayer=debug")
|
||||||
.expect("failed setting msg-level");
|
.expect("failed setting msg-level");
|
||||||
mpv_builder
|
mpv_builder
|
||||||
.set_option("quiet", "yes")
|
.set_option("quiet", "yes")
|
||||||
.expect("failed setting msg-level");
|
.expect("failed setting msg-level");
|
||||||
let mut mpv = mpv_builder.build().expect("Cannot build MPV");
|
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 {
|
'main: loop {
|
||||||
// wait up to X seconds for an event.
|
// 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
|
// even if you don't do anything with the events, it is still necessary to empty
|
||||||
// the event loop
|
// the event loop
|
||||||
|
|
||||||
|
|
@ -105,90 +97,49 @@ impl PartialUi for Player {
|
||||||
} // event processing
|
} // event processing
|
||||||
|
|
||||||
thread::sleep(std::time::Duration::from_millis(30));
|
thread::sleep(std::time::Duration::from_millis(30));
|
||||||
let mut in_message = message.lock().unwrap();
|
for msg in rx.try_iter() {
|
||||||
for msg in in_message.drain(..) {
|
match serde_json::from_str::<InMsg>(msg.as_str()) {
|
||||||
let (command, data): (String, serde_json::Value) =
|
Ok(InMsg(
|
||||||
serde_json::from_str(msg.as_str()).unwrap();
|
InMsgFn::MpvObserveProp,
|
||||||
match command.as_str() {
|
InMsgArgs::ObProp(PropKey::Bool(prop)),
|
||||||
"mpv-observe-prop" => {
|
)) => mpv.observe_property::<bool>(prop.to_string().as_str(), 0),
|
||||||
let property = data.as_str().unwrap_or_default();
|
Ok(InMsg(
|
||||||
match property {
|
InMsgFn::MpvObserveProp,
|
||||||
"pause" | "paused-for-cache" | "seeking" | "eof-reached" => {
|
InMsgArgs::ObProp(PropKey::Int(prop)),
|
||||||
mpv.observe_property::<bool>(property, 0).ok();
|
)) => mpv.observe_property::<i64>(prop.to_string().as_str(), 0),
|
||||||
}
|
Ok(InMsg(
|
||||||
"aid" | "vid" | "sid" => {
|
InMsgFn::MpvObserveProp,
|
||||||
mpv.observe_property::<i64>(property, 0).ok();
|
InMsgArgs::ObProp(PropKey::Fp(prop)),
|
||||||
}
|
)) => mpv.observe_property::<f64>(prop.to_string().as_str(), 0),
|
||||||
"time-pos"
|
Ok(InMsg(
|
||||||
| "volume"
|
InMsgFn::MpvObserveProp,
|
||||||
| "duration"
|
InMsgArgs::ObProp(PropKey::Str(prop)),
|
||||||
| "sub-scale"
|
)) => mpv.observe_property::<&str>(prop.to_string().as_str(), 0),
|
||||||
| "cache-buffering-state"
|
Ok(InMsg(
|
||||||
| "sub-pos" => {
|
InMsgFn::MpvSetProp,
|
||||||
mpv.observe_property::<f64>(property, 0).ok();
|
InMsgArgs::StProp(prop, PropVal::Bool(val)),
|
||||||
}
|
)) => mpv.set_property(prop.to_string().as_str(), val),
|
||||||
"path" | "mpv-version" | "ffmpeg-version" | "track-list"
|
Ok(InMsg(
|
||||||
| "video-params" | "metadata" => {
|
InMsgFn::MpvSetProp,
|
||||||
mpv.observe_property::<&str>(property, 0).ok();
|
InMsgArgs::StProp(prop, PropVal::Num(val)),
|
||||||
}
|
)) => mpv.set_property(prop.to_string().as_str(), val),
|
||||||
other => {
|
Ok(InMsg(
|
||||||
eprintln!("mpv-observe-prop: not implemented for `{}`", other);
|
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()) {
|
eprintln!("MPV unsupported message {}", msg);
|
||||||
Ok(prop_vector) if prop_vector.len() == 2 => {
|
Ok(())
|
||||||
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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
"mpv-command" => {
|
}
|
||||||
match serde_json::from_value::<Vec<String>>(data.clone()) {
|
.ok();
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
} // incoming message drain loop
|
} // incoming message drain loop
|
||||||
} // main loop
|
} // main loop
|
||||||
});
|
}); // builder thread
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue