diff --git a/Cargo.toml b/Cargo.toml index d32fff6..c1500ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,12 @@ license = "GPL-3.0" homepage = "https://gitlab.com/mpv-ipc/mpvipc" repository = "https://gitlab.com/mpv-ipc/mpvipc" documentation = "https://docs.rs/mpvipc/" - +edition = "2018" [dependencies] serde = "1.0.1" -serde_json = "1.0.0" \ No newline at end of file +serde_json = "1.0.0" +log = "0.4.6" + +[dev-dependencies] +env_logger = "*" diff --git a/examples/fetch_state.rs b/examples/fetch_state.rs new file mode 100644 index 0000000..6387399 --- /dev/null +++ b/examples/fetch_state.rs @@ -0,0 +1,15 @@ +use env_logger; +use mpvipc::{Error as MpvError, Mpv}; + +fn main() -> Result<(), MpvError> { + env_logger::init(); + + let mpv = Mpv::connect("/tmp/mpvsocket")?; + let meta = mpv.get_metadata()?; + println!("metadata: {:?}", meta); + let playlist = mpv.get_playlist()?; + println!("playlist: {:?}", playlist); + let playback_time: f64 = mpv.get_property("playback-time")?; + println!("playback-time: {}", playback_time); + Ok(()) +} diff --git a/examples/media_player.rs b/examples/media_player.rs new file mode 100644 index 0000000..c7d0cf0 --- /dev/null +++ b/examples/media_player.rs @@ -0,0 +1,72 @@ +use env_logger; +use mpvipc::{Error, Event, Mpv, MpvDataType, Property}; +use std::io::{self, Write}; + +fn seconds_to_hms(total: f64) -> String { + let total = total as u64; + let seconds = total % 60; + let total = total / 60; + let minutes = total % 60; + let hours = total / 60; + format!("{:02}:{:02}:{:02}", hours, minutes, seconds) +} + +fn main() -> Result<(), Error> { + env_logger::init(); + + let mut mpv = Mpv::connect("/tmp/mpvsocket")?; + let mut pause = false; + let mut playback_time = std::f64::NAN; + let mut duration = std::f64::NAN; + mpv.observe_property(&1, "path")?; + mpv.observe_property(&2, "pause")?; + mpv.observe_property(&3, "playback-time")?; + mpv.observe_property(&4, "duration")?; + mpv.observe_property(&5, "metadata")?; + loop { + let event = mpv.event_listen()?; + match event { + Event::PropertyChange(property) => match property { + Property::Path(Some(value)) => println!("\nPlaying: {}", value), + Property::Path(None) => (), + Property::Pause(value) => pause = value, + Property::PlaybackTime(Some(value)) => playback_time = value, + Property::PlaybackTime(None) => playback_time = std::f64::NAN, + Property::Duration(Some(value)) => duration = value, + Property::Duration(None) => duration = std::f64::NAN, + Property::Metadata(Some(value)) => { + println!("File tags:"); + if let Some(MpvDataType::String(value)) = value.get("ARTIST") { + println!(" Artist: {}", value); + } + if let Some(MpvDataType::String(value)) = value.get("ALBUM") { + println!(" Album: {}", value); + } + if let Some(MpvDataType::String(value)) = value.get("TITLE") { + println!(" Title: {}", value); + } + if let Some(MpvDataType::String(value)) = value.get("TRACK") { + println!(" Track: {}", value); + } + } + Property::Metadata(None) => (), + Property::Unknown { + name: _, + id: _, + data: _, + } => (), + }, + Event::Shutdown => return Ok(()), + Event::Unimplemented => panic!("Unimplemented event"), + _ => (), + } + print!( + "{}{} / {} ({:.0}%)\r", + if pause { "(Paused) " } else { "" }, + seconds_to_hms(playback_time), + seconds_to_hms(duration), + 100. * playback_time / duration + ); + io::stdout().flush().unwrap(); + } +} diff --git a/src/ipc.rs b/src/ipc.rs index f9c8a00..72fb641 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1,9 +1,10 @@ +use super::*; +use log::{debug, warn}; use serde_json::{self, Value}; use std::collections::HashMap; -use std::io::BufReader; use std::io::prelude::*; +use std::io::BufReader; use std::iter::Iterator; -use super::*; #[derive(Debug)] pub struct PlaylistEntry { @@ -256,11 +257,10 @@ pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<( } } -pub fn observe_mpv_property(instance: &Mpv, id: &usize, property: &str) -> Result<(), Error> { +pub fn observe_mpv_property(instance: &Mpv, id: &isize, property: &str) -> Result<(), Error> { let ipc_string = format!( "{{ \"command\": [\"observe_property\", {}, \"{}\"] }}\n", - id, - property + id, property ); match serde_json::from_str::(&send_command_sync(instance, &ipc_string)) { Ok(feedback) => { @@ -278,10 +278,49 @@ pub fn observe_mpv_property(instance: &Mpv, id: &usize, property: &str) -> Resul } } +fn try_convert_property(name: &str, id: isize, data: MpvDataType) -> Event { + let property = match name { + "path" => match data { + MpvDataType::String(value) => Property::Path(Some(value)), + MpvDataType::Null => Property::Path(None), + _ => unimplemented!(), + }, + "pause" => match data { + MpvDataType::Bool(value) => Property::Pause(value), + _ => unimplemented!(), + }, + "playback-time" => match data { + MpvDataType::Double(value) => Property::PlaybackTime(Some(value)), + MpvDataType::Null => Property::PlaybackTime(None), + _ => unimplemented!(), + }, + "duration" => match data { + MpvDataType::Double(value) => Property::Duration(Some(value)), + MpvDataType::Null => Property::Duration(None), + _ => unimplemented!(), + }, + "metadata" => match data { + MpvDataType::HashMap(value) => Property::Metadata(Some(value)), + MpvDataType::Null => Property::Metadata(None), + _ => unimplemented!(), + }, + _ => { + warn!("Property {} not implemented", name); + Property::Unknown { + name: name.to_string(), + id, + data, + } + } + }; + Event::PropertyChange(property) +} + pub fn listen(instance: &mut Mpv) -> Result { let mut response = String::new(); instance.reader.read_line(&mut response).unwrap(); - response = response.trim_right().to_string(); + response = response.trim_end().to_string(); + debug!("Event: {}", response); match serde_json::from_str::(&response) { Ok(e) => { if let Value::String(ref name) = e["event"] { @@ -337,7 +376,7 @@ pub fn listen(instance: &mut Mpv) -> Result { } "property-change" => { let name: String; - let id: usize; + let id: isize; let data: MpvDataType; if let Value::String(ref n) = e["name"] { @@ -347,9 +386,9 @@ pub fn listen(instance: &mut Mpv) -> Result { } if let Value::Number(ref n) = e["id"] { - id = n.as_u64().unwrap() as usize; + id = n.as_i64().unwrap() as isize; } else { - return Err(Error(ErrorCode::JsonContainsUnexptectedType)); + id = 0; } match e["data"] { @@ -389,7 +428,7 @@ pub fn listen(instance: &mut Mpv) -> Result { } } - event = Event::PropertyChange { name, id, data } + event = try_convert_property(name.as_ref(), id, data); } _ => { event = Event::Unimplemented; @@ -406,7 +445,7 @@ pub fn listen(instance: &mut Mpv) -> Result { pub fn listen_raw(instance: &mut Mpv) -> String { let mut response = String::new(); instance.reader.read_line(&mut response).unwrap(); - response.trim_right().to_string() + response.trim_end().to_string() // let mut stream = &instance.0; // let mut buffer = [0; 32]; // stream.read(&mut buffer[..]).unwrap(); @@ -418,6 +457,7 @@ fn send_command_sync(instance: &Mpv, command: &str) -> String { match stream.write_all(command.as_bytes()) { Err(why) => panic!("Error: Could not write to socket: {}", why), Ok(_) => { + debug!("Command: {}", command.trim_end()); let mut response = String::new(); { let mut reader = BufReader::new(stream); @@ -426,6 +466,7 @@ fn send_command_sync(instance: &Mpv, command: &str) -> String { reader.read_line(&mut response).unwrap(); } } + debug!("Response: {}", response.trim_end()); response } } @@ -554,4 +595,4 @@ fn json_array_to_playlist(array: &Vec) -> Vec { }); } output -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index a70143f..8e3ded1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,10 @@ -extern crate serde; -extern crate serde_json; - pub mod ipc; use ipc::*; use std::collections::HashMap; use std::fmt::{self, Display}; +use std::io::{BufReader, Read}; use std::os::unix::net::UnixStream; -use std::io::{Read, BufReader}; #[derive(Debug)] pub enum Event { @@ -39,15 +36,25 @@ pub enum Event { MetadataUpdate, Seek, PlaybackRestart, - PropertyChange { - name: String, - id: usize, - data: MpvDataType, - }, + PropertyChange(Property), ChapterChange, Unimplemented, } +#[derive(Debug)] +pub enum Property { + Path(Option), + Pause(bool), + PlaybackTime(Option), + Duration(Option), + Metadata(Option>), + Unknown { + name: String, + id: isize, + data: MpvDataType, + }, +} + #[derive(Debug)] pub enum MpvDataType { Array(Vec), @@ -110,6 +117,7 @@ pub enum ErrorCode { pub struct Mpv { stream: UnixStream, reader: BufReader, + name: String, } #[derive(Debug)] pub struct Playlist(pub Vec); @@ -122,6 +130,12 @@ impl Drop for Mpv { } } +impl fmt::Debug for Mpv { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_tuple("Mpv").field(&self.name).finish() + } +} + impl Clone for Mpv { fn clone(&self) -> Self { let stream = self.stream.try_clone().expect("cloning UnixStream"); @@ -129,6 +143,7 @@ impl Clone for Mpv { Mpv { stream, reader: BufReader::new(cloned_stream), + name: self.name.clone(), } } @@ -138,6 +153,7 @@ impl Clone for Mpv { *self = Mpv { stream, reader: BufReader::new(cloned_stream), + name: source.name.clone(), } } } @@ -167,9 +183,7 @@ impl Display for ErrorCode { f.write_str("The received value is not of type \'std::f64\'") } ErrorCode::ValueDoesNotContainHashMap => { - f.write_str( - "The received value is not of type \'std::collections::HashMap\'", - ) + f.write_str("The received value is not of type \'std::collections::HashMap\'") } ErrorCode::ValueDoesNotContainPlaylist => { f.write_str("The received value is not of type \'mpvipc::Playlist\'") @@ -263,6 +277,7 @@ impl Mpv { return Ok(Mpv { stream, reader: BufReader::new(cloned_stream), + name: String::from(socket), }); } Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))), @@ -271,9 +286,9 @@ impl Mpv { pub fn disconnect(&self) { let mut stream = &self.stream; - stream.shutdown(std::net::Shutdown::Both).expect( - "socket disconnect", - ); + stream + .shutdown(std::net::Shutdown::Both) + .expect("socket disconnect"); let mut buffer = [0; 32]; for _ in 0..stream.bytes().count() { stream.read(&mut buffer[..]).unwrap(); @@ -316,9 +331,13 @@ impl Mpv { /// /// #Example /// ``` - /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); - /// let paused: bool = mpv.get_property("pause").unwrap(); - /// let title: String = mpv.get_property("media-title").unwrap(); + /// # use mpvipc::{Mpv, Error}; + /// # fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// let paused: bool = mpv.get_property("pause")?; + /// let title: String = mpv.get_property("media-title")?; + /// # Ok(()) + /// # } /// ``` pub fn get_property(&self, property: &str) -> Result { T::get_property_generic(self, property) @@ -336,8 +355,12 @@ impl Mpv { /// #Example /// /// ``` - /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); - /// let title = mpv.get_property_string("media-title").unwrap(); + /// # use mpvipc::{Mpv, Error}; + /// # fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// let title = mpv.get_property_string("media-title")?; + /// # Ok(()) + /// # } /// ``` pub fn get_property_string(&self, property: &str) -> Result { get_mpv_property_string(self, property) @@ -353,10 +376,10 @@ impl Mpv { /// /// #Example /// - /// ``` - /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); + /// ```ignore + /// let mut mpv = Mpv::connect("/tmp/mpvsocket")?; /// loop { - /// let event = mpv.event_listen().unwrap(); + /// let event = mpv.event_listen()?; /// println!("{:?}", event); /// } /// ``` @@ -372,7 +395,7 @@ impl Mpv { run_mpv_command(self, "playlist-next", &[]) } - pub fn observe_property(&self, id: &usize, property: &str) -> Result<(), Error> { + pub fn observe_property(&self, id: &isize, property: &str) -> Result<(), Error> { observe_mpv_property(self, id, property) } @@ -399,13 +422,17 @@ impl Mpv { /// /// #Example /// ``` - /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); + /// # use mpvipc::{Mpv, Error}; + /// # fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; /// /// //Run command 'playlist-shuffle' which takes no arguments - /// mpv.run_command("playlist-shuffle", &[]); + /// mpv.run_command("playlist-shuffle", &[])?; /// /// //Run command 'seek' which in this case takes two arguments - /// mpv.run_command("seek", &["0", "absolute"]); + /// mpv.run_command("seek", &["0", "absolute"])?; + /// # Ok(()) + /// # } /// ``` pub fn run_command(&self, command: &str, args: &[&str]) -> Result<(), Error> { run_mpv_command(self, command, args) @@ -418,33 +445,25 @@ impl Mpv { option: PlaylistAddOptions, ) -> Result<(), Error> { match file_type { - PlaylistAddTypeOptions::File => { - match option { - PlaylistAddOptions::Replace => { - run_mpv_command(self, "loadfile", &[file, "replace"]) - } - PlaylistAddOptions::Append => { - run_mpv_command(self, "loadfile", &[file, "append"]) - } - PlaylistAddOptions::AppendPlay => { - run_mpv_command(self, "loadfile", &[file, "append-play"]) - } + PlaylistAddTypeOptions::File => match option { + PlaylistAddOptions::Replace => { + run_mpv_command(self, "loadfile", &[file, "replace"]) } - } + PlaylistAddOptions::Append => run_mpv_command(self, "loadfile", &[file, "append"]), + PlaylistAddOptions::AppendPlay => { + run_mpv_command(self, "loadfile", &[file, "append-play"]) + } + }, - PlaylistAddTypeOptions::Playlist => { - match option { - PlaylistAddOptions::Replace => { - run_mpv_command(self, "loadlist", &[file, "replace"]) - } - PlaylistAddOptions::Append | - PlaylistAddOptions::AppendPlay => { - run_mpv_command(self, "loadlist", &[file, "append"]) - } + PlaylistAddTypeOptions::Playlist => match option { + PlaylistAddOptions::Replace => { + run_mpv_command(self, "loadlist", &[file, "replace"]) } - } + PlaylistAddOptions::Append | PlaylistAddOptions::AppendPlay => { + run_mpv_command(self, "loadlist", &[file, "append"]) + } + }, } - } pub fn playlist_clear(&self) -> Result<(), Error> { @@ -461,13 +480,11 @@ impl Mpv { pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> { match get_mpv_property::(self, "playlist-pos") { - Ok(current_id) => { - run_mpv_command( - self, - "playlist-move", - &[&id.to_string(), &(current_id + 1).to_string()], - ) - } + Ok(current_id) => run_mpv_command( + self, + "playlist-move", + &[&id.to_string(), &(current_id + 1).to_string()], + ), Err(msg) => Err(msg), } } @@ -502,21 +519,17 @@ impl Mpv { match option { Switch::On => enabled = true, Switch::Off => {} - Switch::Toggle => { - match get_mpv_property_string(self, "loop-file") { - Ok(value) => { - match value.as_ref() { - "false" => { - enabled = true; - } - _ => { - enabled = false; - } - } + Switch::Toggle => match get_mpv_property_string(self, "loop-file") { + Ok(value) => match value.as_ref() { + "false" => { + enabled = true; } - Err(msg) => return Err(msg), - } - } + _ => { + enabled = false; + } + }, + Err(msg) => return Err(msg), + }, } set_mpv_property(self, "loop-file", enabled) } @@ -526,21 +539,17 @@ impl Mpv { match option { Switch::On => enabled = true, Switch::Off => {} - Switch::Toggle => { - match get_mpv_property_string(self, "loop-playlist") { - Ok(value) => { - match value.as_ref() { - "false" => { - enabled = true; - } - _ => { - enabled = false; - } - } + Switch::Toggle => match get_mpv_property_string(self, "loop-playlist") { + Ok(value) => match value.as_ref() { + "false" => { + enabled = true; } - Err(msg) => return Err(msg), - } - } + _ => { + enabled = false; + } + }, + Err(msg) => return Err(msg), + }, } set_mpv_property(self, "loop-playlist", enabled) } @@ -550,14 +559,12 @@ impl Mpv { match option { Switch::On => enabled = true, Switch::Off => {} - Switch::Toggle => { - match get_mpv_property::(self, "mute") { - Ok(value) => { - enabled = !value; - } - Err(msg) => return Err(msg), + Switch::Toggle => match get_mpv_property::(self, "mute") { + Ok(value) => { + enabled = !value; } - } + Err(msg) => return Err(msg), + }, } set_mpv_property(self, "mute", enabled) } @@ -579,8 +586,12 @@ impl Mpv { /// /// #Example /// ``` - /// let mpv = Mpv::connect("/tmp/mpvsocket").unwrap(); - /// mpv.set_property("pause", true); + /// # use mpvipc::{Mpv, Error}; + /// # fn main() -> Result<(), Error> { + /// let mpv = Mpv::connect("/tmp/mpvsocket")?; + /// mpv.set_property("pause", true)?; + /// # Ok(()) + /// # } /// ``` pub fn set_property>( &self, @@ -592,38 +603,34 @@ impl Mpv { pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> { match get_mpv_property::(self, "speed") { - Ok(speed) => { - match option { - NumberChangeOptions::Increase => { - set_mpv_property(self, "speed", speed + input_speed) - } - - NumberChangeOptions::Decrease => { - set_mpv_property(self, "speed", speed - input_speed) - } - - NumberChangeOptions::Absolute => set_mpv_property(self, "speed", input_speed), + Ok(speed) => match option { + NumberChangeOptions::Increase => { + set_mpv_property(self, "speed", speed + input_speed) } - } + + NumberChangeOptions::Decrease => { + set_mpv_property(self, "speed", speed - input_speed) + } + + NumberChangeOptions::Absolute => set_mpv_property(self, "speed", input_speed), + }, Err(msg) => Err(msg), } } pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> { match get_mpv_property::(self, "volume") { - Ok(volume) => { - match option { - NumberChangeOptions::Increase => { - set_mpv_property(self, "volume", volume + input_volume) - } - - NumberChangeOptions::Decrease => { - set_mpv_property(self, "volume", volume - input_volume) - } - - NumberChangeOptions::Absolute => set_mpv_property(self, "volume", input_volume), + Ok(volume) => match option { + NumberChangeOptions::Increase => { + set_mpv_property(self, "volume", volume + input_volume) } - } + + NumberChangeOptions::Decrease => { + set_mpv_property(self, "volume", volume - input_volume) + } + + NumberChangeOptions::Absolute => set_mpv_property(self, "volume", input_volume), + }, Err(msg) => Err(msg), } } @@ -638,4 +645,4 @@ impl Mpv { Err(msg) => Err(msg), } } -} \ No newline at end of file +}