Misc: refactor + serde serialization derives
- Split content into more files - Refactor TypeHandler impls - Add serialization traits to public structs/enums
This commit is contained in:
parent
6d2575b940
commit
762782e680
|
@ -12,6 +12,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0.104"
|
serde_json = "1.0.104"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
|
|
|
@ -0,0 +1,749 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
|
|
||||||
|
use crate::ipc::{
|
||||||
|
get_mpv_property, get_mpv_property_string, listen, listen_raw, observe_mpv_property,
|
||||||
|
run_mpv_command, set_mpv_property, unobserve_mpv_property,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Event {
|
||||||
|
Shutdown,
|
||||||
|
StartFile,
|
||||||
|
EndFile,
|
||||||
|
FileLoaded,
|
||||||
|
TracksChanged,
|
||||||
|
TrackSwitched,
|
||||||
|
Idle,
|
||||||
|
Pause,
|
||||||
|
Unpause,
|
||||||
|
Tick,
|
||||||
|
VideoReconfig,
|
||||||
|
AudioReconfig,
|
||||||
|
MetadataUpdate,
|
||||||
|
Seek,
|
||||||
|
PlaybackRestart,
|
||||||
|
PropertyChange { id: usize, property: Property },
|
||||||
|
ChapterChange,
|
||||||
|
ClientMessage { args: Vec<String> },
|
||||||
|
Unimplemented,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Property {
|
||||||
|
Path(Option<String>),
|
||||||
|
Pause(bool),
|
||||||
|
PlaybackTime(Option<f64>),
|
||||||
|
Duration(Option<f64>),
|
||||||
|
Metadata(Option<HashMap<String, MpvDataType>>),
|
||||||
|
Unknown { name: String, data: MpvDataType },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum MpvCommand {
|
||||||
|
LoadFile {
|
||||||
|
file: String,
|
||||||
|
option: PlaylistAddOptions,
|
||||||
|
},
|
||||||
|
LoadList {
|
||||||
|
file: String,
|
||||||
|
option: PlaylistAddOptions,
|
||||||
|
},
|
||||||
|
PlaylistClear,
|
||||||
|
PlaylistMove {
|
||||||
|
from: usize,
|
||||||
|
to: usize,
|
||||||
|
},
|
||||||
|
Observe {
|
||||||
|
id: isize,
|
||||||
|
property: String,
|
||||||
|
},
|
||||||
|
PlaylistNext,
|
||||||
|
PlaylistPrev,
|
||||||
|
PlaylistRemove(usize),
|
||||||
|
PlaylistShuffle,
|
||||||
|
Quit,
|
||||||
|
ScriptMessage(Vec<String>),
|
||||||
|
ScriptMessageTo {
|
||||||
|
target: String,
|
||||||
|
args: Vec<String>,
|
||||||
|
},
|
||||||
|
Seek {
|
||||||
|
seconds: f64,
|
||||||
|
option: SeekOptions,
|
||||||
|
},
|
||||||
|
Stop,
|
||||||
|
Unobserve(isize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum MpvDataType {
|
||||||
|
Array(Vec<MpvDataType>),
|
||||||
|
Bool(bool),
|
||||||
|
Double(f64),
|
||||||
|
HashMap(HashMap<String, MpvDataType>),
|
||||||
|
Null,
|
||||||
|
Playlist(Playlist),
|
||||||
|
String(String),
|
||||||
|
Usize(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum NumberChangeOptions {
|
||||||
|
Absolute,
|
||||||
|
Increase,
|
||||||
|
Decrease,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum PlaylistAddOptions {
|
||||||
|
Replace,
|
||||||
|
Append,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum PlaylistAddTypeOptions {
|
||||||
|
File,
|
||||||
|
Playlist,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum SeekOptions {
|
||||||
|
Relative,
|
||||||
|
Absolute,
|
||||||
|
RelativePercent,
|
||||||
|
AbsolutePercent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum Switch {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum ErrorCode {
|
||||||
|
MpvError(String),
|
||||||
|
JsonParseError(String),
|
||||||
|
ConnectError(String),
|
||||||
|
JsonContainsUnexptectedType,
|
||||||
|
UnexpectedResult,
|
||||||
|
UnexpectedValue,
|
||||||
|
MissingValue,
|
||||||
|
UnsupportedType,
|
||||||
|
ValueDoesNotContainBool,
|
||||||
|
ValueDoesNotContainF64,
|
||||||
|
ValueDoesNotContainHashMap,
|
||||||
|
ValueDoesNotContainPlaylist,
|
||||||
|
ValueDoesNotContainString,
|
||||||
|
ValueDoesNotContainUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PlaylistEntry {
|
||||||
|
pub id: usize,
|
||||||
|
pub filename: String,
|
||||||
|
pub title: String,
|
||||||
|
pub current: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Mpv {
|
||||||
|
pub(crate) stream: UnixStream,
|
||||||
|
pub(crate) reader: BufReader<UnixStream>,
|
||||||
|
pub(crate) name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Playlist(pub Vec<PlaylistEntry>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Error(pub ErrorCode);
|
||||||
|
|
||||||
|
impl Drop for Mpv {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
|
||||||
|
Mpv {
|
||||||
|
stream,
|
||||||
|
reader: BufReader::new(cloned_stream),
|
||||||
|
name: self.name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_from(&mut self, source: &Self) {
|
||||||
|
let stream = source.stream.try_clone().expect("cloning UnixStream");
|
||||||
|
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
|
||||||
|
*self = Mpv {
|
||||||
|
stream,
|
||||||
|
reader: BufReader::new(cloned_stream),
|
||||||
|
name: source.name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
impl Display for ErrorCode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
|
||||||
|
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
|
||||||
|
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
|
||||||
|
ErrorCode::JsonContainsUnexptectedType => {
|
||||||
|
f.write_str("Mpv sent a value with an unexpected type")
|
||||||
|
}
|
||||||
|
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
|
||||||
|
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
|
||||||
|
ErrorCode::MissingValue => f.write_str("Missing value"),
|
||||||
|
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
|
||||||
|
ErrorCode::ValueDoesNotContainBool => {
|
||||||
|
f.write_str("The received value is not of type \'std::bool\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainF64 => {
|
||||||
|
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\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainPlaylist => {
|
||||||
|
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainString => {
|
||||||
|
f.write_str("The received value is not of type \'std::string::String\'")
|
||||||
|
}
|
||||||
|
ErrorCode::ValueDoesNotContainUsize => {
|
||||||
|
f.write_str("The received value is not of type \'std::usize\'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GetPropertyTypeHandler: Sized {
|
||||||
|
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetPropertyTypeHandler for bool {
|
||||||
|
fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, Error> {
|
||||||
|
get_mpv_property::<bool>(instance, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetPropertyTypeHandler for String {
|
||||||
|
fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, Error> {
|
||||||
|
get_mpv_property::<String>(instance, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetPropertyTypeHandler for f64 {
|
||||||
|
fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, Error> {
|
||||||
|
get_mpv_property::<f64>(instance, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetPropertyTypeHandler for usize {
|
||||||
|
fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, Error> {
|
||||||
|
get_mpv_property::<usize>(instance, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
|
||||||
|
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, Error> {
|
||||||
|
get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetPropertyTypeHandler for HashMap<String, MpvDataType> {
|
||||||
|
fn get_property_generic(
|
||||||
|
instance: &Mpv,
|
||||||
|
property: &str,
|
||||||
|
) -> Result<HashMap<String, MpvDataType>, Error> {
|
||||||
|
get_mpv_property::<HashMap<String, MpvDataType>>(instance, property)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SetPropertyTypeHandler<T> {
|
||||||
|
fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetPropertyTypeHandler<bool> for bool {
|
||||||
|
fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> {
|
||||||
|
set_mpv_property(instance, property, json!(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetPropertyTypeHandler<String> for String {
|
||||||
|
fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> {
|
||||||
|
set_mpv_property(instance, property, json!(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetPropertyTypeHandler<f64> for f64 {
|
||||||
|
fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> {
|
||||||
|
set_mpv_property(instance, property, json!(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetPropertyTypeHandler<usize> for usize {
|
||||||
|
fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> {
|
||||||
|
set_mpv_property(instance, property, json!(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mpv {
|
||||||
|
pub fn connect(socket: &str) -> Result<Mpv, Error> {
|
||||||
|
match UnixStream::connect(socket) {
|
||||||
|
Ok(stream) => {
|
||||||
|
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
|
||||||
|
return Ok(Mpv {
|
||||||
|
stream,
|
||||||
|
reader: BufReader::new(cloned_stream),
|
||||||
|
name: String::from(socket),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnect(&self) {
|
||||||
|
let mut stream = &self.stream;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_stream_ref(&self) -> &UnixStream {
|
||||||
|
&self.stream
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error> {
|
||||||
|
match get_mpv_property(self, "metadata") {
|
||||||
|
Ok(map) => Ok(map),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_playlist(&self) -> Result<Playlist, Error> {
|
||||||
|
match get_mpv_property::<Vec<PlaylistEntry>>(self, "playlist") {
|
||||||
|
Ok(entries) => Ok(Playlist(entries)),
|
||||||
|
Err(msg) => Err(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Retrieves the property value from mpv.
|
||||||
|
///
|
||||||
|
/// ## Supported types
|
||||||
|
/// - String
|
||||||
|
/// - bool
|
||||||
|
/// - HashMap<String, String> (e.g. for the 'metadata' property)
|
||||||
|
/// - Vec<PlaylistEntry> (for the 'playlist' property)
|
||||||
|
/// - usize
|
||||||
|
/// - f64
|
||||||
|
///
|
||||||
|
/// ## Input arguments
|
||||||
|
///
|
||||||
|
/// - **property** defines the mpv property that should be retrieved
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// 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<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, Error> {
|
||||||
|
T::get_property_generic(self, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Retrieves the property value from mpv.
|
||||||
|
/// The result is always of type String, regardless of the type of the value of the mpv property
|
||||||
|
///
|
||||||
|
/// ## Input arguments
|
||||||
|
///
|
||||||
|
/// - **property** defines the mpv property that should be retrieved
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// 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<String, Error> {
|
||||||
|
get_mpv_property_string(self, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kill(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::Quit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Waits until an mpv event occurs and returns the Event.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// let mut mpv = Mpv::connect("/tmp/mpvsocket")?;
|
||||||
|
/// loop {
|
||||||
|
/// let event = mpv.event_listen()?;
|
||||||
|
/// println!("{:?}", event);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn event_listen(&mut self) -> Result<Event, Error> {
|
||||||
|
listen(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_listen_raw(&mut self) -> String {
|
||||||
|
listen_raw(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::PlaylistNext)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::Observe {
|
||||||
|
id: id,
|
||||||
|
property: property.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unobserve_property(&self, id: isize) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::Unobserve(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&self) -> Result<(), Error> {
|
||||||
|
set_mpv_property(self, "pause", json!(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::PlaylistPrev)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restart(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::Seek {
|
||||||
|
seconds: 0f64,
|
||||||
|
option: SeekOptions::Absolute,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Runs mpv commands. The arguments are passed as a String-Vector reference:
|
||||||
|
///
|
||||||
|
/// ## Input arguments
|
||||||
|
///
|
||||||
|
/// - **command** defines the mpv command that should be executed
|
||||||
|
/// - **args** a slice of &str's which define the arguments
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// 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(MpvCommand::PlaylistShuffle)?;
|
||||||
|
///
|
||||||
|
/// //Run command 'seek' which in this case takes two arguments
|
||||||
|
/// mpv.run_command(MpvCommand::Seek {
|
||||||
|
/// seconds: 0f64,
|
||||||
|
/// option: SeekOptions::Absolute,
|
||||||
|
/// })?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
|
||||||
|
match command {
|
||||||
|
MpvCommand::LoadFile { file, option } => run_mpv_command(
|
||||||
|
self,
|
||||||
|
"loadfile",
|
||||||
|
&[
|
||||||
|
file.as_ref(),
|
||||||
|
match option {
|
||||||
|
PlaylistAddOptions::Append => "append",
|
||||||
|
PlaylistAddOptions::Replace => "replace",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MpvCommand::LoadList { file, option } => run_mpv_command(
|
||||||
|
self,
|
||||||
|
"loadlist",
|
||||||
|
&[
|
||||||
|
file.as_ref(),
|
||||||
|
match option {
|
||||||
|
PlaylistAddOptions::Append => "append",
|
||||||
|
PlaylistAddOptions::Replace => "replace",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property),
|
||||||
|
MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]),
|
||||||
|
MpvCommand::PlaylistMove { from, to } => {
|
||||||
|
run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()])
|
||||||
|
}
|
||||||
|
MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]),
|
||||||
|
MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]),
|
||||||
|
MpvCommand::PlaylistRemove(id) => {
|
||||||
|
run_mpv_command(self, "playlist-remove", &[&id.to_string()])
|
||||||
|
}
|
||||||
|
MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]),
|
||||||
|
MpvCommand::Quit => run_mpv_command(self, "quit", &[]),
|
||||||
|
MpvCommand::ScriptMessage(args) => {
|
||||||
|
let str_args: Vec<_> = args.iter().map(String::as_str).collect();
|
||||||
|
run_mpv_command(self, "script-message", &str_args)
|
||||||
|
}
|
||||||
|
MpvCommand::ScriptMessageTo { target, args } => {
|
||||||
|
let mut cmd_args: Vec<_> = vec![target.as_str()];
|
||||||
|
let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
|
||||||
|
cmd_args.append(&mut str_args);
|
||||||
|
run_mpv_command(self, "script-message-to", &cmd_args)
|
||||||
|
}
|
||||||
|
MpvCommand::Seek { seconds, option } => run_mpv_command(
|
||||||
|
self,
|
||||||
|
"seek",
|
||||||
|
&[
|
||||||
|
&seconds.to_string(),
|
||||||
|
match option {
|
||||||
|
SeekOptions::Absolute => "absolute",
|
||||||
|
SeekOptions::Relative => "relative",
|
||||||
|
SeekOptions::AbsolutePercent => "absolute-percent",
|
||||||
|
SeekOptions::RelativePercent => "relative-percent",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
MpvCommand::Stop => run_mpv_command(self, "stop", &[]),
|
||||||
|
MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a custom command.
|
||||||
|
/// This should only be used if the desired command is not implemented
|
||||||
|
/// with [MpvCommand].
|
||||||
|
pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> {
|
||||||
|
run_mpv_command(self, command, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_add(
|
||||||
|
&self,
|
||||||
|
file: &str,
|
||||||
|
file_type: PlaylistAddTypeOptions,
|
||||||
|
option: PlaylistAddOptions,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match file_type {
|
||||||
|
PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile {
|
||||||
|
file: file.to_string(),
|
||||||
|
option,
|
||||||
|
}),
|
||||||
|
|
||||||
|
PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList {
|
||||||
|
file: file.to_string(),
|
||||||
|
option,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_clear(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::PlaylistClear)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::PlaylistMove { from, to })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> {
|
||||||
|
set_mpv_property(self, "playlist-pos", json!(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> {
|
||||||
|
match get_mpv_property::<usize>(self, "playlist-pos") {
|
||||||
|
Ok(current_id) => self.run_command(MpvCommand::PlaylistMove {
|
||||||
|
from: id,
|
||||||
|
to: current_id + 1,
|
||||||
|
}),
|
||||||
|
Err(msg) => Err(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::PlaylistRemove(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn playlist_shuffle(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::PlaylistShuffle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::Seek { seconds, option })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> {
|
||||||
|
let mut enabled = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(msg) => return Err(msg),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
set_mpv_property(self, "loop-file", json!(enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> {
|
||||||
|
let mut enabled = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(msg) => return Err(msg),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
set_mpv_property(self, "loop-playlist", json!(enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mute(&self, option: Switch) -> Result<(), Error> {
|
||||||
|
let mut enabled = false;
|
||||||
|
match option {
|
||||||
|
Switch::On => enabled = true,
|
||||||
|
Switch::Off => {}
|
||||||
|
Switch::Toggle => match get_mpv_property::<bool>(self, "mute") {
|
||||||
|
Ok(value) => {
|
||||||
|
enabled = !value;
|
||||||
|
}
|
||||||
|
Err(msg) => return Err(msg),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
set_mpv_property(self, "mute", json!(enabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Description
|
||||||
|
///
|
||||||
|
/// Sets the mpv property _<property>_ to _<value>_.
|
||||||
|
///
|
||||||
|
/// ## Supported types
|
||||||
|
/// - String
|
||||||
|
/// - bool
|
||||||
|
/// - f64
|
||||||
|
/// - usize
|
||||||
|
///
|
||||||
|
/// ## Input arguments
|
||||||
|
///
|
||||||
|
/// - **property** defines the mpv property that should be retrieved
|
||||||
|
/// - **value** defines the value of the given mpv property _<property>_
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use mpvipc::{Mpv, Error};
|
||||||
|
/// fn main() -> Result<(), Error> {
|
||||||
|
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
|
||||||
|
/// mpv.set_property("pause", true)?;
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn set_property<T: SetPropertyTypeHandler<T>>(
|
||||||
|
&self,
|
||||||
|
property: &str,
|
||||||
|
value: T,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
T::set_property_generic(self, property, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> {
|
||||||
|
match get_mpv_property::<f64>(self, "speed") {
|
||||||
|
Ok(speed) => match option {
|
||||||
|
NumberChangeOptions::Increase => {
|
||||||
|
set_mpv_property(self, "speed", json!(speed + input_speed))
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberChangeOptions::Decrease => {
|
||||||
|
set_mpv_property(self, "speed", json!(speed - input_speed))
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberChangeOptions::Absolute => {
|
||||||
|
set_mpv_property(self, "speed", json!(input_speed))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(msg) => Err(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> {
|
||||||
|
match get_mpv_property::<f64>(self, "volume") {
|
||||||
|
Ok(volume) => match option {
|
||||||
|
NumberChangeOptions::Increase => {
|
||||||
|
set_mpv_property(self, "volume", json!(volume + input_volume))
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberChangeOptions::Decrease => {
|
||||||
|
set_mpv_property(self, "volume", json!(volume - input_volume))
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberChangeOptions::Absolute => {
|
||||||
|
set_mpv_property(self, "volume", json!(input_volume))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(msg) => Err(msg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&self) -> Result<(), Error> {
|
||||||
|
self.run_command(MpvCommand::Stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle(&self) -> Result<(), Error> {
|
||||||
|
run_mpv_command(self, "cycle", &["pause"])
|
||||||
|
}
|
||||||
|
}
|
311
src/ipc.rs
311
src/ipc.rs
|
@ -1,182 +1,16 @@
|
||||||
|
use crate::message_parser::TypeHandler;
|
||||||
|
|
||||||
|
use self::message_parser::json_array_to_playlist;
|
||||||
|
use self::message_parser::json_array_to_vec;
|
||||||
|
use self::message_parser::json_map_to_hashmap;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use log::{debug, warn};
|
use log::{debug, warn};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use serde_json::Value;
|
||||||
use std::io::prelude::*;
|
use std::io::BufRead;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::iter::Iterator;
|
use std::io::Write;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PlaylistEntry {
|
|
||||||
pub id: usize,
|
|
||||||
pub filename: String,
|
|
||||||
pub title: String,
|
|
||||||
pub current: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TypeHandler: Sized {
|
|
||||||
fn get_value(value: Value) -> Result<Self, Error>;
|
|
||||||
fn as_string(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeHandler for String {
|
|
||||||
fn get_value(value: Value) -> Result<String, Error> {
|
|
||||||
if let Value::Object(map) = value {
|
|
||||||
if let Value::String(ref error) = map["error"] {
|
|
||||||
if error == "success" && map.contains_key("data") {
|
|
||||||
if let Value::String(ref s) = map["data"] {
|
|
||||||
Ok(s.to_string())
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::ValueDoesNotContainString))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::MpvError(error.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
self.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeHandler for bool {
|
|
||||||
fn get_value(value: Value) -> Result<bool, Error> {
|
|
||||||
if let Value::Object(map) = value {
|
|
||||||
if let Value::String(ref error) = map["error"] {
|
|
||||||
if error == "success" && map.contains_key("data") {
|
|
||||||
if let Value::Bool(ref b) = map["data"] {
|
|
||||||
Ok(*b)
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::ValueDoesNotContainBool))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::MpvError(error.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
if *self {
|
|
||||||
"true".to_string()
|
|
||||||
} else {
|
|
||||||
"false".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeHandler for f64 {
|
|
||||||
fn get_value(value: Value) -> Result<f64, Error> {
|
|
||||||
if let Value::Object(map) = value {
|
|
||||||
if let Value::String(ref error) = map["error"] {
|
|
||||||
if error == "success" && map.contains_key("data") {
|
|
||||||
if let Value::Number(ref num) = map["data"] {
|
|
||||||
Ok(num.as_f64().unwrap())
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::ValueDoesNotContainF64))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::MpvError(error.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
self.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeHandler for usize {
|
|
||||||
fn get_value(value: Value) -> Result<usize, Error> {
|
|
||||||
if let Value::Object(map) = value {
|
|
||||||
if let Value::String(ref error) = map["error"] {
|
|
||||||
if error == "success" && map.contains_key("data") {
|
|
||||||
if let Value::Number(ref num) = map["data"] {
|
|
||||||
Ok(num.as_u64().unwrap() as usize)
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::ValueDoesNotContainUsize))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::MpvError(error.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
self.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeHandler for HashMap<String, MpvDataType> {
|
|
||||||
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
|
|
||||||
if let Value::Object(map) = value {
|
|
||||||
if let Value::String(ref error) = map["error"] {
|
|
||||||
if error == "success" && map.contains_key("data") {
|
|
||||||
if let Value::Object(ref inner_map) = map["data"] {
|
|
||||||
Ok(json_map_to_hashmap(inner_map))
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::ValueDoesNotContainHashMap))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::MpvError(error.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
format!("{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeHandler for Vec<PlaylistEntry> {
|
|
||||||
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
|
|
||||||
if let Value::Object(map) = value {
|
|
||||||
if let Value::String(ref error) = map["error"] {
|
|
||||||
if error == "success" && map.contains_key("data") {
|
|
||||||
if let Value::Array(ref playlist_vec) = map["data"] {
|
|
||||||
Ok(json_array_to_playlist(playlist_vec))
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::ValueDoesNotContainPlaylist))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::MpvError(error.to_string())))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error(ErrorCode::UnexpectedValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
|
||||||
format!("{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mpv_property<T: TypeHandler>(instance: &Mpv, property: &str) -> Result<T, Error> {
|
pub fn get_mpv_property<T: TypeHandler>(instance: &Mpv, property: &str) -> Result<T, Error> {
|
||||||
let ipc_string = json!({"command": ["get_property", property]});
|
let ipc_string = json!({"command": ["get_property", property]});
|
||||||
|
@ -455,129 +289,4 @@ fn send_command_sync(instance: &Mpv, command: Value) -> String {
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn json_map_to_hashmap(map: &serde_json::map::Map<String, Value>) -> HashMap<String, MpvDataType> {
|
|
||||||
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
|
|
||||||
for (ref key, ref value) in map.iter() {
|
|
||||||
match **value {
|
|
||||||
Value::Array(ref array) => {
|
|
||||||
output_map.insert(
|
|
||||||
key.to_string(),
|
|
||||||
MpvDataType::Array(json_array_to_vec(array)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Value::Bool(ref b) => {
|
|
||||||
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
|
|
||||||
}
|
|
||||||
Value::Number(ref n) => {
|
|
||||||
if n.is_u64() {
|
|
||||||
output_map.insert(
|
|
||||||
key.to_string(),
|
|
||||||
MpvDataType::Usize(n.as_u64().unwrap() as usize),
|
|
||||||
);
|
|
||||||
} else if n.is_f64() {
|
|
||||||
output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
|
|
||||||
} else {
|
|
||||||
panic!("unimplemented number");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::String(ref s) => {
|
|
||||||
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
|
|
||||||
}
|
|
||||||
Value::Object(ref m) => {
|
|
||||||
output_map.insert(
|
|
||||||
key.to_string(),
|
|
||||||
MpvDataType::HashMap(json_map_to_hashmap(m)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Value::Null => {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output_map
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_array_to_vec(array: &Vec<Value>) -> Vec<MpvDataType> {
|
|
||||||
let mut output: Vec<MpvDataType> = Vec::new();
|
|
||||||
if array.len() > 0 {
|
|
||||||
match array[0] {
|
|
||||||
Value::Array(_) => {
|
|
||||||
for entry in array {
|
|
||||||
if let Value::Array(ref a) = *entry {
|
|
||||||
output.push(MpvDataType::Array(json_array_to_vec(a)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Bool(_) => {
|
|
||||||
for entry in array {
|
|
||||||
if let Value::Bool(ref b) = *entry {
|
|
||||||
output.push(MpvDataType::Bool(*b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Number(_) => {
|
|
||||||
for entry in array {
|
|
||||||
if let Value::Number(ref n) = *entry {
|
|
||||||
if n.is_u64() {
|
|
||||||
output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize));
|
|
||||||
} else if n.is_f64() {
|
|
||||||
output.push(MpvDataType::Double(n.as_f64().unwrap()));
|
|
||||||
} else {
|
|
||||||
panic!("unimplemented number");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Object(_) => {
|
|
||||||
for entry in array {
|
|
||||||
if let Value::Object(ref map) = *entry {
|
|
||||||
output.push(MpvDataType::HashMap(json_map_to_hashmap(map)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::String(_) => {
|
|
||||||
for entry in array {
|
|
||||||
if let Value::String(ref s) = *entry {
|
|
||||||
output.push(MpvDataType::String(s.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Null => {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_array_to_playlist(array: &Vec<Value>) -> Vec<PlaylistEntry> {
|
|
||||||
let mut output: Vec<PlaylistEntry> = Vec::new();
|
|
||||||
for (id, entry) in array.iter().enumerate() {
|
|
||||||
let mut filename: String = String::new();
|
|
||||||
let mut title: String = String::new();
|
|
||||||
let mut current: bool = false;
|
|
||||||
if let Value::String(ref f) = entry["filename"] {
|
|
||||||
filename = f.to_string();
|
|
||||||
}
|
|
||||||
if let Value::String(ref t) = entry["title"] {
|
|
||||||
title = t.to_string();
|
|
||||||
}
|
|
||||||
if let Value::Bool(ref b) = entry["current"] {
|
|
||||||
current = *b;
|
|
||||||
}
|
|
||||||
output.push(PlaylistEntry {
|
|
||||||
id,
|
|
||||||
filename,
|
|
||||||
title,
|
|
||||||
current,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
output
|
|
||||||
}
|
|
738
src/lib.rs
738
src/lib.rs
|
@ -1,735 +1,5 @@
|
||||||
pub mod ipc;
|
mod api;
|
||||||
|
mod ipc;
|
||||||
|
mod message_parser;
|
||||||
|
|
||||||
use ipc::*;
|
pub use api::*;
|
||||||
use serde_json::{json, Value};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Display};
|
|
||||||
use std::io::{BufReader, Read};
|
|
||||||
use std::os::unix::net::UnixStream;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
Shutdown,
|
|
||||||
StartFile,
|
|
||||||
EndFile,
|
|
||||||
FileLoaded,
|
|
||||||
TracksChanged,
|
|
||||||
TrackSwitched,
|
|
||||||
Idle,
|
|
||||||
Pause,
|
|
||||||
Unpause,
|
|
||||||
Tick,
|
|
||||||
VideoReconfig,
|
|
||||||
AudioReconfig,
|
|
||||||
MetadataUpdate,
|
|
||||||
Seek,
|
|
||||||
PlaybackRestart,
|
|
||||||
PropertyChange { id: usize, property: Property },
|
|
||||||
ChapterChange,
|
|
||||||
ClientMessage { args: Vec<String> },
|
|
||||||
Unimplemented,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Property {
|
|
||||||
Path(Option<String>),
|
|
||||||
Pause(bool),
|
|
||||||
PlaybackTime(Option<f64>),
|
|
||||||
Duration(Option<f64>),
|
|
||||||
Metadata(Option<HashMap<String, MpvDataType>>),
|
|
||||||
Unknown { name: String, data: MpvDataType },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum MpvCommand {
|
|
||||||
LoadFile {
|
|
||||||
file: String,
|
|
||||||
option: PlaylistAddOptions,
|
|
||||||
},
|
|
||||||
LoadList {
|
|
||||||
file: String,
|
|
||||||
option: PlaylistAddOptions,
|
|
||||||
},
|
|
||||||
PlaylistClear,
|
|
||||||
PlaylistMove {
|
|
||||||
from: usize,
|
|
||||||
to: usize,
|
|
||||||
},
|
|
||||||
Observe {
|
|
||||||
id: isize,
|
|
||||||
property: String,
|
|
||||||
},
|
|
||||||
PlaylistNext,
|
|
||||||
PlaylistPrev,
|
|
||||||
PlaylistRemove(usize),
|
|
||||||
PlaylistShuffle,
|
|
||||||
Quit,
|
|
||||||
ScriptMessage(Vec<String>),
|
|
||||||
ScriptMessageTo {
|
|
||||||
target: String,
|
|
||||||
args: Vec<String>,
|
|
||||||
},
|
|
||||||
Seek {
|
|
||||||
seconds: f64,
|
|
||||||
option: SeekOptions,
|
|
||||||
},
|
|
||||||
Stop,
|
|
||||||
Unobserve(isize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum MpvDataType {
|
|
||||||
Array(Vec<MpvDataType>),
|
|
||||||
Bool(bool),
|
|
||||||
Double(f64),
|
|
||||||
HashMap(HashMap<String, MpvDataType>),
|
|
||||||
Null,
|
|
||||||
Playlist(Playlist),
|
|
||||||
String(String),
|
|
||||||
Usize(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum NumberChangeOptions {
|
|
||||||
Absolute,
|
|
||||||
Increase,
|
|
||||||
Decrease,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum PlaylistAddOptions {
|
|
||||||
Replace,
|
|
||||||
Append,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum PlaylistAddTypeOptions {
|
|
||||||
File,
|
|
||||||
Playlist,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum SeekOptions {
|
|
||||||
Relative,
|
|
||||||
Absolute,
|
|
||||||
RelativePercent,
|
|
||||||
AbsolutePercent,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum Switch {
|
|
||||||
On,
|
|
||||||
Off,
|
|
||||||
Toggle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum ErrorCode {
|
|
||||||
MpvError(String),
|
|
||||||
JsonParseError(String),
|
|
||||||
ConnectError(String),
|
|
||||||
JsonContainsUnexptectedType,
|
|
||||||
UnexpectedResult,
|
|
||||||
UnexpectedValue,
|
|
||||||
MissingValue,
|
|
||||||
UnsupportedType,
|
|
||||||
ValueDoesNotContainBool,
|
|
||||||
ValueDoesNotContainF64,
|
|
||||||
ValueDoesNotContainHashMap,
|
|
||||||
ValueDoesNotContainPlaylist,
|
|
||||||
ValueDoesNotContainString,
|
|
||||||
ValueDoesNotContainUsize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Mpv {
|
|
||||||
stream: UnixStream,
|
|
||||||
reader: BufReader<UnixStream>,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Playlist(pub Vec<PlaylistEntry>);
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Error(pub ErrorCode);
|
|
||||||
|
|
||||||
impl Drop for Mpv {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
|
|
||||||
Mpv {
|
|
||||||
stream,
|
|
||||||
reader: BufReader::new(cloned_stream),
|
|
||||||
name: self.name.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_from(&mut self, source: &Self) {
|
|
||||||
let stream = source.stream.try_clone().expect("cloning UnixStream");
|
|
||||||
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
|
|
||||||
*self = Mpv {
|
|
||||||
stream,
|
|
||||||
reader: BufReader::new(cloned_stream),
|
|
||||||
name: source.name.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
Display::fmt(&self.0, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
impl Display for ErrorCode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
|
|
||||||
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
|
|
||||||
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
|
|
||||||
ErrorCode::JsonContainsUnexptectedType => {
|
|
||||||
f.write_str("Mpv sent a value with an unexpected type")
|
|
||||||
}
|
|
||||||
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
|
|
||||||
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
|
|
||||||
ErrorCode::MissingValue => f.write_str("Missing value"),
|
|
||||||
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
|
|
||||||
ErrorCode::ValueDoesNotContainBool => {
|
|
||||||
f.write_str("The received value is not of type \'std::bool\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainF64 => {
|
|
||||||
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\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainPlaylist => {
|
|
||||||
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainString => {
|
|
||||||
f.write_str("The received value is not of type \'std::string::String\'")
|
|
||||||
}
|
|
||||||
ErrorCode::ValueDoesNotContainUsize => {
|
|
||||||
f.write_str("The received value is not of type \'std::usize\'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait GetPropertyTypeHandler: Sized {
|
|
||||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyTypeHandler for bool {
|
|
||||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, Error> {
|
|
||||||
get_mpv_property::<bool>(instance, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyTypeHandler for String {
|
|
||||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, Error> {
|
|
||||||
get_mpv_property::<String>(instance, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyTypeHandler for f64 {
|
|
||||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, Error> {
|
|
||||||
get_mpv_property::<f64>(instance, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyTypeHandler for usize {
|
|
||||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, Error> {
|
|
||||||
get_mpv_property::<usize>(instance, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
|
|
||||||
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, Error> {
|
|
||||||
get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyTypeHandler for HashMap<String, MpvDataType> {
|
|
||||||
fn get_property_generic(
|
|
||||||
instance: &Mpv,
|
|
||||||
property: &str,
|
|
||||||
) -> Result<HashMap<String, MpvDataType>, Error> {
|
|
||||||
get_mpv_property::<HashMap<String, MpvDataType>>(instance, property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SetPropertyTypeHandler<T> {
|
|
||||||
fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SetPropertyTypeHandler<bool> for bool {
|
|
||||||
fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> {
|
|
||||||
set_mpv_property(instance, property, json!(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SetPropertyTypeHandler<String> for String {
|
|
||||||
fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> {
|
|
||||||
set_mpv_property(instance, property, json!(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SetPropertyTypeHandler<f64> for f64 {
|
|
||||||
fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> {
|
|
||||||
set_mpv_property(instance, property, json!(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SetPropertyTypeHandler<usize> for usize {
|
|
||||||
fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> {
|
|
||||||
set_mpv_property(instance, property, json!(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mpv {
|
|
||||||
pub fn connect(socket: &str) -> Result<Mpv, Error> {
|
|
||||||
match UnixStream::connect(socket) {
|
|
||||||
Ok(stream) => {
|
|
||||||
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
|
|
||||||
return Ok(Mpv {
|
|
||||||
stream,
|
|
||||||
reader: BufReader::new(cloned_stream),
|
|
||||||
name: String::from(socket),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disconnect(&self) {
|
|
||||||
let mut stream = &self.stream;
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_stream_ref(&self) -> &UnixStream {
|
|
||||||
&self.stream
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error> {
|
|
||||||
match get_mpv_property(self, "metadata") {
|
|
||||||
Ok(map) => Ok(map),
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_playlist(&self) -> Result<Playlist, Error> {
|
|
||||||
match get_mpv_property::<Vec<PlaylistEntry>>(self, "playlist") {
|
|
||||||
Ok(entries) => Ok(Playlist(entries)),
|
|
||||||
Err(msg) => Err(msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
|
||||||
///
|
|
||||||
/// Retrieves the property value from mpv.
|
|
||||||
///
|
|
||||||
/// ## Supported types
|
|
||||||
/// - String
|
|
||||||
/// - bool
|
|
||||||
/// - HashMap<String, String> (e.g. for the 'metadata' property)
|
|
||||||
/// - Vec<PlaylistEntry> (for the 'playlist' property)
|
|
||||||
/// - usize
|
|
||||||
/// - f64
|
|
||||||
///
|
|
||||||
/// ## Input arguments
|
|
||||||
///
|
|
||||||
/// - **property** defines the mpv property that should be retrieved
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// 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<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, Error> { T::get_property_generic(self, property)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
|
||||||
///
|
|
||||||
/// Retrieves the property value from mpv.
|
|
||||||
/// The result is always of type String, regardless of the type of the value of the mpv property
|
|
||||||
///
|
|
||||||
/// ## Input arguments
|
|
||||||
///
|
|
||||||
/// - **property** defines the mpv property that should be retrieved
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// 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<String, Error> {
|
|
||||||
get_mpv_property_string(self, property)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kill(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::Quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
|
||||||
///
|
|
||||||
/// Waits until an mpv event occurs and returns the Event.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// let mut mpv = Mpv::connect("/tmp/mpvsocket")?;
|
|
||||||
/// loop {
|
|
||||||
/// let event = mpv.event_listen()?;
|
|
||||||
/// println!("{:?}", event);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn event_listen(&mut self) -> Result<Event, Error> {
|
|
||||||
listen(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_listen_raw(&mut self) -> String {
|
|
||||||
listen_raw(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::PlaylistNext)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::Observe {
|
|
||||||
id: id,
|
|
||||||
property: property.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unobserve_property(&self, id: isize) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::Unobserve(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pause(&self) -> Result<(), Error> {
|
|
||||||
set_mpv_property(self, "pause", json!(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prev(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::PlaylistPrev)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restart(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::Seek {
|
|
||||||
seconds: 0f64,
|
|
||||||
option: SeekOptions::Absolute,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
|
||||||
///
|
|
||||||
/// Runs mpv commands. The arguments are passed as a String-Vector reference:
|
|
||||||
///
|
|
||||||
/// ## Input arguments
|
|
||||||
///
|
|
||||||
/// - **command** defines the mpv command that should be executed
|
|
||||||
/// - **args** a slice of &str's which define the arguments
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// 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(MpvCommand::PlaylistShuffle)?;
|
|
||||||
///
|
|
||||||
/// //Run command 'seek' which in this case takes two arguments
|
|
||||||
/// mpv.run_command(MpvCommand::Seek {
|
|
||||||
/// seconds: 0f64,
|
|
||||||
/// option: SeekOptions::Absolute,
|
|
||||||
/// })?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
|
|
||||||
match command {
|
|
||||||
MpvCommand::LoadFile { file, option } => run_mpv_command(
|
|
||||||
self,
|
|
||||||
"loadfile",
|
|
||||||
&[
|
|
||||||
file.as_ref(),
|
|
||||||
match option {
|
|
||||||
PlaylistAddOptions::Append => "append",
|
|
||||||
PlaylistAddOptions::Replace => "replace",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MpvCommand::LoadList { file, option } => run_mpv_command(
|
|
||||||
self,
|
|
||||||
"loadlist",
|
|
||||||
&[
|
|
||||||
file.as_ref(),
|
|
||||||
match option {
|
|
||||||
PlaylistAddOptions::Append => "append",
|
|
||||||
PlaylistAddOptions::Replace => "replace",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property),
|
|
||||||
MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]),
|
|
||||||
MpvCommand::PlaylistMove { from, to } => {
|
|
||||||
run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()])
|
|
||||||
}
|
|
||||||
MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]),
|
|
||||||
MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]),
|
|
||||||
MpvCommand::PlaylistRemove(id) => {
|
|
||||||
run_mpv_command(self, "playlist-remove", &[&id.to_string()])
|
|
||||||
}
|
|
||||||
MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]),
|
|
||||||
MpvCommand::Quit => run_mpv_command(self, "quit", &[]),
|
|
||||||
MpvCommand::ScriptMessage(args) => {
|
|
||||||
let str_args: Vec<_> = args.iter().map(String::as_str).collect();
|
|
||||||
run_mpv_command(self, "script-message", &str_args)
|
|
||||||
}
|
|
||||||
MpvCommand::ScriptMessageTo { target, args } => {
|
|
||||||
let mut cmd_args: Vec<_> = vec![target.as_str()];
|
|
||||||
let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
|
|
||||||
cmd_args.append(&mut str_args);
|
|
||||||
run_mpv_command(self, "script-message-to", &cmd_args)
|
|
||||||
}
|
|
||||||
MpvCommand::Seek { seconds, option } => run_mpv_command(
|
|
||||||
self,
|
|
||||||
"seek",
|
|
||||||
&[
|
|
||||||
&seconds.to_string(),
|
|
||||||
match option {
|
|
||||||
SeekOptions::Absolute => "absolute",
|
|
||||||
SeekOptions::Relative => "relative",
|
|
||||||
SeekOptions::AbsolutePercent => "absolute-percent",
|
|
||||||
SeekOptions::RelativePercent => "relative-percent",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
),
|
|
||||||
MpvCommand::Stop => run_mpv_command(self, "stop", &[]),
|
|
||||||
MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run a custom command.
|
|
||||||
/// This should only be used if the desired command is not implemented
|
|
||||||
/// with [MpvCommand].
|
|
||||||
pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> {
|
|
||||||
run_mpv_command(self, command, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_add(
|
|
||||||
&self,
|
|
||||||
file: &str,
|
|
||||||
file_type: PlaylistAddTypeOptions,
|
|
||||||
option: PlaylistAddOptions,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
match file_type {
|
|
||||||
PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile {
|
|
||||||
file: file.to_string(),
|
|
||||||
option,
|
|
||||||
}),
|
|
||||||
|
|
||||||
PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList {
|
|
||||||
file: file.to_string(),
|
|
||||||
option,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_clear(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::PlaylistClear)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::PlaylistMove { from, to })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> {
|
|
||||||
set_mpv_property(self, "playlist-pos", json!(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> {
|
|
||||||
match get_mpv_property::<usize>(self, "playlist-pos") {
|
|
||||||
Ok(current_id) => self.run_command(MpvCommand::PlaylistMove {
|
|
||||||
from: id,
|
|
||||||
to: current_id + 1,
|
|
||||||
}),
|
|
||||||
Err(msg) => Err(msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::PlaylistRemove(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn playlist_shuffle(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::PlaylistShuffle)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::Seek { seconds, option })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> {
|
|
||||||
let mut enabled = false;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(msg) => return Err(msg),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
set_mpv_property(self, "loop-file", json!(enabled))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> {
|
|
||||||
let mut enabled = false;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(msg) => return Err(msg),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
set_mpv_property(self, "loop-playlist", json!(enabled))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_mute(&self, option: Switch) -> Result<(), Error> {
|
|
||||||
let mut enabled = false;
|
|
||||||
match option {
|
|
||||||
Switch::On => enabled = true,
|
|
||||||
Switch::Off => {}
|
|
||||||
Switch::Toggle => match get_mpv_property::<bool>(self, "mute") {
|
|
||||||
Ok(value) => {
|
|
||||||
enabled = !value;
|
|
||||||
}
|
|
||||||
Err(msg) => return Err(msg),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
set_mpv_property(self, "mute", json!(enabled))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Description
|
|
||||||
///
|
|
||||||
/// Sets the mpv property _<property>_ to _<value>_.
|
|
||||||
///
|
|
||||||
/// ## Supported types
|
|
||||||
/// - String
|
|
||||||
/// - bool
|
|
||||||
/// - f64
|
|
||||||
/// - usize
|
|
||||||
///
|
|
||||||
/// ## Input arguments
|
|
||||||
///
|
|
||||||
/// - **property** defines the mpv property that should be retrieved
|
|
||||||
/// - **value** defines the value of the given mpv property _<property>_
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use mpvipc::{Mpv, Error};
|
|
||||||
/// fn main() -> Result<(), Error> {
|
|
||||||
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
|
|
||||||
/// mpv.set_property("pause", true)?;
|
|
||||||
/// Ok(())
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn set_property<T: SetPropertyTypeHandler<T>>(
|
|
||||||
&self,
|
|
||||||
property: &str,
|
|
||||||
value: T,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
T::set_property_generic(self, property, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> {
|
|
||||||
match get_mpv_property::<f64>(self, "speed") {
|
|
||||||
Ok(speed) => match option {
|
|
||||||
NumberChangeOptions::Increase => {
|
|
||||||
set_mpv_property(self, "speed", json!(speed + input_speed))
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberChangeOptions::Decrease => {
|
|
||||||
set_mpv_property(self, "speed", json!(speed - input_speed))
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberChangeOptions::Absolute => {
|
|
||||||
set_mpv_property(self, "speed", json!(input_speed))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(msg) => Err(msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> {
|
|
||||||
match get_mpv_property::<f64>(self, "volume") {
|
|
||||||
Ok(volume) => match option {
|
|
||||||
NumberChangeOptions::Increase => {
|
|
||||||
set_mpv_property(self, "volume", json!(volume + input_volume))
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberChangeOptions::Decrease => {
|
|
||||||
set_mpv_property(self, "volume", json!(volume - input_volume))
|
|
||||||
}
|
|
||||||
|
|
||||||
NumberChangeOptions::Absolute => {
|
|
||||||
set_mpv_property(self, "volume", json!(input_volume))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(msg) => Err(msg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&self) -> Result<(), Error> {
|
|
||||||
self.run_command(MpvCommand::Stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle(&self) -> Result<(), Error> {
|
|
||||||
run_mpv_command(self, "cycle", &["pause"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::{Error, ErrorCode, MpvDataType, PlaylistEntry};
|
||||||
|
|
||||||
|
pub trait TypeHandler: Sized {
|
||||||
|
fn get_value(value: Value) -> Result<Self, Error>;
|
||||||
|
fn as_string(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeHandler for String {
|
||||||
|
fn get_value(value: Value) -> Result<String, Error> {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||||
|
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||||
|
.and_then(|(error, data)| match error {
|
||||||
|
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||||
|
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
})
|
||||||
|
.and_then(|d| {
|
||||||
|
d.as_str()
|
||||||
|
.ok_or(Error(ErrorCode::ValueDoesNotContainString))
|
||||||
|
})
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeHandler for bool {
|
||||||
|
fn get_value(value: Value) -> Result<bool, Error> {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||||
|
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||||
|
.and_then(|(error, data)| match error {
|
||||||
|
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||||
|
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
})
|
||||||
|
.and_then(|d| d.as_bool().ok_or(Error(ErrorCode::ValueDoesNotContainBool)))
|
||||||
|
}
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
if *self {
|
||||||
|
"true".to_string()
|
||||||
|
} else {
|
||||||
|
"false".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeHandler for f64 {
|
||||||
|
fn get_value(value: Value) -> Result<f64, Error> {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||||
|
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||||
|
.and_then(|(error, data)| match error {
|
||||||
|
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||||
|
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
})
|
||||||
|
.and_then(|d| d.as_f64().ok_or(Error(ErrorCode::ValueDoesNotContainF64)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeHandler for usize {
|
||||||
|
fn get_value(value: Value) -> Result<usize, Error> {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||||
|
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||||
|
.and_then(|(error, data)| match error {
|
||||||
|
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||||
|
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
})
|
||||||
|
.and_then(|d| d.as_u64().ok_or(Error(ErrorCode::ValueDoesNotContainUsize)))
|
||||||
|
.map(|u| u as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeHandler for HashMap<String, MpvDataType> {
|
||||||
|
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||||
|
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||||
|
.and_then(|(error, data)| match error {
|
||||||
|
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||||
|
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
})
|
||||||
|
.and_then(|d| {
|
||||||
|
d.as_object()
|
||||||
|
.ok_or(Error(ErrorCode::ValueDoesNotContainHashMap))
|
||||||
|
})
|
||||||
|
.map(json_map_to_hashmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeHandler for Vec<PlaylistEntry> {
|
||||||
|
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
|
||||||
|
value
|
||||||
|
.as_object()
|
||||||
|
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
|
||||||
|
.ok_or(Error(ErrorCode::UnexpectedValue))
|
||||||
|
.and_then(|(error, data)| match error {
|
||||||
|
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
|
||||||
|
None => Err(Error(ErrorCode::UnexpectedValue)),
|
||||||
|
})
|
||||||
|
.and_then(|d| {
|
||||||
|
d.as_array()
|
||||||
|
.ok_or(Error(ErrorCode::ValueDoesNotContainPlaylist))
|
||||||
|
})
|
||||||
|
.map(json_array_to_playlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
format!("{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn json_map_to_hashmap(map: &serde_json::map::Map<String, Value>) -> HashMap<String, MpvDataType> {
|
||||||
|
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
|
||||||
|
for (ref key, ref value) in map.iter() {
|
||||||
|
match **value {
|
||||||
|
Value::Array(ref array) => {
|
||||||
|
output_map.insert(
|
||||||
|
key.to_string(),
|
||||||
|
MpvDataType::Array(json_array_to_vec(array)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Value::Bool(ref b) => {
|
||||||
|
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
|
||||||
|
}
|
||||||
|
Value::Number(ref n) => {
|
||||||
|
if n.is_u64() {
|
||||||
|
output_map.insert(
|
||||||
|
key.to_string(),
|
||||||
|
MpvDataType::Usize(n.as_u64().unwrap() as usize),
|
||||||
|
);
|
||||||
|
} else if n.is_f64() {
|
||||||
|
output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
|
||||||
|
} else {
|
||||||
|
panic!("unimplemented number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::String(ref s) => {
|
||||||
|
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
|
||||||
|
}
|
||||||
|
Value::Object(ref m) => {
|
||||||
|
output_map.insert(
|
||||||
|
key.to_string(),
|
||||||
|
MpvDataType::HashMap(json_map_to_hashmap(m)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Value::Null => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output_map
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn json_array_to_vec(array: &Vec<Value>) -> Vec<MpvDataType> {
|
||||||
|
let mut output: Vec<MpvDataType> = Vec::new();
|
||||||
|
if array.len() > 0 {
|
||||||
|
match array[0] {
|
||||||
|
Value::Array(_) => {
|
||||||
|
for entry in array {
|
||||||
|
if let Value::Array(ref a) = *entry {
|
||||||
|
output.push(MpvDataType::Array(json_array_to_vec(a)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Bool(_) => {
|
||||||
|
for entry in array {
|
||||||
|
if let Value::Bool(ref b) = *entry {
|
||||||
|
output.push(MpvDataType::Bool(*b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Number(_) => {
|
||||||
|
for entry in array {
|
||||||
|
if let Value::Number(ref n) = *entry {
|
||||||
|
if n.is_u64() {
|
||||||
|
output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize));
|
||||||
|
} else if n.is_f64() {
|
||||||
|
output.push(MpvDataType::Double(n.as_f64().unwrap()));
|
||||||
|
} else {
|
||||||
|
panic!("unimplemented number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Object(_) => {
|
||||||
|
for entry in array {
|
||||||
|
if let Value::Object(ref map) = *entry {
|
||||||
|
output.push(MpvDataType::HashMap(json_map_to_hashmap(map)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::String(_) => {
|
||||||
|
for entry in array {
|
||||||
|
if let Value::String(ref s) = *entry {
|
||||||
|
output.push(MpvDataType::String(s.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Null => {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn json_array_to_playlist(array: &Vec<Value>) -> Vec<PlaylistEntry> {
|
||||||
|
let mut output: Vec<PlaylistEntry> = Vec::new();
|
||||||
|
for (id, entry) in array.iter().enumerate() {
|
||||||
|
let mut filename: String = String::new();
|
||||||
|
let mut title: String = String::new();
|
||||||
|
let mut current: bool = false;
|
||||||
|
if let Value::String(ref f) = entry["filename"] {
|
||||||
|
filename = f.to_string();
|
||||||
|
}
|
||||||
|
if let Value::String(ref t) = entry["title"] {
|
||||||
|
title = t.to_string();
|
||||||
|
}
|
||||||
|
if let Value::Bool(ref b) = entry["current"] {
|
||||||
|
current = *b;
|
||||||
|
}
|
||||||
|
output.push(PlaylistEntry {
|
||||||
|
id,
|
||||||
|
filename,
|
||||||
|
title,
|
||||||
|
current,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
Loading…
Reference in New Issue