new
This commit is contained in:
@@ -54,6 +54,7 @@ pub struct PlayerController {
|
||||
status_tx: tokio::sync::watch::Sender<PlayerStatus>,
|
||||
status_rx: tokio::sync::watch::Receiver<PlayerStatus>,
|
||||
pub window_id: Mutex<Option<u64>>,
|
||||
pub ipc_tx: Mutex<Option<tokio::sync::mpsc::UnboundedSender<serde_json::Value>>>,
|
||||
}
|
||||
|
||||
impl PlayerController {
|
||||
@@ -67,6 +68,7 @@ impl PlayerController {
|
||||
status_tx,
|
||||
status_rx,
|
||||
window_id: Mutex::new(None),
|
||||
ipc_tx: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -139,6 +141,13 @@ impl PlayerController {
|
||||
cmd.arg(format!("--input-ipc-server={}", self.config.ipc_socket));
|
||||
cmd.arg(format!("--hwdec={}", self.config.hwdec));
|
||||
cmd.arg(format!("--vo={}", self.config.vo));
|
||||
cmd.arg("--osd-bar=yes");
|
||||
cmd.arg("--osd-level=1");
|
||||
cmd.arg("--osd-font-size=28");
|
||||
cmd.arg("--osd-on-seek=msg-bar");
|
||||
cmd.arg("--input-vo-keyboard=no");
|
||||
cmd.arg("--input-cursor=no");
|
||||
cmd.arg("--input-cursor-passthrough=yes");
|
||||
|
||||
// Configure yt-dlp for YouTube streams
|
||||
let ytdlp_path = if std::path::Path::new("/home/bart/.local/bin/yt-dlp").exists() {
|
||||
@@ -189,12 +198,19 @@ impl PlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
// Create persistent IPC command channel
|
||||
let (ipc_tx, ipc_rx) = tokio::sync::mpsc::unbounded_channel::<serde_json::Value>();
|
||||
{
|
||||
let mut guard = self.ipc_tx.lock().await;
|
||||
*guard = Some(ipc_tx);
|
||||
}
|
||||
|
||||
// Start background IPC monitoring task
|
||||
let sock_file = self.config.ipc_socket.clone();
|
||||
let status_arc = self.status.clone();
|
||||
let status_tx = self.status_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = monitor_mpv_ipc(sock_file, status_arc, status_tx).await;
|
||||
let _ = monitor_mpv_ipc(sock_file, status_arc, status_tx, ipc_rx).await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -212,7 +228,7 @@ impl PlayerController {
|
||||
PlaybackState::Stopped => PlaybackState::Stopped,
|
||||
};
|
||||
|
||||
let _ = self.send_ipc_cmd(&["cycle", "pause"]).await;
|
||||
let _ = self.send_ipc_cmd(&["osd-msg", "cycle", "pause"]).await;
|
||||
{
|
||||
let mut st = self.status.lock().await;
|
||||
st.state = new_state;
|
||||
@@ -222,6 +238,10 @@ impl PlayerController {
|
||||
|
||||
pub async fn stop(&self) {
|
||||
let _ = self.send_ipc_cmd(&["stop"]).await;
|
||||
{
|
||||
let mut guard = self.ipc_tx.lock().await;
|
||||
*guard = None;
|
||||
}
|
||||
let mut proc_guard = self.child_process.lock().await;
|
||||
if let Some(mut child) = proc_guard.take() {
|
||||
let _ = child.kill();
|
||||
@@ -241,7 +261,17 @@ impl PlayerController {
|
||||
}
|
||||
|
||||
pub async fn seek(&self, seconds: f64) {
|
||||
let _ = self.send_ipc_cmd(&["seek", &seconds.to_string(), "relative"]).await;
|
||||
let _ = self.send_ipc_cmd(&["osd-msg-bar", "seek", &seconds.to_string(), "relative"]).await;
|
||||
}
|
||||
|
||||
pub async fn seek_percent(&self, percent: f64) {
|
||||
let p = percent.clamp(0.0, 100.0);
|
||||
let _ = self.send_ipc_cmd(&["osd-msg-bar", "seek", &p.to_string(), "absolute-percent"]).await;
|
||||
}
|
||||
|
||||
pub async fn seek_time(&self, seconds: f64) {
|
||||
let s = seconds.max(0.0);
|
||||
let _ = self.send_ipc_cmd(&["osd-msg-bar", "seek", &s.to_string(), "absolute"]).await;
|
||||
}
|
||||
|
||||
pub async fn toggle_mute(&self) {
|
||||
@@ -272,7 +302,7 @@ impl PlayerController {
|
||||
let mut st = self.status.lock().await;
|
||||
st.volume = vol;
|
||||
}
|
||||
let _ = self.send_ipc_cmd(&["set", "volume", &vol.to_string()]).await;
|
||||
let _ = self.send_ipc_cmd(&["osd-msg-bar", "set", "volume", &vol.to_string()]).await;
|
||||
self.notify_status().await;
|
||||
tracing::info!("Player volume set to {}", vol);
|
||||
}
|
||||
@@ -283,7 +313,7 @@ impl PlayerController {
|
||||
st.volume = (st.volume + 5.0).min(100.0);
|
||||
st.volume
|
||||
};
|
||||
let _ = self.send_ipc_cmd(&["add", "volume", "5"]).await;
|
||||
let _ = self.send_ipc_cmd(&["osd-msg-bar", "add", "volume", "5"]).await;
|
||||
self.notify_status().await;
|
||||
tracing::info!("Player volume increased to {}", new_vol);
|
||||
}
|
||||
@@ -294,28 +324,49 @@ impl PlayerController {
|
||||
st.volume = (st.volume - 5.0).max(0.0);
|
||||
st.volume
|
||||
};
|
||||
let _ = self.send_ipc_cmd(&["add", "volume", "-5"]).await;
|
||||
let _ = self.send_ipc_cmd(&["osd-msg-bar", "add", "volume", "-5"]).await;
|
||||
self.notify_status().await;
|
||||
tracing::info!("Player volume decreased to {}", new_vol);
|
||||
}
|
||||
|
||||
pub async fn cycle_subtitle(&self) {
|
||||
let _ = self.send_ipc_cmd(&["cycle", "sub"]).await;
|
||||
let _ = self.send_ipc_cmd(&[
|
||||
"expand-properties",
|
||||
"show-text",
|
||||
"💬 Ondertitels: ${current-tracks/sub/title:${current-tracks/sub/lang:${sub:Geen beschikbaar}}}",
|
||||
"2500",
|
||||
]).await;
|
||||
tracing::info!("Cycled subtitle track");
|
||||
}
|
||||
|
||||
pub async fn cycle_audio(&self) {
|
||||
let _ = self.send_ipc_cmd(&["cycle", "audio"]).await;
|
||||
let _ = self.send_ipc_cmd(&[
|
||||
"expand-properties",
|
||||
"show-text",
|
||||
"🎬 Audio: ${current-tracks/audio/title:${current-tracks/audio/lang:${audio:Standaard}}}",
|
||||
"2500",
|
||||
]).await;
|
||||
tracing::info!("Cycled audio track");
|
||||
}
|
||||
|
||||
pub async fn set_video_visible(&self, visible: bool) {
|
||||
let arg = if visible { "auto" } else { "no" };
|
||||
let _ = self.send_ipc_cmd(&["set", "vid", arg]).await;
|
||||
tracing::info!("Set MPV video visibility to {}", visible);
|
||||
}
|
||||
|
||||
async fn send_ipc_cmd(&self, args: &[&str]) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut stream = UnixStream::connect(&self.config.ipc_socket).await?;
|
||||
let cmd_json = serde_json::json!({
|
||||
"command": args
|
||||
});
|
||||
stream.write_all(format!("{}\n", cmd_json).as_bytes()).await?;
|
||||
Ok(())
|
||||
let guard = self.ipc_tx.lock().await;
|
||||
if let Some(ref tx) = *guard {
|
||||
let cmd_json = serde_json::json!({
|
||||
"command": args
|
||||
});
|
||||
let _ = tx.send(cmd_json);
|
||||
return Ok(());
|
||||
}
|
||||
Err("MPV IPC not connected".into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +374,7 @@ async fn monitor_mpv_ipc(
|
||||
sock_path: String,
|
||||
status: Arc<Mutex<PlayerStatus>>,
|
||||
status_tx: tokio::sync::watch::Sender<PlayerStatus>,
|
||||
mut ipc_rx: tokio::sync::mpsc::UnboundedReceiver<serde_json::Value>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Wait for socket to become available
|
||||
for _ in 0..30 {
|
||||
@@ -356,7 +408,23 @@ async fn monitor_mpv_ipc(
|
||||
let _ = writer.write_all(format!("{}\n", c).as_bytes()).await;
|
||||
}
|
||||
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
loop {
|
||||
tokio::select! {
|
||||
cmd_opt = ipc_rx.recv() => {
|
||||
match cmd_opt {
|
||||
Some(cmd_val) => {
|
||||
let payload = format!("{}\n", cmd_val);
|
||||
if let Err(e) = writer.write_all(payload.as_bytes()).await {
|
||||
tracing::warn!("MPV IPC write error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
line_res = lines.next_line() => {
|
||||
match line_res {
|
||||
Ok(Some(line)) => {
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&line) {
|
||||
if let Some(event) = v.get("event").and_then(|e| e.as_str()) {
|
||||
if event == "property-change" {
|
||||
@@ -487,6 +555,11 @@ async fn monitor_mpv_ipc(
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user