mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-03-11 17:15:49 +00:00
Merge pull request #3 from Stremio/dev-progress-update
libmpv integration
This commit is contained in:
commit
17c122a2a5
9 changed files with 267 additions and 333 deletions
181
Cargo.lock
generated
181
Cargo.lock
generated
|
|
@ -118,15 +118,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 = "flume"
|
||||
version = "0.10.9"
|
||||
|
|
@ -140,12 +131,6 @@ dependencies = [
|
|||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.17"
|
||||
|
|
@ -212,9 +197,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
|
|
@ -222,6 +207,21 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
|
||||
|
||||
[[package]]
|
||||
name = "libmpv"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a58e2d19b34775e81e0fdca194b3b8ee8de973b092e7582b416343979e22e7"
|
||||
dependencies = [
|
||||
"libmpv-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libmpv-sys"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0df938d3145cd8f134572721a27afa3a51f9bc1c26ae30a1d5077162f96d074b"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
|
|
@ -231,15 +231,6 @@ dependencies = [
|
|||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
dependencies = [
|
||||
"log 0.4.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
|
|
@ -255,16 +246,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 = "git+https://github.com/Stremio/mpv-rs.git#3ef09f637803711f474edfe2c73a9f7dd35067e2"
|
||||
dependencies = [
|
||||
"enum_primitive",
|
||||
"log 0.3.9",
|
||||
"num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muldiv"
|
||||
version = "0.2.1"
|
||||
|
|
@ -308,84 +289,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"
|
||||
|
|
@ -469,7 +372,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",
|
||||
|
|
@ -532,43 +435,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"
|
||||
|
|
@ -586,12 +452,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"
|
||||
|
|
@ -660,7 +520,8 @@ dependencies = [
|
|||
"bitflags",
|
||||
"embed-resource",
|
||||
"flume",
|
||||
"mpv",
|
||||
"libmpv",
|
||||
"libmpv-sys",
|
||||
"native-windows-derive",
|
||||
"native-windows-gui",
|
||||
"once_cell",
|
||||
|
|
@ -869,7 +730,7 @@ checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log 0.4.14",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ winapi = { version = "0.3.9", features = [
|
|||
] }
|
||||
webview2 = "0.1.0"
|
||||
webview2-sys = "0.1.0-beta.1"
|
||||
mpv = { git = "https://github.com/Stremio/mpv-rs.git" }
|
||||
libmpv = "2.0.1"
|
||||
libmpv-sys = "3.1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
structopt = "0.3"
|
||||
|
|
@ -26,4 +27,4 @@ flume = "0.10.9"
|
|||
[build-dependencies]
|
||||
embed-resource = "1.3"
|
||||
[dev-dependencies]
|
||||
serde_test = "1.0.*"
|
||||
serde_test = "1.0.*"
|
||||
|
|
|
|||
8
build.rs
8
build.rs
|
|
@ -1,4 +1,4 @@
|
|||
extern crate embed_resource;
|
||||
fn main() {
|
||||
embed_resource::compile("resources.rc");
|
||||
}
|
||||
extern crate embed_resource;
|
||||
fn main() {
|
||||
embed_resource::compile("resources.rc");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,10 @@ impl MainWindow {
|
|||
let web_rx = web_rx.clone();
|
||||
// Read message from player
|
||||
thread::spawn(move || loop {
|
||||
player_rx.iter().map(|msg| web_tx_player.send(msg)).for_each(drop);
|
||||
player_rx
|
||||
.iter()
|
||||
.map(|msg| web_tx_player.send(msg))
|
||||
.for_each(drop);
|
||||
}); // thread
|
||||
|
||||
let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender();
|
||||
|
|
@ -129,7 +132,7 @@ impl MainWindow {
|
|||
hide_splash_sender.notice();
|
||||
if let Some(arg) = msg.get_params() {
|
||||
// TODO: Make this modal dialog
|
||||
eprintln!("Web App Error: {}", arg.to_string());
|
||||
eprintln!("Web App Error: {}", arg);
|
||||
}
|
||||
}
|
||||
Some("open-external") => {
|
||||
|
|
@ -210,5 +213,6 @@ impl MainWindow {
|
|||
self.window.set_visible(false);
|
||||
self.tray.tray_show_hide.set_checked(self.window.visible());
|
||||
self.transmit_window_full_screen_change(false);
|
||||
nwg::stop_thread_dispatch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,9 @@ impl RPCRequest {
|
|||
.and_then(|arg| arg.as_str())
|
||||
}
|
||||
pub fn get_params(&self) -> Option<&serde_json::Value> {
|
||||
self.args.as_ref().and_then(|args| {
|
||||
if args.len() > 1 {
|
||||
Some(&args[1])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
self.args
|
||||
.as_ref()
|
||||
.and_then(|args| if args.len() > 1 { Some(&args[1]) } else { None })
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use core::convert::TryFrom;
|
||||
use libmpv::{events::PropertyData, mpv_end_file_reason, EndFileReason};
|
||||
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())
|
||||
}
|
||||
}
|
||||
PropertyData::Node(_) => unimplemented!("`PropertyData::Node` is not supported"),
|
||||
}
|
||||
}
|
||||
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,14 @@ pub struct PlayerEnded {
|
|||
reason: String,
|
||||
}
|
||||
impl PlayerEnded {
|
||||
fn string_from_end_reason(data: mpv::EndFileReason) -> String {
|
||||
fn string_from_end_reason(data: EndFileReason) -> String {
|
||||
match data {
|
||||
mpv::EndFileReason::MPV_END_FILE_REASON_ERROR => "error".to_string(),
|
||||
mpv::EndFileReason::MPV_END_FILE_REASON_QUIT => "quit".to_string(),
|
||||
mpv_end_file_reason::Error => "error".to_string(),
|
||||
mpv_end_file_reason::Quit => "quit".to_string(),
|
||||
_ => "other".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn from_end_reason(data: mpv::EndFileReason) -> Self {
|
||||
pub fn from_end_reason(data: EndFileReason) -> Self {
|
||||
Self {
|
||||
reason: Self::string_from_end_reason(data),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::stremio_app::stremio_player::communication::{
|
|||
BoolProp, CmdVal, InMsg, InMsgArgs, InMsgFn, MpvCmd, PlayerEnded, PlayerProprChange, PropKey,
|
||||
PropVal,
|
||||
};
|
||||
use libmpv::{events::PropertyData, mpv_end_file_reason};
|
||||
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
|
|
@ -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(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),
|
||||
&PlayerEnded::from_end_reason(mpv_end_file_reason::Quit),
|
||||
&typed_tokens,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
use crate::stremio_app::ipc;
|
||||
use crate::stremio_app::RPCResponse;
|
||||
use flume::{Receiver, Sender};
|
||||
use libmpv::{events::Event, Format, Mpv, SetData};
|
||||
use native_windows_gui::{self as nwg, PartialUi};
|
||||
use std::cell::RefCell;
|
||||
use std::thread;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use winapi::shared::windef::HWND;
|
||||
|
||||
use crate::stremio_app::stremio_player::{
|
||||
InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
||||
CmdVal, InMsg, InMsgArgs, InMsgFn, PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
||||
PropKey, PropVal,
|
||||
};
|
||||
|
||||
struct ObserveProperty {
|
||||
name: String,
|
||||
format: Format,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Player {
|
||||
pub channel: ipc::Channel,
|
||||
|
|
@ -16,147 +26,206 @@ pub struct Player {
|
|||
|
||||
impl PartialUi for Player {
|
||||
fn build_partial<W: Into<nwg::ControlHandle>>(
|
||||
// @TODO replace with `&mut self`?
|
||||
data: &mut Self,
|
||||
parent: Option<W>,
|
||||
) -> Result<(), nwg::NwgError> {
|
||||
let (tx, rx) = flume::unbounded();
|
||||
let (tx1, rx1) = flume::unbounded();
|
||||
data.channel = RefCell::new(Some((tx, rx1)));
|
||||
let hwnd = parent
|
||||
.expect("No parent window")
|
||||
// @TODO replace all `expect`s with proper error handling?
|
||||
|
||||
let window_handle = parent
|
||||
.expect("no parent window")
|
||||
.into()
|
||||
.hwnd()
|
||||
.expect("Cannot obtain window handle") as i64;
|
||||
.expect("cannot obtain window handle");
|
||||
|
||||
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_built = mpv_builder.build().expect("Cannot build MPV");
|
||||
let (in_msg_sender, in_msg_receiver) = flume::unbounded();
|
||||
let (rpc_response_sender, rpc_response_receiver) = flume::unbounded();
|
||||
let (observe_property_sender, observe_property_receiver) = flume::unbounded();
|
||||
data.channel = ipc::Channel::new(Some((in_msg_sender, rpc_response_receiver)));
|
||||
|
||||
// FIXME: very often the audio track isn't selected when using "aid" = "auto"
|
||||
mpv_built.set_property("aid", 1).ok();
|
||||
let mpv = create_shareable_mpv(window_handle);
|
||||
|
||||
let mut mpv = mpv_built.clone();
|
||||
let event_thread = thread::spawn(move || {
|
||||
// -1.0 means to block and wait for an event.
|
||||
while let Some(event) = mpv.wait_event(-1.0) {
|
||||
if mpv.raw().is_null() {
|
||||
return;
|
||||
}
|
||||
let _event_thread = create_event_thread(
|
||||
Arc::clone(&mpv),
|
||||
observe_property_receiver,
|
||||
rpc_response_sender,
|
||||
);
|
||||
let _message_thread = create_message_thread(mpv, observe_property_sender, in_msg_receiver);
|
||||
// @TODO implement a mechanism to stop threads on `Player` drop if needed
|
||||
|
||||
// even if you don't do anything with the events, it is still necessary to empty
|
||||
// the event loop
|
||||
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;
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if resp_event.is_some() {
|
||||
tx1.send(RPCResponse::response_message(resp_event)).ok();
|
||||
}
|
||||
} // event drain loop
|
||||
}); // event thread
|
||||
|
||||
let mut mpv = mpv_built.clone();
|
||||
let message_thread = thread::spawn(move || {
|
||||
for msg in rx.iter() {
|
||||
if mpv.raw().is_null() {
|
||||
return;
|
||||
}
|
||||
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
|
||||
}); // message thread
|
||||
|
||||
// If we don't join our communication threads
|
||||
// the `mpv_built` gets dropped and we have
|
||||
// "use after free" errors which is very bad
|
||||
event_thread.join().ok();
|
||||
message_thread.join().ok();
|
||||
}); // builder thread
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_shareable_mpv(window_handle: HWND) -> Arc<Mpv> {
|
||||
let mpv = Mpv::with_initializer(|initializer| {
|
||||
macro_rules! set_property {
|
||||
($name:literal, $value:expr) => {
|
||||
initializer
|
||||
.set_property($name, $value)
|
||||
.expect(concat!("failed to set ", $name));
|
||||
};
|
||||
}
|
||||
set_property!("wid", window_handle as i64);
|
||||
// initializer.set_property("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)
|
||||
set_property!("gpu-context", "angle");
|
||||
set_property!("gpu-api", "auto");
|
||||
set_property!("title", "Stremio");
|
||||
set_property!("terminal", "yes");
|
||||
set_property!("msg-level", "all=no,cplayer=debug");
|
||||
set_property!("quiet", "yes");
|
||||
set_property!("hwdec", "auto");
|
||||
// FIXME: very often the audio track isn't selected when using "aid" = "auto"
|
||||
set_property!("aid", "1");
|
||||
Ok(())
|
||||
});
|
||||
Arc::new(mpv.expect("cannot build MPV"))
|
||||
}
|
||||
|
||||
fn create_event_thread(
|
||||
mpv: Arc<Mpv>,
|
||||
observe_property_receiver: Receiver<ObserveProperty>,
|
||||
rpc_response_sender: Sender<String>,
|
||||
) -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
let mut event_context = mpv.create_event_context();
|
||||
event_context
|
||||
.disable_deprecated_events()
|
||||
.expect("failed to disable deprecated MPV events");
|
||||
|
||||
// -- Event handler loop --
|
||||
|
||||
loop {
|
||||
for ObserveProperty { name, format } in observe_property_receiver.drain() {
|
||||
event_context
|
||||
.observe_property(&name, format, 0)
|
||||
.expect("failed to observer MPV property");
|
||||
}
|
||||
|
||||
// -1.0 means to block and wait for an event.
|
||||
let event = match event_context.wait_event(-1.) {
|
||||
Some(Ok(event)) => event,
|
||||
Some(Err(error)) => {
|
||||
eprintln!("Event errored: {error:?}");
|
||||
continue;
|
||||
}
|
||||
// dummy event received (may be created on a wake up call or on timeout)
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// even if you don't do anything with the events, it is still necessary to empty the event loop
|
||||
let player_response = match event {
|
||||
Event::PropertyChange { name, change, .. } => PlayerResponse(
|
||||
"mpv-prop-change",
|
||||
PlayerEvent::PropChange(PlayerProprChange::from_name_value(
|
||||
name.to_string(),
|
||||
change,
|
||||
)),
|
||||
),
|
||||
Event::EndFile(reason) => PlayerResponse(
|
||||
"mpv-event-ended",
|
||||
PlayerEvent::End(PlayerEnded::from_end_reason(reason)),
|
||||
),
|
||||
Event::Shutdown => {
|
||||
break;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
rpc_response_sender
|
||||
.send(RPCResponse::response_message(player_response.to_value()))
|
||||
.expect("failed to send RPCResponse");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn create_message_thread(
|
||||
mpv: Arc<Mpv>,
|
||||
observe_property_sender: Sender<ObserveProperty>,
|
||||
in_msg_receiver: Receiver<String>,
|
||||
) -> JoinHandle<()> {
|
||||
thread::spawn(move || {
|
||||
// -- Helpers --
|
||||
|
||||
let observe_property = |name: String, format: Format| {
|
||||
observe_property_sender
|
||||
.send(ObserveProperty { name, format })
|
||||
.expect("cannot send ObserveProperty");
|
||||
mpv.wake_up();
|
||||
};
|
||||
|
||||
let send_command = |cmd: CmdVal| {
|
||||
let (name, arg) = match cmd {
|
||||
CmdVal::Double(name, arg) => (name, format!(r#""{arg}""#)),
|
||||
CmdVal::Single((name,)) => (name, String::new()),
|
||||
};
|
||||
if let Err(error) = mpv.command(&name.to_string(), &[&arg]) {
|
||||
eprintln!("failed to execute MPV command: '{error:#}'")
|
||||
}
|
||||
};
|
||||
|
||||
fn set_property(name: impl ToString, value: impl SetData, mpv: &Mpv) {
|
||||
if let Err(error) = mpv.set_property(&name.to_string(), value) {
|
||||
eprintln!("cannot set MPV property: '{error:#}'")
|
||||
}
|
||||
}
|
||||
|
||||
// -- InMsg handler loop --
|
||||
|
||||
for msg in in_msg_receiver.iter() {
|
||||
let in_msg: InMsg = match serde_json::from_str(&msg) {
|
||||
Ok(in_msg) => in_msg,
|
||||
Err(error) => {
|
||||
eprintln!("cannot parse InMsg: {error:#}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match in_msg {
|
||||
InMsg(InMsgFn::MpvObserveProp, InMsgArgs::ObProp(PropKey::Bool(prop))) => {
|
||||
observe_property(prop.to_string(), Format::Flag);
|
||||
}
|
||||
InMsg(InMsgFn::MpvObserveProp, InMsgArgs::ObProp(PropKey::Int(prop))) => {
|
||||
observe_property(prop.to_string(), Format::Int64);
|
||||
}
|
||||
InMsg(InMsgFn::MpvObserveProp, InMsgArgs::ObProp(PropKey::Fp(prop))) => {
|
||||
observe_property(prop.to_string(), Format::Double);
|
||||
}
|
||||
InMsg(InMsgFn::MpvObserveProp, InMsgArgs::ObProp(PropKey::Str(prop))) => {
|
||||
observe_property(prop.to_string(), Format::String);
|
||||
}
|
||||
InMsg(InMsgFn::MpvSetProp, InMsgArgs::StProp(name, PropVal::Bool(value))) => {
|
||||
set_property(name, value, &mpv);
|
||||
}
|
||||
InMsg(InMsgFn::MpvSetProp, InMsgArgs::StProp(name, PropVal::Num(value))) => {
|
||||
set_property(name, value, &mpv);
|
||||
}
|
||||
InMsg(InMsgFn::MpvSetProp, InMsgArgs::StProp(name, PropVal::Str(value))) => {
|
||||
set_property(name, value, &mpv);
|
||||
}
|
||||
InMsg(InMsgFn::MpvCommand, InMsgArgs::Cmd(cmd)) => {
|
||||
send_command(cmd);
|
||||
}
|
||||
msg => {
|
||||
eprintln!("MPV unsupported message: '{msg:?}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
trait MpvExt {
|
||||
fn wake_up(&self);
|
||||
}
|
||||
|
||||
impl MpvExt for Mpv {
|
||||
// @TODO create a PR to the `libmpv` crate and then remove `libmpv-sys` from Cargo.toml?
|
||||
fn wake_up(&self) {
|
||||
unsafe { libmpv_sys::mpv_wakeup(self.ctx.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::os::windows::process::CommandExt;
|
||||
use std::process::Command;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use win32job::Job;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue