diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index 2edcdc0..3598e0e 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -100,13 +100,7 @@ impl MainWindow { // Read message from player thread::spawn(move || loop { let rx = player_rx.lock().unwrap(); - if let Ok(msg) = rx.recv() { - web_tx_player - .send(RPCResponse::response_message( - serde_json::from_str(&msg).ok(), - )) - .ok(); - } // recv + rx.iter().map(|msg| web_tx_player.send(msg)).for_each(drop); }); // thread let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender(); @@ -157,7 +151,6 @@ impl MainWindow { } } Some(player_command) if player_command.starts_with("mpv-") => { - // FIXME: filter out the run command let resp_json = serde_json::to_string( &msg.args.expect("Cannot have method without args"), ) diff --git a/src/stremio_app/stremio_player/communication.rs b/src/stremio_app/stremio_player/communication.rs new file mode 100644 index 0000000..f6397dc --- /dev/null +++ b/src/stremio_app/stremio_player/communication.rs @@ -0,0 +1,75 @@ +use serde::{Deserialize, Serialize}; + +const JSON_RESPONSES: [&str; 3] = ["track-list", "video-params", "metadata"]; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PlayerProprChange { + name: String, + data: serde_json::Value, +} +impl PlayerProprChange { + fn value_from_format(data: mpv::Format, 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( + serde_json::Number::from_f64(d as f64).expect("MPV returned invalid number"), + ), + mpv::Format::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) => { + if as_json { + serde_json::from_str(s).expect("MPV returned invalid JSON data") + } else { + serde_json::Value::String(s.to_string()) + } + } + } + } + pub fn from_name_value(name: String, value: mpv::Format) -> Self { + let is_json = JSON_RESPONSES.contains(&name.as_str()); + Self { + name, + data: Self::value_from_format(value, is_json), + } + } +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PlayerEnded { + reason: String, +} +impl PlayerEnded { + fn string_from_end_reason(data: mpv::EndFileReason) -> String { + match data { + mpv::EndFileReason::MPV_END_FILE_REASON_ERROR => "error".to_string(), + mpv::EndFileReason::MPV_END_FILE_REASON_QUIT => "quit".to_string(), + _ => "other".to_string(), + } + } + pub fn from_end_reason(data: mpv::EndFileReason) -> Self { + Self { + reason: Self::string_from_end_reason(data), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PlayerError { + pub error: String, +} +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum PlayerEvent { + PropChange(PlayerProprChange), + End(PlayerEnded), + Error(PlayerError), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PlayerResponse<'a>(pub &'a str, pub PlayerEvent); +impl PlayerResponse<'_> { + pub fn to_value(&self) -> Option { + serde_json::to_value(self).ok() + } +} diff --git a/src/stremio_app/stremio_player/mod.rs b/src/stremio_app/stremio_player/mod.rs index c85c7ce..64f56d9 100644 --- a/src/stremio_app/stremio_player/mod.rs +++ b/src/stremio_app/stremio_player/mod.rs @@ -1,2 +1,4 @@ pub mod player; pub use player::Player; +pub mod communication; +pub use communication::{PlayerEnded, PlayerError, PlayerEvent, PlayerProprChange, PlayerResponse}; diff --git a/src/stremio_app/stremio_player/player.rs b/src/stremio_app/stremio_player/player.rs index 7112d9b..bff40d6 100644 --- a/src/stremio_app/stremio_player/player.rs +++ b/src/stremio_app/stremio_player/player.rs @@ -1,52 +1,15 @@ use crate::stremio_app::ipc; +use crate::stremio_app::RPCResponse; use native_windows_gui::{self as nwg, PartialUi}; -use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::collections::VecDeque; use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::thread; -#[derive(Default, Serialize, Deserialize, Debug, Clone)] -pub struct MpvEvent { - #[serde(skip_serializing_if = "Option::is_none")] - error: Option, - #[serde(skip_serializing_if = "Option::is_none")] - name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - reason: Option, -} - -impl MpvEvent { - fn value_from_format(data: mpv::Format, 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( - serde_json::Number::from_f64(d as f64).expect("MPV returned invalid number"), - ), - mpv::Format::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) => { - if as_json { - serde_json::from_str(s).expect("MPV returned invalid JSON data") - } else { - serde_json::Value::String(s.to_string()) - } - } - } - } - fn string_from_end_reason(data: mpv::EndFileReason) -> String { - match data { - mpv::EndFileReason::MPV_END_FILE_REASON_ERROR => "error".to_string(), - mpv::EndFileReason::MPV_END_FILE_REASON_QUIT => "quit".to_string(), - _ => "other".to_string(), - } - } -} +use crate::stremio_app::stremio_player::communication::{ + PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse, +}; #[derive(Default)] pub struct Player { @@ -113,40 +76,31 @@ impl PartialUi for Player { // even if you don't do anything with the events, it is still necessary to empty // the event loop - let json_responses = ["track-list", "video-params", "metadata"]; let resp_event = match event { mpv::Event::PropertyChange { name, change, reply_userdata: _, - } => Some(( + } => PlayerResponse( "mpv-prop-change", - MpvEvent { - name: Some(name.to_string()), - data: Some(MpvEvent::value_from_format( - change, - json_responses.contains(&name), - )), - ..Default::default() - }, - )), - mpv::Event::EndFile(Ok(reason)) => Some(( + PlayerEvent::PropChange(PlayerProprChange::from_name_value( + name.to_string(), + change, + )), + ) + .to_value(), + mpv::Event::EndFile(Ok(reason)) => PlayerResponse( "mpv-event-ended", - MpvEvent { - reason: Some(MpvEvent::string_from_end_reason(reason)), - ..Default::default() - }, - )), + PlayerEvent::End(PlayerEnded::from_end_reason(reason)), + ) + .to_value(), mpv::Event::Shutdown => { break 'main; } _ => None, }; - if let Some(resp) = resp_event { - tx1.send( - serde_json::to_string(&resp).expect("Cannot generate MPV event JSON"), - ) - .ok(); + if resp_event.is_some() { + tx1.send(RPCResponse::response_message(resp_event)).ok(); } } // event processing @@ -157,57 +111,58 @@ impl PartialUi for Player { serde_json::from_str(msg.as_str()).unwrap(); match command.as_str() { "mpv-observe-prop" => { - if let Some(property) = data.as_str() { - match property { - "pause" | "paused-for-cache" | "seeking" | "eof-reached" => { - mpv.observe_property::(property, 0).ok(); - } - "aid" | "vid" | "sid" => { - mpv.observe_property::(property, 0).ok(); - } - "time-pos" - | "volume" - | "duration" - | "sub-scale" - | "cache-buffering-state" - | "sub-pos" => { - mpv.observe_property::(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 - ); - } - }; - } + let property = data.as_str().unwrap_or_default(); + match property { + "pause" | "paused-for-cache" | "seeking" | "eof-reached" => { + mpv.observe_property::(property, 0).ok(); + } + "aid" | "vid" | "sid" => { + mpv.observe_property::(property, 0).ok(); + } + "time-pos" + | "volume" + | "duration" + | "sub-scale" + | "cache-buffering-state" + | "sub-pos" => { + mpv.observe_property::(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); + } + }; } "mpv-set-prop" => { match serde_json::from_value::>(data.clone()) { - Ok(prop_vector) => { - if let [prop, val] = &prop_vector[..] { - let prop = prop.as_str().expect("Property is not a string"); - // 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(); - } - _ => {} - }; + 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) @@ -216,10 +171,13 @@ impl PartialUi for Player { } "mpv-command" => { match serde_json::from_value::>(data.clone()) { - Ok(data) => { + Ok(data) if data.len() > 0 => { let data: Vec<_> = data.iter().map(|s| s.as_str()).collect(); - mpv.command(&data).ok(); + if data[0] != "run" { + mpv.command(&data).ok(); + } } + Ok(_) => {} Err(e) => { eprintln!("mpv-command Error: {:?} for data {}", e, data) }