Use libmpv-rs instead of mpv-rs

This commit is contained in:
Vladimir Borisov 2021-08-27 19:09:05 +03:00
parent 4d05ec1a5d
commit 98a079db82
5 changed files with 174 additions and 284 deletions

178
Cargo.lock generated
View file

@ -107,6 +107,16 @@ dependencies = [
"syn",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "embed-resource"
version = "1.6.3"
@ -118,21 +128,6 @@ dependencies = [
"winreg",
]
[[package]]
name = "enum_primitive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180"
dependencies = [
"num-traits 0.1.43",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "heck"
version = "0.3.3"
@ -185,14 +180,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "log"
version = "0.3.9"
name = "libmpv"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
checksum = "61a58e2d19b34775e81e0fdca194b3b8ee8de973b092e7582b416343979e22e7"
dependencies = [
"log 0.4.14",
"libmpv-sys",
]
[[package]]
name = "libmpv-sys"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df938d3145cd8f134572721a27afa3a51f9bc1c26ae30a1d5077162f96d074b"
[[package]]
name = "log"
version = "0.4.14"
@ -208,17 +209,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "mpv"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e57fd944655bbef6aaab8a154b0f78ed55aaaaf211edf956330c56309236f82"
dependencies = [
"enum_primitive",
"log 0.3.9",
"num",
]
[[package]]
name = "muldiv"
version = "0.2.1"
@ -253,84 +243,6 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "num"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits 0.2.14",
]
[[package]]
name = "num-bigint"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
dependencies = [
"num-integer",
"num-traits 0.2.14",
"rand",
"rustc-serialize",
]
[[package]]
name = "num-complex"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
dependencies = [
"num-traits 0.2.14",
"rustc-serialize",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits 0.2.14",
]
[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
"autocfg",
"num-integer",
"num-traits 0.2.14",
]
[[package]]
name = "num-rational"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"
dependencies = [
"num-bigint",
"num-integer",
"num-traits 0.2.14",
"rustc-serialize",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
dependencies = [
"num-traits 0.2.14",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -394,7 +306,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"
dependencies = [
"num-traits 0.2.14",
"num-traits",
"plotters-backend",
"wasm-bindgen",
"web-sys",
@ -457,43 +369,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.5.4"
@ -511,12 +386,6 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "ryu"
version = "1.0.5"
@ -568,8 +437,9 @@ name = "stremio-shell-ng"
version = "0.1.0"
dependencies = [
"bitflags",
"crossbeam-utils",
"embed-resource",
"mpv",
"libmpv",
"native-windows-derive",
"native-windows-gui",
"once_cell",
@ -772,7 +642,7 @@ checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [
"bumpalo",
"lazy_static",
"log 0.4.14",
"log",
"proc-macro2",
"quote",
"syn",

View file

@ -5,14 +5,22 @@ edition = "2018"
[dependencies]
once_cell = "1.3.1"
native-windows-gui = { version = "1.0.4", features = ["high-dpi", "notice", "tray-notification", "menu"] }
native-windows-gui = { version = "1.0.4", features = [
"high-dpi",
"notice",
"tray-notification",
"menu",
] }
native-windows-derive = "1.0.3"
winapi = { version = "0.3.9", features = [
"libloaderapi", "handleapi", "wincon", "winuser"
"libloaderapi",
"handleapi",
"wincon",
"winuser",
] }
webview2 = "0.1.0"
webview2-sys = "0.1.0-beta.1"
mpv = "0.2.3"
libmpv = { version = "2.0.1", features = [] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
structopt = "0.3"
@ -21,8 +29,9 @@ urlencoding = "2.1.0"
bitflags = "1.2.1"
win32job = "1"
parse-display = "0.5.1"
crossbeam-utils = "0.8.5"
[build-dependencies]
embed-resource = "1.3"
[dev-dependencies]
serde_test = "1.0.*"
serde_test = "1.0.*"

View file

@ -1,4 +1,5 @@
use core::convert::TryFrom;
use libmpv::events::PropertyData;
use parse_display::{Display, FromStr};
use serde::{Deserialize, Serialize};
use std::fmt;
@ -12,26 +13,27 @@ pub struct PlayerProprChange {
data: serde_json::Value,
}
impl PlayerProprChange {
fn value_from_format(data: mpv::Format, as_json: bool) -> serde_json::Value {
fn value_from_format(data: PropertyData, as_json: bool) -> serde_json::Value {
match data {
mpv::Format::Flag(d) => serde_json::Value::Bool(d),
mpv::Format::Int(d) => serde_json::Value::Number(
PropertyData::Flag(d) => serde_json::Value::Bool(d),
PropertyData::Int64(d) => serde_json::Value::Number(
serde_json::Number::from_f64(d as f64).expect("MPV returned invalid number"),
),
mpv::Format::Double(d) => serde_json::Value::Number(
PropertyData::Double(d) => serde_json::Value::Number(
serde_json::Number::from_f64(d).expect("MPV returned invalid number"),
),
mpv::Format::OsdStr(s) => serde_json::Value::String(s.to_string()),
mpv::Format::Str(s) => {
PropertyData::OsdStr(s) => serde_json::Value::String(s.to_string()),
PropertyData::Str(s) => {
if as_json {
serde_json::from_str(s).expect("MPV returned invalid JSON data")
} else {
serde_json::Value::String(s.to_string())
}
}
_ => serde_json::Value::Null,
}
}
pub fn from_name_value(name: String, value: mpv::Format) -> Self {
pub fn from_name_value(name: String, value: PropertyData) -> Self {
let is_json = JSON_RESPONSES.contains(&name.as_str());
Self {
name,
@ -44,14 +46,15 @@ pub struct PlayerEnded {
reason: String,
}
impl PlayerEnded {
fn string_from_end_reason(data: mpv::EndFileReason) -> String {
fn string_from_end_reason(data: u32) -> String {
// defined here https://github.com/mpv-player/mpv/blob/master/libmpv/client.h#L1585
match data {
mpv::EndFileReason::MPV_END_FILE_REASON_ERROR => "error".to_string(),
mpv::EndFileReason::MPV_END_FILE_REASON_QUIT => "quit".to_string(),
4 => "error".to_string(),
3 => "quit".to_string(),
_ => "other".to_string(),
}
}
pub fn from_end_reason(data: mpv::EndFileReason) -> Self {
pub fn from_end_reason(data: u32) -> Self {
Self {
reason: Self::string_from_end_reason(data),
}

View file

@ -4,6 +4,7 @@ use crate::stremio_app::stremio_player::communication::{
};
use serde_test::{assert_tokens, Token};
use libmpv::events::PropertyData;
#[test]
fn propr_change_tokens() {
@ -20,7 +21,7 @@ fn propr_change_tokens() {
Token::StructEnd,
];
fn tokens_by_type(tokens: &[Token; 6], name: &'static str, val: mpv::Format, token: Token) {
fn tokens_by_type(tokens: &[Token; 6], name: &'static str, val: PropertyData, token: Token) {
let mut typed_tokens = tokens.clone();
typed_tokens[2] = Token::Str(name);
typed_tokens[4] = token;
@ -29,29 +30,29 @@ fn propr_change_tokens() {
&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"));
tokens_by_type(&tokens, prop, PropertyData::Flag(true), Token::Bool(true));
tokens_by_type(&tokens, prop, PropertyData::Int64(1), Token::F64(1.0));
tokens_by_type(&tokens, prop, PropertyData::Double(1.0), Token::F64(1.0));
tokens_by_type(&tokens, prop, PropertyData::OsdStr("ok"), Token::Str("ok"));
tokens_by_type(&tokens, prop, PropertyData::Str("ok"), Token::Str("ok"));
// JSON response
tokens_by_type(
&tokens,
"track-list",
mpv::Format::Str(r#""ok""#),
PropertyData::Str(r#""ok""#),
Token::Str("ok"),
);
tokens_by_type(
&tokens,
"video-params",
mpv::Format::Str(r#""ok""#),
PropertyData::Str(r#""ok""#),
Token::Str("ok"),
);
tokens_by_type(
&tokens,
"metadata",
mpv::Format::Str(r#""ok""#),
PropertyData::Str(r#""ok""#),
Token::Str("ok"),
);
}
@ -70,13 +71,13 @@ fn ended_tokens() {
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),
&PlayerEnded::from_end_reason(4),
&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),
&PlayerEnded::from_end_reason(3),
&typed_tokens,
);
}

View file

@ -1,5 +1,7 @@
use crate::stremio_app::ipc;
use crate::stremio_app::RPCResponse;
use libmpv::events::Event;
use libmpv::{Format, Mpv};
use native_windows_gui::{self as nwg, PartialUi};
use std::cell::RefCell;
use std::sync::mpsc;
@ -30,115 +32,120 @@ impl PartialUi for Player {
.hwnd()
.expect("Cannot obtain window handle") as i64;
thread::spawn(move || {
let mut mpv_builder =
mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
mpv_builder
.set_option("wid", hwnd)
.expect("failed setting wid");
// mpv_builder.set_option("vo", "gpu").expect("unable to set vo");
// win, opengl: works but least performancy, 10-15% CPU
// winvk, vulkan: works as good as d3d11
// d3d11, d1d11: works great
// dxinterop, auto: works, slightly more cpu use than d3d11
// default (auto) seems to be d3d11 (vo/gpu/d3d11)
mpv_builder
.set_option("gpu-context", "angle")
.and_then(|_| mpv_builder.set_option("gpu-api", "auto"))
.expect("setting gpu options failed");
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=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");
// builder thread
let mpv = Mpv::with_initializer(|init| {
init.set_property("wid", hwnd)?;
init.set_property("gpu-context", "angle")?;
init.set_property("gpu-api", "auto")?;
init.set_property("title", "Stremio")?;
init.set_property("terminal", "yes")?;
init.set_property("msg-level", "all=no,cplayer=debug")?;
init.set_property("quiet", "yes")?;
Ok(())
})
.expect("Cannot create MPV");
let ev_ctx = Arc::new(Mutex::new(mpv.create_event_context()));
crossbeam_utils::thread::scope(|scope| {
scope.spawn(|_| {
let rx = rx;
for msg in rx.iter() {
match serde_json::from_str::<InMsg>(msg.as_str()) {
Ok(InMsg(
InMsgFn::MpvObserveProp,
InMsgArgs::ObProp(PropKey::Bool(prop)),
)) => {
let ev_ctx = ev_ctx.lock().unwrap();
ev_ctx.observe_property(prop.to_string().as_str(), Format::Flag, 0)
}
Ok(InMsg(
InMsgFn::MpvObserveProp,
InMsgArgs::ObProp(PropKey::Int(prop)),
)) => {
let ev_ctx = ev_ctx.lock().unwrap();
ev_ctx.observe_property(prop.to_string().as_str(), Format::Int64, 0)
}
Ok(InMsg(
InMsgFn::MpvObserveProp,
InMsgArgs::ObProp(PropKey::Fp(prop)),
)) => {
let ev_ctx = ev_ctx.lock().unwrap();
ev_ctx.observe_property(
prop.to_string().as_str(),
Format::Double,
0,
)
}
Ok(InMsg(
InMsgFn::MpvObserveProp,
InMsgArgs::ObProp(PropKey::Str(prop)),
)) => {
let ev_ctx = ev_ctx.lock().unwrap();
ev_ctx.observe_property(
prop.to_string().as_str(),
Format::String,
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();
let cmd = cmd.iter().map(|s| s.as_str()).collect::<Vec<_>>();
mpv.command(cmd[0], &cmd[1..])
}
_ => {
eprintln!("MPV unsupported message {}", msg);
Ok(())
}
}
.ok();
} // incoming message drain loop
});
'main: loop {
// wait up to X seconds for an event.
while let Some(event) = mpv.wait_event(0.0) {
'main: loop {
// Give time for observe_property commands to be processed before locking the mutex
thread::sleep(std::time::Duration::from_millis(30));
// even if you don't do anything with the events, it is still necessary to empty
// the event loop
let mut ev_ctx = ev_ctx.lock().unwrap();
// wait up to X seconds for an event. -1 means forever; 0 returns event isntantly
while let Some(event) = ev_ctx.wait_event(1.0) {
let resp_event = match event {
Ok(Event::PropertyChange { name, change, .. }) => PlayerResponse(
"mpv-prop-change",
PlayerEvent::PropChange(PlayerProprChange::from_name_value(
name.to_string(),
change,
)),
)
.to_value(),
let resp_event = match event {
mpv::Event::PropertyChange {
name,
change,
reply_userdata: _,
} => PlayerResponse(
"mpv-prop-change",
PlayerEvent::PropChange(PlayerProprChange::from_name_value(
name.to_string(),
change,
)),
)
.to_value(),
mpv::Event::EndFile(Ok(reason)) => PlayerResponse(
"mpv-event-ended",
PlayerEvent::End(PlayerEnded::from_end_reason(reason)),
)
.to_value(),
mpv::Event::Shutdown => {
break 'main;
Ok(Event::EndFile(reason)) => PlayerResponse(
"mpv-event-ended",
PlayerEvent::End(PlayerEnded::from_end_reason(reason)),
)
.to_value(),
Ok(Event::Shutdown) => {
break 'main;
}
_ => None,
};
if resp_event.is_some() {
tx1.send(RPCResponse::response_message(resp_event)).ok();
}
_ => None,
};
if resp_event.is_some() {
tx1.send(RPCResponse::response_message(resp_event)).ok();
}
} // event processing
thread::sleep(std::time::Duration::from_millis(30));
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<_>>())
}
_ => {
eprintln!("MPV unsupported message {}", msg);
Ok(())
}
}
.ok();
} // incoming message drain loop
} // main loop
} // event processing loop
} // main loop
}) // crossbeam scope
}); // builder thread
Ok(())
}