mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-05-04 22:19:05 +00:00
Progress on the IPC
This commit is contained in:
parent
56d255f44a
commit
d3d05a43fb
4 changed files with 211 additions and 110 deletions
|
|
@ -5,7 +5,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
once_cell = "1.3.1"
|
once_cell = "1.3.1"
|
||||||
native-windows-gui = { version = "1.0.4", features = ["high-dpi"] }
|
native-windows-gui = { version = "1.0.4", features = ["high-dpi", "notice"] }
|
||||||
native-windows-derive = "1.0.3"
|
native-windows-derive = "1.0.3"
|
||||||
winapi = { version = "0.3.9", features = [
|
winapi = { version = "0.3.9", features = [
|
||||||
"libloaderapi", "handleapi",
|
"libloaderapi", "handleapi",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,43 @@
|
||||||
use native_windows_derive::NwgUi;
|
use native_windows_derive::NwgUi;
|
||||||
use native_windows_gui as nwg;
|
use native_windows_gui as nwg;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
use crate::stremio_app::stremio_player::Player;
|
use crate::stremio_app::stremio_player::Player;
|
||||||
use crate::stremio_app::stremio_wevbiew::WebView;
|
use crate::stremio_app::stremio_wevbiew::WebView;
|
||||||
|
|
||||||
|
//////////////////////////////////////////
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct RPCRequest {
|
||||||
|
id: u64,
|
||||||
|
args: Option<Vec<serde_json::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct RPCResponseDataTransport {
|
||||||
|
properties: Vec<Vec<String>>,
|
||||||
|
signals: Vec<String>,
|
||||||
|
methods: Vec<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct RPCResponseData {
|
||||||
|
transport: RPCResponseDataTransport,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
struct RPCResponse {
|
||||||
|
id: u64,
|
||||||
|
object: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
response_type: u32,
|
||||||
|
data: RPCResponseData,
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////
|
||||||
|
|
||||||
#[derive(Default, NwgUi)]
|
#[derive(Default, NwgUi)]
|
||||||
pub struct MainWindow {
|
pub struct MainWindow {
|
||||||
#[nwg_control(title: "Stremio", flags: "MAIN_WINDOW|VISIBLE")]
|
#[nwg_control(title: "Stremio", flags: "MAIN_WINDOW|VISIBLE")]
|
||||||
|
|
@ -26,13 +59,74 @@ impl MainWindow {
|
||||||
self.window
|
self.window
|
||||||
.set_size(dimensions.0 as u32, dimensions.1 as u32);
|
.set_size(dimensions.0 as u32, dimensions.1 as u32);
|
||||||
self.window.set_position(x, y);
|
self.window.set_position(x, y);
|
||||||
// let video_path = "/home/ivo/storage/bbb_sunflower_1080p_30fps_normal.mp4";
|
|
||||||
let video_path = "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4";
|
let player_channel = self.player.channel.borrow();
|
||||||
self.player.command(&["loadfile", video_path]);
|
let (player_tx, player_rx) = player_channel.as_ref().unwrap();
|
||||||
// self.player.set_prop("time-pos", 120.0);
|
let player_tx = player_tx.clone();
|
||||||
self.player.set_prop("speed", 2.0);
|
let player_rx = Arc::clone(player_rx);
|
||||||
// self.player.set_prop("pause", true);
|
|
||||||
self.player.command(&["stop"]);
|
let web_channel = self.webview.channel.borrow();
|
||||||
|
let (web_tx, web_rx) = web_channel.as_ref().unwrap();
|
||||||
|
let web_tx = web_tx.clone();
|
||||||
|
let web_rx = Arc::clone(web_rx);
|
||||||
|
thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
// Read message from player
|
||||||
|
{
|
||||||
|
let rx = player_rx.lock().unwrap();
|
||||||
|
if let Ok(msg) = rx.try_recv() {
|
||||||
|
println!("APP GOT FROM PLAYER {}", msg);
|
||||||
|
// web_tx.send(msg).ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// read message from WebView
|
||||||
|
{
|
||||||
|
let rx = web_rx.lock().unwrap();
|
||||||
|
if let Ok(msg) = rx.try_recv() {
|
||||||
|
let msg: RPCRequest = serde_json::from_str(&msg).unwrap();
|
||||||
|
if msg.id == 0 {
|
||||||
|
let resp: RPCResponse = RPCResponse {
|
||||||
|
id: 0,
|
||||||
|
object: "transport".to_string(),
|
||||||
|
response_type: 3,
|
||||||
|
data: RPCResponseData {
|
||||||
|
transport: RPCResponseDataTransport {
|
||||||
|
properties: vec![
|
||||||
|
vec![],
|
||||||
|
vec![
|
||||||
|
"".to_string(),
|
||||||
|
"shellVersion".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
"5.0.0".to_string(),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
signals: vec![],
|
||||||
|
methods: vec![vec!["onEvent".to_string(), "".to_string()]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let resp_json = serde_json::to_string(&resp).unwrap();
|
||||||
|
web_tx.send(resp_json).ok();
|
||||||
|
} else if let Some(args) = msg.args {
|
||||||
|
// TODO: this can panic
|
||||||
|
let method = serde_json::from_value::<String>(args[0].clone()).unwrap();
|
||||||
|
if method.starts_with("mpv-") {
|
||||||
|
let resp_json = serde_json::to_string(&args).unwrap();
|
||||||
|
player_tx.send(resp_json).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// // let video_path = "/home/ivo/storage/bbb_sunflower_1080p_30fps_normal.mp4";
|
||||||
|
// let video_path = "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4";
|
||||||
|
// self.player.command(&["loadfile", video_path]);
|
||||||
|
// // self.player.set_prop("time-pos", 120.0);
|
||||||
|
// self.player.set_prop("speed", 2.0);
|
||||||
|
// // self.player.set_prop("pause", true);
|
||||||
|
// self.player.command(&["stop"]);
|
||||||
}
|
}
|
||||||
fn on_quit(&self) {
|
fn on_quit(&self) {
|
||||||
nwg::stop_thread_dispatch();
|
nwg::stop_thread_dispatch();
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,12 @@
|
||||||
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::sync::mpsc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
mpv: RefCell<Option<mpv::MpvHandler>>,
|
pub channel: RefCell<Option<(mpsc::Sender<String>, Arc<Mutex<mpsc::Receiver<String>>>)>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
pub fn command(&self, args: &[&str]) {
|
|
||||||
let mut mpv = self.mpv.borrow_mut();
|
|
||||||
let mpv = mpv.as_mut().expect("Failed to create MPV");
|
|
||||||
if let Err(e) = mpv.command(args) {
|
|
||||||
eprintln!("Failed to execute command {:?} - {:?}", args, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn set_prop<T: mpv::MpvFormat>(&self, prop: &str, val: T) {
|
|
||||||
let mut mpv = self.mpv.borrow_mut();
|
|
||||||
let mpv = mpv.as_mut().expect("Failed to create MPV");
|
|
||||||
if let Err(e) = mpv.set_property(prop, val) {
|
|
||||||
eprintln!("Failed to set property {} - {:?}", prop, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialUi for Player {
|
impl PartialUi for Player {
|
||||||
|
|
@ -28,39 +14,66 @@ impl PartialUi for Player {
|
||||||
data: &mut Self,
|
data: &mut Self,
|
||||||
parent: Option<W>,
|
parent: Option<W>,
|
||||||
) -> Result<(), nwg::NwgError> {
|
) -> Result<(), nwg::NwgError> {
|
||||||
let mut mpv_builder =
|
let (tx, rx) = mpsc::channel::<String>();
|
||||||
mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
|
let (tx1, rx1) = mpsc::channel::<String>();
|
||||||
mpv_builder
|
data.channel = RefCell::new(Some((tx, Arc::new(Mutex::new(rx1)))));
|
||||||
.set_option(
|
let hwnd = parent
|
||||||
"wid",
|
.expect("No parent window")
|
||||||
parent
|
.into()
|
||||||
.expect("No parent window")
|
.hwnd()
|
||||||
.into()
|
.expect("Cannot obtain window handle") as i64;
|
||||||
.hwnd()
|
thread::spawn(move || {
|
||||||
.expect("Cannot obtain window handle") as i64,
|
let mut mpv_builder =
|
||||||
)
|
mpv::MpvHandlerBuilder::new().expect("Error while creating MPV builder");
|
||||||
.expect("failed setting wid");
|
mpv_builder
|
||||||
// mpv_builder.set_option("vo", "gpu").expect("unable to set vo");
|
.set_option("wid", hwnd)
|
||||||
// win, opengl: works but least performancy, 10-15% CPU
|
.expect("failed setting wid");
|
||||||
// winvk, vulkan: works as good as d3d11
|
// mpv_builder.set_option("vo", "gpu").expect("unable to set vo");
|
||||||
// d3d11, d1d11: works great
|
// win, opengl: works but least performancy, 10-15% CPU
|
||||||
// dxinterop, auto: works, slightly more cpu use than d3d11
|
// winvk, vulkan: works as good as d3d11
|
||||||
// default (auto) seems to be d3d11 (vo/gpu/d3d11)
|
// d3d11, d1d11: works great
|
||||||
mpv_builder
|
// dxinterop, auto: works, slightly more cpu use than d3d11
|
||||||
.set_option("gpu-context", "angle")
|
// default (auto) seems to be d3d11 (vo/gpu/d3d11)
|
||||||
.and_then(|_| mpv_builder.set_option("gpu-api", "auto"))
|
mpv_builder
|
||||||
.expect("setting gpu options failed");
|
.set_option("gpu-context", "angle")
|
||||||
mpv_builder
|
.and_then(|_| mpv_builder.set_option("gpu-api", "auto"))
|
||||||
.try_hardware_decoding()
|
.expect("setting gpu options failed");
|
||||||
.expect("failed setting hwdec");
|
mpv_builder
|
||||||
mpv_builder
|
.try_hardware_decoding()
|
||||||
.set_option("terminal", "yes")
|
.expect("failed setting hwdec");
|
||||||
.expect("failed setting terminal");
|
mpv_builder
|
||||||
mpv_builder
|
.set_option("terminal", "yes")
|
||||||
.set_option("msg-level", "all=v")
|
.expect("failed setting terminal");
|
||||||
.expect("failed setting msg-level");
|
mpv_builder
|
||||||
//mpv_builder.set_option("quiet", "yes").expect("failed setting msg-level");
|
.set_option("msg-level", "all=v")
|
||||||
data.mpv = RefCell::new(mpv_builder.build().ok());
|
.expect("failed setting msg-level");
|
||||||
|
//mpv_builder.set_option("quiet", "yes").expect("failed setting msg-level");
|
||||||
|
let mut mpv = mpv_builder.build().unwrap();
|
||||||
|
'main: loop {
|
||||||
|
// wait up to 0.0 seconds for an event.
|
||||||
|
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
|
||||||
|
// TODO: Parse and format the Event in proper JSON format
|
||||||
|
tx1.send(format!("{:?}", event)).ok();
|
||||||
|
println!("RECEIVED EVENT : {:?}", event);
|
||||||
|
match event {
|
||||||
|
mpv::Event::Shutdown | mpv::Event::EndFile(_) => {
|
||||||
|
break 'main;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Ok(msg) = rx.try_recv() {
|
||||||
|
println!("PLAYER RECEIVED MESSAGE: {}", msg);
|
||||||
|
// let video_path = "http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4";
|
||||||
|
// mpv.command(&["loadfile", video_path]).ok();
|
||||||
|
// mpv.command(&["stop"]).ok();
|
||||||
|
// mpv.set_property("paused", true).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,22 @@
|
||||||
use native_windows_gui::{self as nwg, PartialUi};
|
use native_windows_gui::{self as nwg, PartialUi};
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
use serde::{Deserialize, Serialize};
|
use std::cell::RefCell;
|
||||||
use serde_json;
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
use webview2::Controller;
|
use webview2::Controller;
|
||||||
use winapi::shared::windef::HWND__;
|
use winapi::shared::windef::HWND__;
|
||||||
use winapi::um::winuser::*;
|
use winapi::um::winuser::*;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct RPCRequest {
|
|
||||||
id: u64,
|
|
||||||
args: Option<Vec<serde_json::Value>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct RPCResponseDataTransport {
|
|
||||||
properties: Vec<Vec<String>>,
|
|
||||||
signals: Vec<String>,
|
|
||||||
methods: Vec<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct RPCResponseData {
|
|
||||||
transport: RPCResponseDataTransport,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct RPCResponse {
|
|
||||||
id: u64,
|
|
||||||
object: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
response_type: u32,
|
|
||||||
data: RPCResponseData,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct WebView {
|
pub struct WebView {
|
||||||
controller: Rc<OnceCell<Controller>>,
|
controller: Rc<OnceCell<Controller>>,
|
||||||
|
pub channel: RefCell<Option<(mpsc::Sender<String>, Arc<Mutex<mpsc::Receiver<String>>>)>>,
|
||||||
|
notice: nwg::Notice,
|
||||||
|
compute: RefCell<Option<thread::JoinHandle<()>>>,
|
||||||
|
message: Arc<Mutex<Option<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebView {
|
impl WebView {
|
||||||
|
|
@ -61,9 +40,19 @@ impl PartialUi for WebView {
|
||||||
data: &mut Self,
|
data: &mut Self,
|
||||||
parent: Option<W>,
|
parent: Option<W>,
|
||||||
) -> Result<(), nwg::NwgError> {
|
) -> Result<(), nwg::NwgError> {
|
||||||
|
let (tx, rx) = mpsc::channel::<String>();
|
||||||
|
let (tx1, rx1) = mpsc::channel::<String>();
|
||||||
|
data.channel = RefCell::new(Some((tx, Arc::new(Mutex::new(rx1)))));
|
||||||
|
|
||||||
let parent = parent.expect("No parent window").into();
|
let parent = parent.expect("No parent window").into();
|
||||||
let hwnd = parent.hwnd().expect("Cannot obtain window handle");
|
|
||||||
|
let hwnd = parent.hwnd().expect("Cannot obtain window handle") as i64;
|
||||||
|
nwg::Notice::builder()
|
||||||
|
.parent(parent)
|
||||||
|
.build(&mut data.notice)
|
||||||
|
.ok();
|
||||||
let controller_clone = data.controller.clone();
|
let controller_clone = data.controller.clone();
|
||||||
|
let hwnd = hwnd as *mut HWND__;
|
||||||
let result = webview2::EnvironmentBuilder::new()
|
let result = webview2::EnvironmentBuilder::new()
|
||||||
.with_additional_browser_arguments("--disable-gpu")
|
.with_additional_browser_arguments("--disable-gpu")
|
||||||
.build(move |env| {
|
.build(move |env| {
|
||||||
|
|
@ -98,30 +87,12 @@ impl PartialUi for WebView {
|
||||||
|_| Ok(()),
|
|_| Ok(()),
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
webview.add_web_message_received(|w, msg| {
|
webview.add_web_message_received(move |_w, msg| {
|
||||||
let msg = msg.try_get_web_message_as_string()?;
|
let msg = msg.try_get_web_message_as_string()?;
|
||||||
let msg: RPCRequest = serde_json::from_str(&msg).unwrap();
|
tx1.send(msg).ok();
|
||||||
dbg!(msg.clone());
|
|
||||||
if msg.id == 0 {
|
|
||||||
let resp: RPCResponse = RPCResponse {
|
|
||||||
id: 0,
|
|
||||||
object: "transport".to_string(),
|
|
||||||
response_type: 3,
|
|
||||||
data: RPCResponseData {
|
|
||||||
transport: RPCResponseDataTransport {
|
|
||||||
properties: vec![vec![], vec!["".to_string(), "shellVersion".to_string(), "".to_string(), "5.0.0".to_string()]],
|
|
||||||
signals: vec![],
|
|
||||||
methods: vec![vec!["onEvent".to_string(), "".to_string()]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let resp_json = serde_json::to_string(&resp).unwrap();
|
|
||||||
dbg!(resp_json.clone());
|
|
||||||
w.post_web_message_as_string(&resp_json).ok();
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}).ok();
|
}).ok();
|
||||||
WebView::resize_to_window_bounds_and_show(Some(&controller), parent.hwnd());
|
WebView::resize_to_window_bounds_and_show(Some(&controller), Some(hwnd));
|
||||||
controller_clone
|
controller_clone
|
||||||
.set(controller)
|
.set(controller)
|
||||||
.expect("Cannot update the controller");
|
.expect("Cannot update the controller");
|
||||||
|
|
@ -135,6 +106,17 @@ impl PartialUi for WebView {
|
||||||
&format!("{}", e),
|
&format!("{}", e),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sender = data.notice.sender();
|
||||||
|
let message = data.message.clone();
|
||||||
|
*data.compute.borrow_mut() = Some(thread::spawn(move || loop {
|
||||||
|
if let Ok(msg) = rx.try_recv() {
|
||||||
|
let mut message = message.lock().unwrap();
|
||||||
|
*message = Some(msg);
|
||||||
|
sender.notice();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn process_event<'a>(
|
fn process_event<'a>(
|
||||||
|
|
@ -145,6 +127,7 @@ impl PartialUi for WebView {
|
||||||
) {
|
) {
|
||||||
use nwg::Event as E;
|
use nwg::Event as E;
|
||||||
match evt {
|
match evt {
|
||||||
|
E::OnInit => {}
|
||||||
E::OnResize | E::OnWindowMaximize => {
|
E::OnResize | E::OnWindowMaximize => {
|
||||||
WebView::resize_to_window_bounds_and_show(self.controller.get(), handle.hwnd());
|
WebView::resize_to_window_bounds_and_show(self.controller.get(), handle.hwnd());
|
||||||
}
|
}
|
||||||
|
|
@ -153,6 +136,17 @@ impl PartialUi for WebView {
|
||||||
controller.put_is_visible(false).ok();
|
controller.put_is_visible(false).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
E::OnNotice => {
|
||||||
|
let msg = self.message.clone();
|
||||||
|
let mut msg = msg.lock().unwrap();
|
||||||
|
if let Some(msg) = &*msg {
|
||||||
|
if let Some(controller) = self.controller.get() {
|
||||||
|
let webview = controller.get_webview().expect("Cannot get vebview");
|
||||||
|
webview.post_web_message_as_string(msg).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*msg = None;
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue