From ea0e60097dcf56cede4837b9abc947f48d6abd82 Mon Sep 17 00:00:00 2001 From: Jonas Frei Date: Mon, 29 May 2017 17:54:12 +0200 Subject: [PATCH] Keeps the socket open for the entire lifetime of the Mpv struct. Now using channels to inform about new events (listen command). Added Readme --- Cargo.toml | 2 +- README.md | 45 ++++++++++++++ src/ipc.rs | 80 ++++++++++-------------- src/lib.rs | 178 ++++++++++++++++------------------------------------- 4 files changed, 134 insertions(+), 171 deletions(-) create mode 100644 README.md diff --git a/Cargo.toml b/Cargo.toml index ffad0db..3049108 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mpvipc" -version = "1.0.0" +version = "1.1.0" authors = ["Jonas Frei "] description = "A small library which provides bindings to control existing mpv instances through sockets." license = "GPL-3.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..a46a99c --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# mpvipc + +A small library which provides bindings to control existing mpv instances through sockets. + +To make use of this library, please make sure mpv is started with the following option: +` +$ mpv --input-ipc-server=/tmp/mpvsocket --idle ... +` + +## Dependencies + +- `mpv` +- `cargo` (makedep) + +## Install + +- [Cargo](https://crates.io/crates/mpvipc) + +You can use this package with cargo. + +## Example + +Make sure mpv is started with the following option: +` +$ mpv --input-ipc-server=/tmp/mpvsocket --idle +` + +Here is a small code example which connects to the socket /tmp/mpvsocket and toggles playback. + +``` +use mpvipc::*; +use std::sync::mpsc::channel; + +fn main() { + let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); + let paused: bool = mpv.get_property("pause").unwrap(); + mpv.set_property("pause", !paused).expect("Error pausing"); +} +``` + +For a more extensive example and proof of concept, see project [mpvc](https://github.com/freijon/mpvc-rs). + +## Bugs / Ideas + +Check out the [Issue Tracker](https://github.com/freijon/mpvipc/issues) \ No newline at end of file diff --git a/src/ipc.rs b/src/ipc.rs index 235b77d..a555c07 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -4,9 +4,8 @@ use std::error::Error; use std::io::BufReader; use std::io::prelude::*; use std::iter::Iterator; -use std::os::unix::net::UnixStream; -use std::net::Shutdown; use std::sync::mpsc::Sender; +use super::Mpv; #[derive(Debug)] pub struct PlaylistEntry { @@ -209,18 +208,18 @@ impl TypeHandler for Vec { } } -pub fn get_mpv_property(socket: &str, property: &str) -> Result { +pub fn get_mpv_property(instance: &Mpv, property: &str) -> Result { let ipc_string = format!("{{ \"command\": [\"get_property\",\"{}\"] }}\n", property); - match serde_json::from_str::(&send_command_sync(socket, &ipc_string)) { + match serde_json::from_str::(&send_command_sync(instance, &ipc_string)) { Ok(val) => T::get_value(val), Err(why) => Err(format!("Error while getting property: {}", why)), } } -pub fn get_mpv_property_string(socket: &str, property: &str) -> Result { +pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result { let ipc_string = format!("{{ \"command\": [\"get_property\",\"{}\"] }}\n", property); - match serde_json::from_str::(&send_command_sync(socket, &ipc_string)) { + match serde_json::from_str::(&send_command_sync(instance, &ipc_string)) { Ok(val) => { if let Value::Object(map) = val { if let Value::String(ref error) = map["error"] { @@ -247,20 +246,20 @@ pub fn get_mpv_property_string(socket: &str, property: &str) -> Result(socket: &str, +pub fn set_mpv_property(instance: &Mpv, property: &str, value: T) -> Result<(), String> { let ipc_string = format!("{{ \"command\": [\"set_property\", \"{}\", {}] }}\n", property, value.as_string()); - match serde_json::from_str::(&send_command_sync(socket, &ipc_string)) { + match serde_json::from_str::(&send_command_sync(instance, &ipc_string)) { Ok(_) => Ok(()), Err(why) => Err(why.description().to_string()), } } -pub fn run_mpv_command(socket: &str, command: &str, args: &Vec<&str>) -> Result<(), String> { +pub fn run_mpv_command(instance: &Mpv, command: &str, args: &Vec<&str>) -> Result<(), String> { let mut ipc_string = format!("{{ \"command\": [\"{}\"", command); if args.len() > 0 { for arg in args.iter() { @@ -269,7 +268,7 @@ pub fn run_mpv_command(socket: &str, command: &str, args: &Vec<&str>) -> Result< } ipc_string.push_str("] }\n"); ipc_string = ipc_string; - match serde_json::from_str::(&send_command_sync(socket, &ipc_string)) { + match serde_json::from_str::(&send_command_sync(instance, &ipc_string)) { Ok(feedback) => { if let Value::String(ref error) = feedback["error"] { if error == "success" { @@ -278,6 +277,7 @@ pub fn run_mpv_command(socket: &str, command: &str, args: &Vec<&str>) -> Result< Err(error.to_string()) } } else { + //Ok(()) Err("Error: Unexpected result received".to_string()) } } @@ -285,11 +285,11 @@ pub fn run_mpv_command(socket: &str, command: &str, args: &Vec<&str>) -> Result< } } -pub fn observe_mpv_property(socket: &str, id: &usize, property: &str) -> Result<(), String> { +pub fn observe_mpv_property(instance: &Mpv, id: &usize, property: &str) -> Result<(), String> { let ipc_string = format!("{{ \"command\": [\"observe_property\", {}, \"{}\"] }}\n", id, property); - match serde_json::from_str::(&send_command_sync(socket, &ipc_string)) { + match serde_json::from_str::(&send_command_sync(instance, &ipc_string)) { Ok(feedback) => { if let Value::String(ref error) = feedback["error"] { if error == "success" { @@ -314,47 +314,35 @@ pub fn observe_mpv_property(socket: &str, id: &usize, property: &str) -> Result< /// ``` /// listen("/tmp/mpvsocket"); /// ``` -pub fn listen(socket: &str, tx: &Sender) { - match UnixStream::connect(socket) { - Ok(stream) => { - let mut response = String::new(); - let mut reader = BufReader::new(&stream); - reader.read_line(&mut response).unwrap(); - match serde_json::from_str::(&response) { - Ok(e) => { - if let Value::String(ref name) = e["event"] { - tx.send(name.to_string()).unwrap(); - } - } - Err(why) => panic!("{}", why.description().to_string()), +pub fn listen(instance: &Mpv, tx: &Sender) { + let mut response = String::new(); + let mut reader = BufReader::new(instance); + reader.read_line(&mut response).unwrap(); + match serde_json::from_str::(&response) { + Ok(e) => { + if let Value::String(ref name) = e["event"] { + tx.send(name.to_string()).unwrap(); } - response.clear(); - stream - .shutdown(Shutdown::Both) - .expect("shutdown function failed"); } - Err(why) => panic!("Error: Could not connect to socket: {}", why.description()), + Err(why) => panic!("{}", why.description().to_string()), } + response.clear(); } -fn send_command_sync(socket: &str, command: &str) -> String { - match UnixStream::connect(socket) { - Ok(mut stream) => { - match stream.write_all(command.as_bytes()) { - Err(why) => panic!("Error: Could not write to socket: {}", why.description()), - Ok(_) => { - let mut response = String::new(); - { - let mut reader = BufReader::new(&stream); - reader.read_line(&mut response).unwrap(); - } - stream - .shutdown(Shutdown::Both) - .expect("shutdown function failed"); - response +fn send_command_sync(instance: &Mpv, command: &str) -> String { + let mut stream = instance; + match stream.write_all(command.as_bytes()) { + Err(why) => panic!("Error: Could not write to socket: {}", why.description()), + Ok(_) => { + let mut response = String::new(); + { + let mut reader = BufReader::new(stream); + while !response.contains("\"error\":") { + response.clear(); + reader.read_line(&mut response).unwrap(); } } + response } - Err(why) => panic!("Error: Could not connect to socket: {}", why.description()), } } diff --git a/src/lib.rs b/src/lib.rs index 90b29aa..2b602ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,10 @@ pub mod ipc; use ipc::*; use std::collections::HashMap; +use std::os::unix::net::UnixStream; use std::sync::mpsc::Sender; - -pub type Socket = String; +pub type Mpv = UnixStream; pub enum NumberChangeOptions { Absolute, @@ -35,159 +35,88 @@ pub enum Switch { Toggle, } -pub struct Playlist { - pub socket: Socket, - pub entries: Vec, +pub struct Playlist(pub Vec); + +pub trait MpvConnector { + fn connect(socket: &str) -> Result; +} + +impl MpvConnector for Mpv { + fn connect(socket: &str) -> Result { + match UnixStream::connect(socket) { + Ok(stream) => Ok(stream), + Err(msg) => Err(msg.to_string()), + } + } } pub trait GetPropertyTypeHandler: Sized { - fn get_property_generic(socket: &str, property: &str) -> Result; + fn get_property_generic(instance: &Mpv, property: &str) -> Result; } impl GetPropertyTypeHandler for bool { - fn get_property_generic(socket: &str, property: &str) -> Result { - get_mpv_property::(socket, property) + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) } } impl GetPropertyTypeHandler for String { - fn get_property_generic(socket: &str, property: &str) -> Result { - get_mpv_property::(socket, property) + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) } } impl GetPropertyTypeHandler for f64 { - fn get_property_generic(socket: &str, property: &str) -> Result { - get_mpv_property::(socket, property) + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) } } impl GetPropertyTypeHandler for usize { - fn get_property_generic(socket: &str, property: &str) -> Result { - get_mpv_property::(socket, property) + fn get_property_generic(instance: &Mpv, property: &str) -> Result { + get_mpv_property::(instance, property) } } impl GetPropertyTypeHandler for Vec { - fn get_property_generic(socket: &str, property: &str) -> Result, String> { - get_mpv_property::>(socket, property) + fn get_property_generic(instance: &Mpv, property: &str) -> Result, String> { + get_mpv_property::>(instance, property) } } impl GetPropertyTypeHandler for HashMap { - fn get_property_generic(socket: &str, + fn get_property_generic(instance: &Mpv, property: &str) -> Result, String> { - get_mpv_property::>(socket, property) + get_mpv_property::>(instance, property) } } pub trait SetPropertyTypeHandler { - fn set_property_generic(socket: &str, property: &str, value: T) -> Result<(), String>; + fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), String>; } impl SetPropertyTypeHandler for bool { - fn set_property_generic(socket: &str, property: &str, value: bool) -> Result<(), String> { - set_mpv_property::(socket, property, value) + fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), String> { + set_mpv_property::(instance, property, value) } } impl SetPropertyTypeHandler for String { - fn set_property_generic(socket: &str, property: &str, value: String) -> Result<(), String> { - set_mpv_property::(socket, property, value) + fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), String> { + set_mpv_property::(instance, property, value) } } impl SetPropertyTypeHandler for f64 { - fn set_property_generic(socket: &str, property: &str, value: f64) -> Result<(), String> { - set_mpv_property::(socket, property, value) + fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), String> { + set_mpv_property::(instance, property, value) } } impl SetPropertyTypeHandler for usize { - fn set_property_generic(socket: &str, property: &str, value: usize) -> Result<(), String> { - set_mpv_property::(socket, property, value) - } -} - -pub trait PlaylistHandler { - fn get_from(socket: Socket) -> Result; - fn shuffle(&mut self) -> &mut Playlist; - fn remove_id(&mut self, id: usize) -> &mut Playlist; - fn move_entry(&mut self, from: usize, to: usize) -> &mut Playlist; - fn current_id(&self) -> Option; -} - -impl PlaylistHandler for Playlist { - fn get_from(socket: Socket) -> Result { - match get_mpv_property(&socket, "playlist") { - Ok(playlist) => { - Ok(Playlist { - socket: socket, - entries: playlist, - }) - } - Err(why) => Err(why), - } - } - - fn shuffle(&mut self) -> &mut Playlist { - if let Err(error_msg) = run_mpv_command(&self.socket, "playlist-shuffle", &vec![]) { - panic!("Error: {}", error_msg); - } - if let Ok(mut playlist_entries) = - get_mpv_property::>(&self.socket, "playlist") { - if self.entries.len() == playlist_entries.len() { - for (i, entry) in playlist_entries.drain(0..).enumerate() { - self.entries[i] = entry; - } - } - } - self - } - - fn remove_id(&mut self, id: usize) -> &mut Playlist { - self.entries.remove(id); - if let Err(error_msg) = run_mpv_command(&self.socket, - "playlist-remove", - &vec![&id.to_string()]) { - panic!("Error: {}", error_msg); - } - self - } - - fn move_entry(&mut self, from: usize, to: usize) -> &mut Playlist { - if from != to { - if let Err(error_msg) = run_mpv_command(&self.socket, - "playlist-move", - &vec![&from.to_string(), &to.to_string()]) { - panic!("Error: {}", error_msg); - } - if from < to { - self.entries[from].id = to - 1; - self.entries[to].id = to - 2; - for i in from..to - 2 { - self.entries[i + 1].id = i; - } - self.entries.sort_by_key(|entry| entry.id); - } else if from > to { - self.entries[from].id = to; - for i in to..from - 1 { - self.entries[i].id = i + 1; - } - self.entries.sort_by_key(|entry| entry.id); - } - } - self - } - - fn current_id(&self) -> Option { - for entry in self.entries.iter() { - if entry.current { - return Some(entry.id); - } - } - None + fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), String> { + set_mpv_property::(instance, property, value) } } @@ -204,6 +133,8 @@ pub trait Commands { /// - bool /// - HashMap (e.g. for the 'metadata' property) /// - Vec (for the 'playlist' property) + /// - usize + /// - f64 /// /// ##Input arguments /// @@ -212,7 +143,7 @@ pub trait Commands { /// /// #Example /// ``` - /// let mpv: Socket = String::from(matches.value_of("socket").unwrap()); + /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); /// let paused: bool = mpv.get_property("pause").unwrap(); /// let title: String = mpv.get_property("media-title").unwrap(); /// ``` @@ -231,7 +162,7 @@ pub trait Commands { /// #Example /// /// ``` - /// let mpv: Socket = String::from(matches.value_of("socket").unwrap()); + /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); /// let title = mpv.get_property_string("media-title").unwrap(); /// ``` fn get_property_string(&self, property: &str) -> Result; @@ -256,7 +187,7 @@ pub trait Commands { /// /// #Example /// ``` - /// let mpv: Socket = String::from(matches.value_of("socket").unwrap()); + /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); /// /// //Run command 'playlist-shuffle' which takes no arguments /// mpv.run_command("playlist-shuffle", &vec![]); @@ -287,7 +218,7 @@ pub trait Commands { /// /// #Example /// ``` - /// let mpv: Socket = String::from(matches.value_of("socket").unwrap()); + /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); /// mpv.set_property("pause", true); /// ``` fn set_property>(&self, @@ -300,7 +231,7 @@ pub trait Commands { fn toggle(&self) -> Result<(), String>; } -impl Commands for Socket { +impl Commands for Mpv { fn get_metadata(&self) -> Result, String> { match get_mpv_property(self, "metadata") { Ok(map) => Ok(map), @@ -309,7 +240,10 @@ impl Commands for Socket { } fn get_playlist(&self) -> Result { - Playlist::get_from(self.to_string()) + match get_mpv_property::>(self, "playlist") { + Ok(entries) => Ok(Playlist(entries)), + Err(msg) => Err(msg), + } } fn get_property(&self, property: &str) -> Result { @@ -379,17 +313,13 @@ impl Commands for Socket { } fn playlist_play_next(&self, id: usize) -> Result<(), String> { - match Playlist::get_from(self.to_string()) { - Ok(playlist) => { - if let Some(current_id) = playlist.current_id() { - run_mpv_command(self, - "playlist-move", - &vec![&id.to_string(), &(current_id + 1).to_string()]) - } else { - Err("There is no file playing at the moment.".to_string()) - } + match get_mpv_property::(self, "playlist-pos") { + Ok(current_id) => { + run_mpv_command(self, + "playlist-move", + &vec![&id.to_string(), &(current_id + 1).to_string()]) } - Err(why) => Err(why), + Err(msg) => Err(msg), } }