mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-05-18 15:11:53 +00:00
Improved communication with player
This commit is contained in:
parent
9cb8681d76
commit
e5a3264906
4 changed files with 148 additions and 120 deletions
|
|
@ -100,13 +100,7 @@ impl MainWindow {
|
||||||
// Read message from player
|
// Read message from player
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
let rx = player_rx.lock().unwrap();
|
let rx = player_rx.lock().unwrap();
|
||||||
if let Ok(msg) = rx.recv() {
|
rx.iter().map(|msg| web_tx_player.send(msg)).for_each(drop);
|
||||||
web_tx_player
|
|
||||||
.send(RPCResponse::response_message(
|
|
||||||
serde_json::from_str(&msg).ok(),
|
|
||||||
))
|
|
||||||
.ok();
|
|
||||||
} // recv
|
|
||||||
}); // thread
|
}); // thread
|
||||||
|
|
||||||
let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender();
|
let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender();
|
||||||
|
|
@ -157,7 +151,6 @@ impl MainWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(player_command) if player_command.starts_with("mpv-") => {
|
Some(player_command) if player_command.starts_with("mpv-") => {
|
||||||
// FIXME: filter out the run command
|
|
||||||
let resp_json = serde_json::to_string(
|
let resp_json = serde_json::to_string(
|
||||||
&msg.args.expect("Cannot have method without args"),
|
&msg.args.expect("Cannot have method without args"),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
75
src/stremio_app/stremio_player/communication.rs
Normal file
75
src/stremio_app/stremio_player/communication.rs
Normal file
|
|
@ -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::Value> {
|
||||||
|
serde_json::to_value(self).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub use player::Player;
|
pub use player::Player;
|
||||||
|
pub mod communication;
|
||||||
|
pub use communication::{PlayerEnded, PlayerError, PlayerEvent, PlayerProprChange, PlayerResponse};
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,15 @@
|
||||||
use crate::stremio_app::ipc;
|
use crate::stremio_app::ipc;
|
||||||
|
use crate::stremio_app::RPCResponse;
|
||||||
use native_windows_gui::{self as nwg, PartialUi};
|
use native_windows_gui::{self as nwg, PartialUi};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::VecDeque;
|
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;
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
use crate::stremio_app::stremio_player::communication::{
|
||||||
pub struct MpvEvent {
|
PlayerEnded, PlayerEvent, PlayerProprChange, PlayerResponse,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
};
|
||||||
error: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
name: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
data: Option<serde_json::Value>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Player {
|
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
|
// even if you don't do anything with the events, it is still necessary to empty
|
||||||
// the event loop
|
// the event loop
|
||||||
|
|
||||||
let json_responses = ["track-list", "video-params", "metadata"];
|
|
||||||
let resp_event = match event {
|
let resp_event = match event {
|
||||||
mpv::Event::PropertyChange {
|
mpv::Event::PropertyChange {
|
||||||
name,
|
name,
|
||||||
change,
|
change,
|
||||||
reply_userdata: _,
|
reply_userdata: _,
|
||||||
} => Some((
|
} => PlayerResponse(
|
||||||
"mpv-prop-change",
|
"mpv-prop-change",
|
||||||
MpvEvent {
|
PlayerEvent::PropChange(PlayerProprChange::from_name_value(
|
||||||
name: Some(name.to_string()),
|
name.to_string(),
|
||||||
data: Some(MpvEvent::value_from_format(
|
change,
|
||||||
change,
|
)),
|
||||||
json_responses.contains(&name),
|
)
|
||||||
)),
|
.to_value(),
|
||||||
..Default::default()
|
mpv::Event::EndFile(Ok(reason)) => PlayerResponse(
|
||||||
},
|
|
||||||
)),
|
|
||||||
mpv::Event::EndFile(Ok(reason)) => Some((
|
|
||||||
"mpv-event-ended",
|
"mpv-event-ended",
|
||||||
MpvEvent {
|
PlayerEvent::End(PlayerEnded::from_end_reason(reason)),
|
||||||
reason: Some(MpvEvent::string_from_end_reason(reason)),
|
)
|
||||||
..Default::default()
|
.to_value(),
|
||||||
},
|
|
||||||
)),
|
|
||||||
mpv::Event::Shutdown => {
|
mpv::Event::Shutdown => {
|
||||||
break 'main;
|
break 'main;
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(resp) = resp_event {
|
if resp_event.is_some() {
|
||||||
tx1.send(
|
tx1.send(RPCResponse::response_message(resp_event)).ok();
|
||||||
serde_json::to_string(&resp).expect("Cannot generate MPV event JSON"),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
}
|
}
|
||||||
} // event processing
|
} // event processing
|
||||||
|
|
||||||
|
|
@ -157,57 +111,58 @@ impl PartialUi for Player {
|
||||||
serde_json::from_str(msg.as_str()).unwrap();
|
serde_json::from_str(msg.as_str()).unwrap();
|
||||||
match command.as_str() {
|
match command.as_str() {
|
||||||
"mpv-observe-prop" => {
|
"mpv-observe-prop" => {
|
||||||
if let Some(property) = data.as_str() {
|
let property = data.as_str().unwrap_or_default();
|
||||||
match property {
|
match property {
|
||||||
"pause" | "paused-for-cache" | "seeking" | "eof-reached" => {
|
"pause" | "paused-for-cache" | "seeking" | "eof-reached" => {
|
||||||
mpv.observe_property::<bool>(property, 0).ok();
|
mpv.observe_property::<bool>(property, 0).ok();
|
||||||
}
|
}
|
||||||
"aid" | "vid" | "sid" => {
|
"aid" | "vid" | "sid" => {
|
||||||
mpv.observe_property::<i64>(property, 0).ok();
|
mpv.observe_property::<i64>(property, 0).ok();
|
||||||
}
|
}
|
||||||
"time-pos"
|
"time-pos"
|
||||||
| "volume"
|
| "volume"
|
||||||
| "duration"
|
| "duration"
|
||||||
| "sub-scale"
|
| "sub-scale"
|
||||||
| "cache-buffering-state"
|
| "cache-buffering-state"
|
||||||
| "sub-pos" => {
|
| "sub-pos" => {
|
||||||
mpv.observe_property::<f64>(property, 0).ok();
|
mpv.observe_property::<f64>(property, 0).ok();
|
||||||
}
|
}
|
||||||
"path" | "mpv-version" | "ffmpeg-version" | "track-list"
|
"path" | "mpv-version" | "ffmpeg-version" | "track-list"
|
||||||
| "video-params" | "metadata" => {
|
| "video-params" | "metadata" => {
|
||||||
mpv.observe_property::<&str>(property, 0).ok();
|
mpv.observe_property::<&str>(property, 0).ok();
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
eprintln!(
|
eprintln!("mpv-observe-prop: not implemented for `{}`", other);
|
||||||
"mpv-observe-prop: not implemented for `{}`",
|
}
|
||||||
other
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"mpv-set-prop" => {
|
"mpv-set-prop" => {
|
||||||
match serde_json::from_value::<Vec<serde_json::Value>>(data.clone()) {
|
match serde_json::from_value::<Vec<serde_json::Value>>(data.clone()) {
|
||||||
Ok(prop_vector) => {
|
Ok(prop_vector) if prop_vector.len() == 2 => {
|
||||||
if let [prop, val] = &prop_vector[..] {
|
let prop =
|
||||||
let prop = prop.as_str().expect("Property is not a string");
|
prop_vector[0].as_str().expect("Property is not a string");
|
||||||
// If we change vo MPV panics
|
let val = prop_vector[1].clone();
|
||||||
if prop != "vo" {
|
// If we change vo MPV panics
|
||||||
match val {
|
if prop != "vo" {
|
||||||
serde_json::Value::Bool(v) => {
|
match val {
|
||||||
mpv.set_property(prop, *v).ok();
|
serde_json::Value::Bool(v) => {
|
||||||
}
|
mpv.set_property(prop, v).ok();
|
||||||
serde_json::Value::Number(v) => {
|
}
|
||||||
mpv.set_property(prop, v.as_f64().unwrap())
|
serde_json::Value::Number(v) => {
|
||||||
.ok();
|
mpv.set_property(prop, v.as_f64().unwrap()).ok();
|
||||||
}
|
}
|
||||||
serde_json::Value::String(v) => {
|
serde_json::Value::String(v) => {
|
||||||
mpv.set_property(prop, v.as_str()).ok();
|
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) => {
|
Err(e) => {
|
||||||
eprintln!("mpv-set-prop Error: {:?} for data {}", e, data)
|
eprintln!("mpv-set-prop Error: {:?} for data {}", e, data)
|
||||||
|
|
@ -216,10 +171,13 @@ impl PartialUi for Player {
|
||||||
}
|
}
|
||||||
"mpv-command" => {
|
"mpv-command" => {
|
||||||
match serde_json::from_value::<Vec<String>>(data.clone()) {
|
match serde_json::from_value::<Vec<String>>(data.clone()) {
|
||||||
Ok(data) => {
|
Ok(data) if data.len() > 0 => {
|
||||||
let data: Vec<_> = data.iter().map(|s| s.as_str()).collect();
|
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) => {
|
Err(e) => {
|
||||||
eprintln!("mpv-command Error: {:?} for data {}", e, data)
|
eprintln!("mpv-command Error: {:?} for data {}", e, data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue