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:
parent
e797bd871a
commit
ea0e60097d
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mpvipc"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
authors = ["Jonas Frei <freijon@gmail.com>"]
|
||||
description = "A small library which provides bindings to control existing mpv instances through sockets."
|
||||
license = "GPL-3.0"
|
||||
|
45
README.md
Normal file
45
README.md
Normal 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)
|
52
src/ipc.rs
52
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<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);
|
||||
|
||||
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),
|
||||
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);
|
||||
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) => {
|
||||
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<String, S
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mpv_property<T: TypeHandler>(socket: &str,
|
||||
pub fn set_mpv_property<T: TypeHandler>(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::<Value>(&send_command_sync(socket, &ipc_string)) {
|
||||
match serde_json::from_str::<Value>(&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::<Value>(&send_command_sync(socket, &ipc_string)) {
|
||||
match serde_json::from_str::<Value>(&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::<Value>(&send_command_sync(socket, &ipc_string)) {
|
||||
match serde_json::from_str::<Value>(&send_command_sync(instance, &ipc_string)) {
|
||||
Ok(feedback) => {
|
||||
if let Value::String(ref error) = feedback["error"] {
|
||||
if error == "success" {
|
||||
@ -314,11 +314,9 @@ pub fn observe_mpv_property(socket: &str, id: &usize, property: &str) -> Result<
|
||||
/// ```
|
||||
/// listen("/tmp/mpvsocket");
|
||||
/// ```
|
||||
pub fn listen(socket: &str, tx: &Sender<String>) {
|
||||
match UnixStream::connect(socket) {
|
||||
Ok(stream) => {
|
||||
pub fn listen(instance: &Mpv, tx: &Sender<String>) {
|
||||
let mut response = String::new();
|
||||
let mut reader = BufReader::new(&stream);
|
||||
let mut reader = BufReader::new(instance);
|
||||
reader.read_line(&mut response).unwrap();
|
||||
match serde_json::from_str::<Value>(&response) {
|
||||
Ok(e) => {
|
||||
@ -329,32 +327,22 @@ pub fn listen(socket: &str, tx: &Sender<String>) {
|
||||
Err(why) => panic!("{}", why.description().to_string()),
|
||||
}
|
||||
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 {
|
||||
match UnixStream::connect(socket) {
|
||||
Ok(mut stream) => {
|
||||
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);
|
||||
let mut reader = BufReader::new(stream);
|
||||
while !response.contains("\"error\":") {
|
||||
response.clear();
|
||||
reader.read_line(&mut response).unwrap();
|
||||
}
|
||||
stream
|
||||
.shutdown(Shutdown::Both)
|
||||
.expect("shutdown function failed");
|
||||
}
|
||||
response
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => panic!("Error: Could not connect to socket: {}", why.description()),
|
||||
}
|
||||
}
|
||||
|
172
src/lib.rs
172
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<PlaylistEntry>,
|
||||
pub struct Playlist(pub 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 {
|
||||
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 {
|
||||
fn get_property_generic(socket: &str, property: &str) -> Result<bool, String> {
|
||||
get_mpv_property::<bool>(socket, property)
|
||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, String> {
|
||||
get_mpv_property::<bool>(instance, property)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPropertyTypeHandler for String {
|
||||
fn get_property_generic(socket: &str, property: &str) -> Result<String, String> {
|
||||
get_mpv_property::<String>(socket, property)
|
||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, String> {
|
||||
get_mpv_property::<String>(instance, property)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPropertyTypeHandler for f64 {
|
||||
fn get_property_generic(socket: &str, property: &str) -> Result<f64, String> {
|
||||
get_mpv_property::<f64>(socket, property)
|
||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, String> {
|
||||
get_mpv_property::<f64>(instance, property)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPropertyTypeHandler for usize {
|
||||
fn get_property_generic(socket: &str, property: &str) -> Result<usize, String> {
|
||||
get_mpv_property::<usize>(socket, property)
|
||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, String> {
|
||||
get_mpv_property::<usize>(instance, property)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
|
||||
fn get_property_generic(socket: &str, property: &str) -> Result<Vec<PlaylistEntry>, String> {
|
||||
get_mpv_property::<Vec<PlaylistEntry>>(socket, property)
|
||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, String> {
|
||||
get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPropertyTypeHandler for HashMap<String, String> {
|
||||
fn get_property_generic(socket: &str,
|
||||
fn get_property_generic(instance: &Mpv,
|
||||
property: &str)
|
||||
-> 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> {
|
||||
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 {
|
||||
fn set_property_generic(socket: &str, property: &str, value: bool) -> Result<(), String> {
|
||||
set_mpv_property::<bool>(socket, property, value)
|
||||
fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), String> {
|
||||
set_mpv_property::<bool>(instance, property, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SetPropertyTypeHandler<String> for String {
|
||||
fn set_property_generic(socket: &str, property: &str, value: String) -> Result<(), String> {
|
||||
set_mpv_property::<String>(socket, property, value)
|
||||
fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), String> {
|
||||
set_mpv_property::<String>(instance, property, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SetPropertyTypeHandler<f64> for f64 {
|
||||
fn set_property_generic(socket: &str, property: &str, value: f64) -> Result<(), String> {
|
||||
set_mpv_property::<f64>(socket, property, value)
|
||||
fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), String> {
|
||||
set_mpv_property::<f64>(instance, property, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SetPropertyTypeHandler<usize> for usize {
|
||||
fn set_property_generic(socket: &str, property: &str, value: usize) -> Result<(), String> {
|
||||
set_mpv_property::<usize>(socket, 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
|
||||
fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), String> {
|
||||
set_mpv_property::<usize>(instance, property, value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,6 +133,8 @@ pub trait Commands {
|
||||
/// - bool
|
||||
/// - HashMap<String, String> (e.g. for the 'metadata' property)
|
||||
/// - Vec<PlaylistEntry> (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<String, String>;
|
||||
@ -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<T: SetPropertyTypeHandler<T>>(&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<HashMap<String, String>, 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, 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> {
|
||||
@ -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() {
|
||||
match get_mpv_property::<usize>(self, "playlist-pos") {
|
||||
Ok(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())
|
||||
}
|
||||
}
|
||||
Err(why) => Err(why),
|
||||
Err(msg) => Err(msg),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user