mirror of
https://github.com/NotAShelf/mpvrc.git
synced 2026-04-17 08:19:51 +00:00
move generic functions into lib target
This commit is contained in:
parent
21b7df991b
commit
d30a19a7c9
2 changed files with 154 additions and 31 deletions
136
src/lib.rs
136
src/lib.rs
|
|
@ -1,4 +1,4 @@
|
||||||
use serde_json::{Value, json};
|
use serde_json::{json, Value};
|
||||||
use std::io::{self};
|
use std::io::{self};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
|
|
@ -6,20 +6,26 @@ use tracing::{debug, error};
|
||||||
|
|
||||||
pub const SOCKET_PATH: &str = "/tmp/mpvsocket";
|
pub const SOCKET_PATH: &str = "/tmp/mpvsocket";
|
||||||
|
|
||||||
/// Sends an IPC command to the MPV socket and returns the parsed response data.
|
/// Sends a generic IPC command to the specified socket and returns the parsed response data.
|
||||||
pub async fn send_ipc_command(command: &str, args: &[Value]) -> io::Result<Option<Value>> {
|
///
|
||||||
|
/// If a socket path is not provided, it will fall back to the example path of `/tmp/mpvsocket`
|
||||||
|
pub async fn send_ipc_command(
|
||||||
|
command: &str,
|
||||||
|
args: &[Value],
|
||||||
|
socket_path: Option<&str>,
|
||||||
|
) -> io::Result<Option<Value>> {
|
||||||
|
let socket_path = socket_path.unwrap_or(SOCKET_PATH);
|
||||||
debug!(
|
debug!(
|
||||||
"Sending IPC command: {} with arguments: {:?}",
|
"Sending IPC command: {} with arguments: {:?}",
|
||||||
command, args
|
command, args
|
||||||
);
|
);
|
||||||
|
|
||||||
match UnixStream::connect(SOCKET_PATH).await {
|
match UnixStream::connect(socket_path).await {
|
||||||
Ok(mut socket) => {
|
Ok(mut socket) => {
|
||||||
debug!("Found MPV socket at {}", SOCKET_PATH);
|
debug!("Connected to socket at {}", socket_path);
|
||||||
|
|
||||||
let mut command_array = vec![json!(command)];
|
let mut command_array = vec![json!(command)];
|
||||||
command_array.extend_from_slice(args);
|
command_array.extend_from_slice(args);
|
||||||
|
|
||||||
let message = json!({ "command": command_array });
|
let message = json!({ "command": command_array });
|
||||||
let message_str = format!("{}\n", serde_json::to_string(&message)?);
|
let message_str = format!("{}\n", serde_json::to_string(&message)?);
|
||||||
debug!("Serialized message to send with newline: {}", message_str);
|
debug!("Serialized message to send with newline: {}", message_str);
|
||||||
|
|
@ -52,3 +58,121 @@ pub async fn send_ipc_command(command: &str, args: &[Value]) -> io::Result<Optio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Common MPV commands
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MpvCommand {
|
||||||
|
SetProperty,
|
||||||
|
PlaylistNext,
|
||||||
|
PlaylistPrev,
|
||||||
|
Seek,
|
||||||
|
Quit,
|
||||||
|
PlaylistMove,
|
||||||
|
PlaylistRemove,
|
||||||
|
PlaylistClear,
|
||||||
|
GetProperty,
|
||||||
|
LoadFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send any generic command to the MPV IPC socket.
|
||||||
|
pub async fn set_property(
|
||||||
|
property: &str,
|
||||||
|
value: &Value,
|
||||||
|
socket_path: Option<&str>,
|
||||||
|
) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(
|
||||||
|
MpvCommand::SetProperty.as_str(),
|
||||||
|
&[json!(property), value.clone()],
|
||||||
|
socket_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn playlist_next(socket_path: Option<&str>) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(MpvCommand::PlaylistNext.as_str(), &[], socket_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn playlist_prev(socket_path: Option<&str>) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(MpvCommand::PlaylistPrev.as_str(), &[], socket_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn seek(seconds: f64, socket_path: Option<&str>) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(MpvCommand::Seek.as_str(), &[json!(seconds)], socket_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn quit(socket_path: Option<&str>) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(MpvCommand::Quit.as_str(), &[], socket_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn playlist_move(
|
||||||
|
from_index: usize,
|
||||||
|
to_index: usize,
|
||||||
|
socket_path: Option<&str>,
|
||||||
|
) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(
|
||||||
|
MpvCommand::PlaylistMove.as_str(),
|
||||||
|
&[json!(from_index), json!(to_index)],
|
||||||
|
socket_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn playlist_remove(
|
||||||
|
index: Option<usize>,
|
||||||
|
socket_path: Option<&str>,
|
||||||
|
) -> io::Result<Option<Value>> {
|
||||||
|
let args = match index {
|
||||||
|
Some(idx) => vec![json!(idx)],
|
||||||
|
None => vec![json!("current")],
|
||||||
|
};
|
||||||
|
send_ipc_command(MpvCommand::PlaylistRemove.as_str(), &args, socket_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn playlist_clear(socket_path: Option<&str>) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(MpvCommand::PlaylistClear.as_str(), &[], socket_path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_property(property: &str, socket_path: Option<&str>) -> io::Result<Option<Value>> {
|
||||||
|
send_ipc_command(
|
||||||
|
MpvCommand::GetProperty.as_str(),
|
||||||
|
&[json!(property)],
|
||||||
|
socket_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn loadfile(
|
||||||
|
filename: &str,
|
||||||
|
append: bool,
|
||||||
|
socket_path: Option<&str>,
|
||||||
|
) -> io::Result<Option<Value>> {
|
||||||
|
let append_flag = if append {
|
||||||
|
json!("append-play")
|
||||||
|
} else {
|
||||||
|
json!("replace")
|
||||||
|
};
|
||||||
|
send_ipc_command(
|
||||||
|
MpvCommand::LoadFile.as_str(),
|
||||||
|
&[json!(filename), append_flag],
|
||||||
|
socket_path,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MpvCommand {
|
||||||
|
// Convert commands to their string equivalents
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
MpvCommand::SetProperty => "set_property",
|
||||||
|
MpvCommand::PlaylistNext => "playlist-next",
|
||||||
|
MpvCommand::PlaylistPrev => "playlist-prev",
|
||||||
|
MpvCommand::Seek => "seek",
|
||||||
|
MpvCommand::Quit => "quit",
|
||||||
|
MpvCommand::PlaylistMove => "playlist-move",
|
||||||
|
MpvCommand::PlaylistRemove => "playlist-remove",
|
||||||
|
MpvCommand::PlaylistClear => "playlist-clear",
|
||||||
|
MpvCommand::GetProperty => "get_property",
|
||||||
|
MpvCommand::LoadFile => "loadfile",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
49
src/main.rs
49
src/main.rs
|
|
@ -1,13 +1,15 @@
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use serde_json::{Value, json};
|
use serde_json::json;
|
||||||
use std::io::{self};
|
use std::io::{self};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
// TODO: SOCKET_PATH should be an argument to send_ipc_command
|
use mrc::set_property;
|
||||||
// and in the CLI, it should be expected as a command line argument
|
use mrc::SOCKET_PATH;
|
||||||
// or/and environment variable.
|
use mrc::{
|
||||||
use mrc::{SOCKET_PATH, send_ipc_command};
|
get_property, loadfile, playlist_clear, playlist_move, playlist_next, playlist_prev,
|
||||||
|
playlist_remove, quit, seek,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
|
|
@ -89,10 +91,6 @@ enum CommandOptions {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_command(command: &str, args: &[Value]) -> io::Result<Option<Value>> {
|
|
||||||
send_ipc_command(command, args).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
@ -108,60 +106,61 @@ async fn main() -> io::Result<()> {
|
||||||
CommandOptions::Play { index } => {
|
CommandOptions::Play { index } => {
|
||||||
if let Some(idx) = index {
|
if let Some(idx) = index {
|
||||||
info!("Playing media at index: {}", idx);
|
info!("Playing media at index: {}", idx);
|
||||||
execute_command("set_property", &[json!("playlist-pos"), json!(idx)]).await?;
|
set_property("playlist-pos", &json!(idx), None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Unpausing playback");
|
info!("Unpausing playback");
|
||||||
execute_command("set_property", &[json!("pause"), json!(false)]).await?;
|
set_property("pause", &json!(false), None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Pause => {
|
CommandOptions::Pause => {
|
||||||
info!("Pausing playback");
|
info!("Pausing playback");
|
||||||
execute_command("set_property", &[json!("pause"), json!(true)]).await?;
|
set_property("pause", &json!(true), None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Stop => {
|
CommandOptions::Stop => {
|
||||||
info!("Stopping playback and quitting MPV");
|
info!("Stopping playback and quitting MPV");
|
||||||
execute_command("quit", &[]).await?;
|
quit(None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Next => {
|
CommandOptions::Next => {
|
||||||
info!("Skipping to next item in the playlist");
|
info!("Skipping to next item in the playlist");
|
||||||
execute_command("playlist-next", &[]).await?;
|
playlist_next(None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Prev => {
|
CommandOptions::Prev => {
|
||||||
info!("Skipping to previous item in the playlist");
|
info!("Skipping to previous item in the playlist");
|
||||||
execute_command("playlist-prev", &[]).await?;
|
playlist_prev(None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Seek { seconds } => {
|
CommandOptions::Seek { seconds } => {
|
||||||
info!("Seeking to {} seconds", seconds);
|
info!("Seeking to {} seconds", seconds);
|
||||||
execute_command("seek", &[json!(seconds)]).await?;
|
seek(seconds.into(), None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Move { index1, index2 } => {
|
CommandOptions::Move { index1, index2 } => {
|
||||||
info!("Moving item from index {} to {}", index1, index2);
|
info!("Moving item from index {} to {}", index1, index2);
|
||||||
execute_command("playlist-move", &[json!(index1), json!(index2)]).await?;
|
playlist_move(index1, index2, None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Remove { index } => {
|
CommandOptions::Remove { index } => {
|
||||||
if let Some(idx) = index {
|
if let Some(idx) = index {
|
||||||
info!("Removing item at index {}", idx);
|
info!("Removing item at index {}", idx);
|
||||||
execute_command("playlist-remove", &[json!(idx)]).await?;
|
playlist_remove(Some(idx), None).await?;
|
||||||
} else {
|
} else {
|
||||||
info!("Removing current item from playlist");
|
info!("Removing current item from playlist");
|
||||||
execute_command("playlist-remove", &[json!("current")]).await?;
|
playlist_remove(None, None).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Clear => {
|
CommandOptions::Clear => {
|
||||||
info!("Clearing the playlist");
|
info!("Clearing the playlist");
|
||||||
execute_command("playlist-clear", &[]).await?;
|
playlist_clear(None).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::List => {
|
CommandOptions::List => {
|
||||||
info!("Listing playlist items");
|
info!("Listing playlist items");
|
||||||
if let Some(data) = send_ipc_command("get_property", &[json!("playlist")]).await? {
|
if let Some(data) = get_property("playlist", None).await? {
|
||||||
println!("{}", serde_json::to_string_pretty(&data)?);
|
println!("{}", serde_json::to_string_pretty(&data)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,16 +173,16 @@ async fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
info!("Adding {} files to the playlist", filenames.len());
|
info!("Adding {} files to the playlist", filenames.len());
|
||||||
for filename in filenames {
|
for filename in filenames {
|
||||||
execute_command("loadfile", &[json!(filename), json!("append-play")]).await?;
|
loadfile(&filename, true, None).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandOptions::Replace { filenames } => {
|
CommandOptions::Replace { filenames } => {
|
||||||
info!("Replacing current playlist with {} files", filenames.len());
|
info!("Replacing current playlist with {} files", filenames.len());
|
||||||
if let Some(first_file) = filenames.first() {
|
if let Some(first_file) = filenames.first() {
|
||||||
execute_command("loadfile", &[json!(first_file), json!("replace")]).await?;
|
loadfile(first_file, false, None).await?;
|
||||||
for filename in &filenames[1..] {
|
for filename in &filenames[1..] {
|
||||||
execute_command("loadfile", &[json!(filename), json!("append-play")]).await?;
|
loadfile(filename, true, None).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +190,7 @@ async fn main() -> io::Result<()> {
|
||||||
CommandOptions::Prop { properties } => {
|
CommandOptions::Prop { properties } => {
|
||||||
info!("Fetching properties: {:?}", properties);
|
info!("Fetching properties: {:?}", properties);
|
||||||
for property in properties {
|
for property in properties {
|
||||||
if let Some(data) = send_ipc_command("get_property", &[json!(property)]).await? {
|
if let Some(data) = get_property(&property, None).await? {
|
||||||
println!("{property}: {data}");
|
println!("{property}: {data}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue