Keeps the socket open for the entire lifetime of the Mpv struct. Now using channels to inform about new events (listen command). Added Readme

This commit is contained in:
Jonas Frei 2017-05-29 17:54:12 +02:00
parent e797bd871a
commit ea0e60097d
4 changed files with 134 additions and 171 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "mpvipc" name = "mpvipc"
version = "1.0.0" version = "1.1.0"
authors = ["Jonas Frei <freijon@gmail.com>"] authors = ["Jonas Frei <freijon@gmail.com>"]
description = "A small library which provides bindings to control existing mpv instances through sockets." description = "A small library which provides bindings to control existing mpv instances through sockets."
license = "GPL-3.0" license = "GPL-3.0"

45
README.md Normal file
View File

@ -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)

View File

@ -4,9 +4,8 @@ use std::error::Error;
use std::io::BufReader; use std::io::BufReader;
use std::io::prelude::*; use std::io::prelude::*;
use std::iter::Iterator; use std::iter::Iterator;
use std::os::unix::net::UnixStream;
use std::net::Shutdown;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use super::Mpv;
#[derive(Debug)] #[derive(Debug)]
pub struct PlaylistEntry { pub struct PlaylistEntry {
@ -209,18 +208,18 @@ impl TypeHandler for Vec<PlaylistEntry> {
} }
} }
pub fn get_mpv_property<T: TypeHandler>(socket: &str, property: &str) -> Result<T, String> { pub fn get_mpv_property<T: TypeHandler>(instance: &Mpv, property: &str) -> Result<T, String> {
let ipc_string = format!("{{ \"command\": [\"get_property\",\"{}\"] }}\n", property); let ipc_string = format!("{{ \"command\": [\"get_property\",\"{}\"] }}\n", property);
match serde_json::from_str::<Value>(&send_command_sync(socket, &ipc_string)) { match serde_json::from_str::<Value>(&send_command_sync(instance, &ipc_string)) {
Ok(val) => T::get_value(val), Ok(val) => T::get_value(val),
Err(why) => Err(format!("Error while getting property: {}", why)), Err(why) => Err(format!("Error while getting property: {}", why)),
} }
} }
pub fn get_mpv_property_string(socket: &str, property: &str) -> Result<String, String> { pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result<String, String> {
let ipc_string = format!("{{ \"command\": [\"get_property\",\"{}\"] }}\n", property); let ipc_string = format!("{{ \"command\": [\"get_property\",\"{}\"] }}\n", property);
match serde_json::from_str::<Value>(&send_command_sync(socket, &ipc_string)) { match serde_json::from_str::<Value>(&send_command_sync(instance, &ipc_string)) {
Ok(val) => { Ok(val) => {
if let Value::Object(map) = val { if let Value::Object(map) = val {
if let Value::String(ref error) = map["error"] { if let Value::String(ref error) = map["error"] {
@ -247,20 +246,20 @@ pub fn get_mpv_property_string(socket: &str, property: &str) -> Result<String, S
} }
} }
pub fn set_mpv_property<T: TypeHandler>(socket: &str, pub fn set_mpv_property<T: TypeHandler>(instance: &Mpv,
property: &str, property: &str,
value: T) value: T)
-> Result<(), String> { -> Result<(), String> {
let ipc_string = format!("{{ \"command\": [\"set_property\", \"{}\", {}] }}\n", let ipc_string = format!("{{ \"command\": [\"set_property\", \"{}\", {}] }}\n",
property, property,
value.as_string()); value.as_string());
match serde_json::from_str::<Value>(&send_command_sync(socket, &ipc_string)) { match serde_json::from_str::<Value>(&send_command_sync(instance, &ipc_string)) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(why) => Err(why.description().to_string()), 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); let mut ipc_string = format!("{{ \"command\": [\"{}\"", command);
if args.len() > 0 { if args.len() > 0 {
for arg in args.iter() { 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.push_str("] }\n");
ipc_string = ipc_string; ipc_string = ipc_string;
match serde_json::from_str::<Value>(&send_command_sync(socket, &ipc_string)) { match serde_json::from_str::<Value>(&send_command_sync(instance, &ipc_string)) {
Ok(feedback) => { Ok(feedback) => {
if let Value::String(ref error) = feedback["error"] { if let Value::String(ref error) = feedback["error"] {
if error == "success" { if error == "success" {
@ -278,6 +277,7 @@ pub fn run_mpv_command(socket: &str, command: &str, args: &Vec<&str>) -> Result<
Err(error.to_string()) Err(error.to_string())
} }
} else { } else {
//Ok(())
Err("Error: Unexpected result received".to_string()) 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", let ipc_string = format!("{{ \"command\": [\"observe_property\", {}, \"{}\"] }}\n",
id, id,
property); property);
match serde_json::from_str::<Value>(&send_command_sync(socket, &ipc_string)) { match serde_json::from_str::<Value>(&send_command_sync(instance, &ipc_string)) {
Ok(feedback) => { Ok(feedback) => {
if let Value::String(ref error) = feedback["error"] { if let Value::String(ref error) = feedback["error"] {
if error == "success" { if error == "success" {
@ -314,11 +314,9 @@ pub fn observe_mpv_property(socket: &str, id: &usize, property: &str) -> Result<
/// ``` /// ```
/// listen("/tmp/mpvsocket"); /// listen("/tmp/mpvsocket");
/// ``` /// ```
pub fn listen(socket: &str, tx: &Sender<String>) { pub fn listen(instance: &Mpv, tx: &Sender<String>) {
match UnixStream::connect(socket) {
Ok(stream) => {
let mut response = String::new(); let mut response = String::new();
let mut reader = BufReader::new(&stream); let mut reader = BufReader::new(instance);
reader.read_line(&mut response).unwrap(); reader.read_line(&mut response).unwrap();
match serde_json::from_str::<Value>(&response) { match serde_json::from_str::<Value>(&response) {
Ok(e) => { Ok(e) => {
@ -329,32 +327,22 @@ pub fn listen(socket: &str, tx: &Sender<String>) {
Err(why) => panic!("{}", why.description().to_string()), Err(why) => panic!("{}", why.description().to_string()),
} }
response.clear(); response.clear();
stream
.shutdown(Shutdown::Both)
.expect("shutdown function failed");
}
Err(why) => panic!("Error: Could not connect to socket: {}", why.description()),
}
} }
fn send_command_sync(socket: &str, command: &str) -> String { fn send_command_sync(instance: &Mpv, command: &str) -> String {
match UnixStream::connect(socket) { let mut stream = instance;
Ok(mut stream) => {
match stream.write_all(command.as_bytes()) { match stream.write_all(command.as_bytes()) {
Err(why) => panic!("Error: Could not write to socket: {}", why.description()), Err(why) => panic!("Error: Could not write to socket: {}", why.description()),
Ok(_) => { Ok(_) => {
let mut response = String::new(); let mut response = String::new();
{ {
let mut reader = BufReader::new(&stream); let mut reader = BufReader::new(stream);
while !response.contains("\"error\":") {
response.clear();
reader.read_line(&mut response).unwrap(); reader.read_line(&mut response).unwrap();
} }
stream }
.shutdown(Shutdown::Both)
.expect("shutdown function failed");
response response
} }
} }
}
Err(why) => panic!("Error: Could not connect to socket: {}", why.description()),
}
} }

View File

@ -5,10 +5,10 @@ pub mod ipc;
use ipc::*; use ipc::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::os::unix::net::UnixStream;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
pub type Mpv = UnixStream;
pub type Socket = String;
pub enum NumberChangeOptions { pub enum NumberChangeOptions {
Absolute, Absolute,
@ -35,159 +35,88 @@ pub enum Switch {
Toggle, Toggle,
} }
pub struct Playlist { pub struct Playlist(pub Vec<PlaylistEntry>);
pub socket: Socket,
pub entries: Vec<PlaylistEntry>, pub trait MpvConnector {
fn connect(socket: &str) -> Result<Mpv, String>;
}
impl MpvConnector for Mpv {
fn connect(socket: &str) -> Result<Mpv, String> {
match UnixStream::connect(socket) {
Ok(stream) => Ok(stream),
Err(msg) => Err(msg.to_string()),
}
}
} }
pub trait GetPropertyTypeHandler: Sized { pub trait GetPropertyTypeHandler: Sized {
fn get_property_generic(socket: &str, property: &str) -> Result<Self, String>; fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, String>;
} }
impl GetPropertyTypeHandler for bool { impl GetPropertyTypeHandler for bool {
fn get_property_generic(socket: &str, property: &str) -> Result<bool, String> { fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, String> {
get_mpv_property::<bool>(socket, property) get_mpv_property::<bool>(instance, property)
} }
} }
impl GetPropertyTypeHandler for String { impl GetPropertyTypeHandler for String {
fn get_property_generic(socket: &str, property: &str) -> Result<String, String> { fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, String> {
get_mpv_property::<String>(socket, property) get_mpv_property::<String>(instance, property)
} }
} }
impl GetPropertyTypeHandler for f64 { impl GetPropertyTypeHandler for f64 {
fn get_property_generic(socket: &str, property: &str) -> Result<f64, String> { fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, String> {
get_mpv_property::<f64>(socket, property) get_mpv_property::<f64>(instance, property)
} }
} }
impl GetPropertyTypeHandler for usize { impl GetPropertyTypeHandler for usize {
fn get_property_generic(socket: &str, property: &str) -> Result<usize, String> { fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, String> {
get_mpv_property::<usize>(socket, property) get_mpv_property::<usize>(instance, property)
} }
} }
impl GetPropertyTypeHandler for Vec<PlaylistEntry> { impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
fn get_property_generic(socket: &str, property: &str) -> Result<Vec<PlaylistEntry>, String> { fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, String> {
get_mpv_property::<Vec<PlaylistEntry>>(socket, property) get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
} }
} }
impl GetPropertyTypeHandler for HashMap<String, String> { impl GetPropertyTypeHandler for HashMap<String, String> {
fn get_property_generic(socket: &str, fn get_property_generic(instance: &Mpv,
property: &str) property: &str)
-> Result<HashMap<String, String>, String> { -> Result<HashMap<String, String>, String> {
get_mpv_property::<HashMap<String, String>>(socket, property) get_mpv_property::<HashMap<String, String>>(instance, property)
} }
} }
pub trait SetPropertyTypeHandler<T> { pub trait SetPropertyTypeHandler<T> {
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<bool> for bool { impl SetPropertyTypeHandler<bool> for bool {
fn set_property_generic(socket: &str, property: &str, value: bool) -> Result<(), String> { fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), String> {
set_mpv_property::<bool>(socket, property, value) set_mpv_property::<bool>(instance, property, value)
} }
} }
impl SetPropertyTypeHandler<String> for String { impl SetPropertyTypeHandler<String> for String {
fn set_property_generic(socket: &str, property: &str, value: String) -> Result<(), String> { fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), String> {
set_mpv_property::<String>(socket, property, value) set_mpv_property::<String>(instance, property, value)
} }
} }
impl SetPropertyTypeHandler<f64> for f64 { impl SetPropertyTypeHandler<f64> for f64 {
fn set_property_generic(socket: &str, property: &str, value: f64) -> Result<(), String> { fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), String> {
set_mpv_property::<f64>(socket, property, value) set_mpv_property::<f64>(instance, property, value)
} }
} }
impl SetPropertyTypeHandler<usize> for usize { impl SetPropertyTypeHandler<usize> for usize {
fn set_property_generic(socket: &str, property: &str, value: usize) -> Result<(), String> { fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), String> {
set_mpv_property::<usize>(socket, property, value) set_mpv_property::<usize>(instance, property, value)
}
}
pub trait PlaylistHandler {
fn get_from(socket: Socket) -> Result<Playlist, String>;
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<usize>;
}
impl PlaylistHandler for Playlist {
fn get_from(socket: Socket) -> Result<Playlist, String> {
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::<Vec<PlaylistEntry>>(&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<usize> {
for entry in self.entries.iter() {
if entry.current {
return Some(entry.id);
}
}
None
} }
} }
@ -204,6 +133,8 @@ pub trait Commands {
/// - bool /// - bool
/// - HashMap<String, String> (e.g. for the 'metadata' property) /// - HashMap<String, String> (e.g. for the 'metadata' property)
/// - Vec<PlaylistEntry> (for the 'playlist' property) /// - Vec<PlaylistEntry> (for the 'playlist' property)
/// - usize
/// - f64
/// ///
/// ##Input arguments /// ##Input arguments
/// ///
@ -212,7 +143,7 @@ pub trait Commands {
/// ///
/// #Example /// #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 paused: bool = mpv.get_property("pause").unwrap();
/// let title: String = mpv.get_property("media-title").unwrap(); /// let title: String = mpv.get_property("media-title").unwrap();
/// ``` /// ```
@ -231,7 +162,7 @@ pub trait Commands {
/// #Example /// #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(); /// let title = mpv.get_property_string("media-title").unwrap();
/// ``` /// ```
fn get_property_string(&self, property: &str) -> Result<String, String>; fn get_property_string(&self, property: &str) -> Result<String, String>;
@ -256,7 +187,7 @@ pub trait Commands {
/// ///
/// #Example /// #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 /// //Run command 'playlist-shuffle' which takes no arguments
/// mpv.run_command("playlist-shuffle", &vec![]); /// mpv.run_command("playlist-shuffle", &vec![]);
@ -287,7 +218,7 @@ pub trait Commands {
/// ///
/// #Example /// #Example
/// ``` /// ```
/// let mpv: Socket = String::from(matches.value_of("socket").unwrap()); /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap();
/// mpv.set_property("pause", true); /// mpv.set_property("pause", true);
/// ``` /// ```
fn set_property<T: SetPropertyTypeHandler<T>>(&self, fn set_property<T: SetPropertyTypeHandler<T>>(&self,
@ -300,7 +231,7 @@ pub trait Commands {
fn toggle(&self) -> Result<(), String>; fn toggle(&self) -> Result<(), String>;
} }
impl Commands for Socket { impl Commands for Mpv {
fn get_metadata(&self) -> Result<HashMap<String, String>, String> { fn get_metadata(&self) -> Result<HashMap<String, String>, String> {
match get_mpv_property(self, "metadata") { match get_mpv_property(self, "metadata") {
Ok(map) => Ok(map), Ok(map) => Ok(map),
@ -309,7 +240,10 @@ impl Commands for Socket {
} }
fn get_playlist(&self) -> Result<Playlist, String> { fn get_playlist(&self) -> Result<Playlist, String> {
Playlist::get_from(self.to_string()) match get_mpv_property::<Vec<PlaylistEntry>>(self, "playlist") {
Ok(entries) => Ok(Playlist(entries)),
Err(msg) => Err(msg),
}
} }
fn get_property<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, String> { fn get_property<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, String> {
@ -379,17 +313,13 @@ impl Commands for Socket {
} }
fn playlist_play_next(&self, id: usize) -> Result<(), String> { fn playlist_play_next(&self, id: usize) -> Result<(), String> {
match Playlist::get_from(self.to_string()) { match get_mpv_property::<usize>(self, "playlist-pos") {
Ok(playlist) => { Ok(current_id) => {
if let Some(current_id) = playlist.current_id() {
run_mpv_command(self, run_mpv_command(self,
"playlist-move", "playlist-move",
&vec![&id.to_string(), &(current_id + 1).to_string()]) &vec![&id.to_string(), &(current_id + 1).to_string()])
} else {
Err("There is no file playing at the moment.".to_string())
} }
} Err(msg) => Err(msg),
Err(why) => Err(why),
} }
} }