reuse `property_parser` for highlevel api, add more highlevel functions
This commit is contained in:
parent
66d54a58aa
commit
eb81d7c463
|
@ -1,5 +1,5 @@
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mpvipc::{parse_event_property, Event, Mpv, MpvDataType, MpvError, MpvExt, Property};
|
use mpvipc::{parse_property, Event, Mpv, MpvDataType, MpvError, MpvExt, Property};
|
||||||
|
|
||||||
fn seconds_to_hms(total: f64) -> String {
|
fn seconds_to_hms(total: f64) -> String {
|
||||||
let total = total as u64;
|
let total = total as u64;
|
||||||
|
@ -25,34 +25,36 @@ async fn main() -> Result<(), MpvError> {
|
||||||
let mut events = mpv.get_event_stream().await;
|
let mut events = mpv.get_event_stream().await;
|
||||||
while let Some(Ok(event)) = events.next().await {
|
while let Some(Ok(event)) = events.next().await {
|
||||||
match event {
|
match event {
|
||||||
mpvipc::Event::PropertyChange { .. } => match parse_event_property(event)? {
|
mpvipc::Event::PropertyChange { name, data, .. } => {
|
||||||
(1, Property::Path(Some(value))) => println!("\nPlaying: {}", value),
|
match parse_property(&name, data)? {
|
||||||
(2, Property::Pause(value)) => {
|
Property::Path(Some(value)) => println!("\nPlaying: {}", value),
|
||||||
println!("Pause: {}", value);
|
Property::Pause(value) => {
|
||||||
}
|
println!("Pause: {}", value);
|
||||||
(3, Property::PlaybackTime(Some(value))) => {
|
|
||||||
println!("Playback time: {}", seconds_to_hms(value));
|
|
||||||
}
|
|
||||||
(4, Property::Duration(Some(value))) => {
|
|
||||||
println!("Duration: {}", seconds_to_hms(value));
|
|
||||||
}
|
|
||||||
(5, 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") {
|
Property::PlaybackTime(Some(value)) => {
|
||||||
println!(" Album: {}", value);
|
println!("Playback time: {}", seconds_to_hms(value));
|
||||||
}
|
}
|
||||||
if let Some(MpvDataType::String(value)) = value.get("TITLE") {
|
Property::Duration(Some(value)) => {
|
||||||
println!(" Title: {}", value);
|
println!("Duration: {}", seconds_to_hms(value));
|
||||||
}
|
}
|
||||||
if let Some(MpvDataType::String(value)) = value.get("TRACK") {
|
Property::Metadata(Some(value)) => {
|
||||||
println!(" Track: {}", 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
_ => (),
|
}
|
||||||
},
|
|
||||||
Event::Shutdown => return Ok(()),
|
Event::Shutdown => return Ok(()),
|
||||||
Event::Unimplemented(_) => panic!("Unimplemented event"),
|
Event::Unimplemented(_) => panic!("Unimplemented event"),
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::MpvDataType;
|
use crate::{MpvDataType, Property};
|
||||||
|
|
||||||
/// Any error that can occur when interacting with mpv.
|
/// Any error that can occur when interacting with mpv.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -43,6 +43,9 @@ pub enum MpvError {
|
||||||
map: Map<String, Value>,
|
map: Map<String, Value>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("Unexpected property: {0:?}")]
|
||||||
|
UnexpectedProperty(Property),
|
||||||
|
|
||||||
#[error("Unknown error: {0}")]
|
#[error("Unknown error: {0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//! High-level API extension for [`Mpv`].
|
//! High-level API extension for [`Mpv`].
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
IntoRawCommandPart, Mpv, MpvCommand, MpvDataType, MpvError, Playlist, PlaylistAddOptions,
|
parse_property, IntoRawCommandPart, LoopProperty, Mpv, MpvCommand, MpvDataType, MpvError,
|
||||||
PlaylistEntry, SeekOptions,
|
Playlist, PlaylistAddOptions, Property, SeekOptions,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -44,35 +44,7 @@ pub enum PlaylistAddTypeOptions {
|
||||||
// TODO: fix this
|
// TODO: fix this
|
||||||
#[allow(async_fn_in_trait)]
|
#[allow(async_fn_in_trait)]
|
||||||
pub trait MpvExt {
|
pub trait MpvExt {
|
||||||
/// Stop the player completely (as opposed to pausing),
|
// COMMANDS
|
||||||
/// removing the pointer to the current video.
|
|
||||||
async fn stop(&self) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Set the volume of the player.
|
|
||||||
async fn set_volume(
|
|
||||||
&self,
|
|
||||||
input_volume: f64,
|
|
||||||
option: NumberChangeOptions,
|
|
||||||
) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Set the playback speed of the player.
|
|
||||||
async fn set_speed(
|
|
||||||
&self,
|
|
||||||
input_speed: f64,
|
|
||||||
option: NumberChangeOptions,
|
|
||||||
) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Toggle/set the pause state of the player.
|
|
||||||
async fn set_playback(&self, option: Switch) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Toggle/set the mute state of the player.
|
|
||||||
async fn set_mute(&self, option: Switch) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Toggle/set whether the player should loop the current playlist.
|
|
||||||
async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Toggle/set whether the player should loop the current video.
|
|
||||||
async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError>;
|
|
||||||
|
|
||||||
/// Seek to a specific position in the current video.
|
/// Seek to a specific position in the current video.
|
||||||
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError>;
|
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError>;
|
||||||
|
@ -132,74 +104,122 @@ pub trait MpvExt {
|
||||||
/// it to exit itself. If mpv is stuck, it may not respond to this command.
|
/// it to exit itself. If mpv is stuck, it may not respond to this command.
|
||||||
async fn kill(&self) -> Result<(), MpvError>;
|
async fn kill(&self) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
/// Stop the player completely (as opposed to pausing),
|
||||||
|
/// removing the pointer to the current video.
|
||||||
|
async fn stop(&self) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
// SETTERS
|
||||||
|
|
||||||
|
/// Set the volume of the player.
|
||||||
|
async fn set_volume(
|
||||||
|
&self,
|
||||||
|
input_volume: f64,
|
||||||
|
option: NumberChangeOptions,
|
||||||
|
) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
/// Set the playback speed of the player.
|
||||||
|
async fn set_speed(
|
||||||
|
&self,
|
||||||
|
input_speed: f64,
|
||||||
|
option: NumberChangeOptions,
|
||||||
|
) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
/// Toggle/set the pause state of the player.
|
||||||
|
async fn set_playback(&self, option: Switch) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
/// Toggle/set the mute state of the player.
|
||||||
|
async fn set_mute(&self, option: Switch) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
/// Toggle/set whether the player should loop the current playlist.
|
||||||
|
async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
/// Toggle/set whether the player should loop the current video.
|
||||||
|
async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError>;
|
||||||
|
|
||||||
|
// GETTERS
|
||||||
|
|
||||||
/// Get a list of all entries in the playlist.
|
/// Get a list of all entries in the playlist.
|
||||||
async fn get_playlist(&self) -> Result<Playlist, MpvError>;
|
async fn get_playlist(&self) -> Result<Playlist, MpvError>;
|
||||||
|
|
||||||
/// Get metadata about the current video.
|
/// Get metadata about the current video.
|
||||||
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError>;
|
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError>;
|
||||||
|
|
||||||
|
/// Get the path of the current video.
|
||||||
|
async fn get_file_path(&self) -> Result<String, MpvError>;
|
||||||
|
|
||||||
|
/// Get the current volume of the player.
|
||||||
|
async fn get_volume(&self) -> Result<f64, MpvError>;
|
||||||
|
|
||||||
|
/// Get the playback speed of the player.
|
||||||
|
async fn get_speed(&self) -> Result<f64, MpvError>;
|
||||||
|
|
||||||
|
/// Get the current position in the current video.
|
||||||
|
async fn get_time_pos(&self) -> Result<f64, MpvError>;
|
||||||
|
|
||||||
|
/// Get the amount of time remaining in the current video.
|
||||||
|
async fn get_time_remaining(&self) -> Result<f64, MpvError>;
|
||||||
|
|
||||||
|
/// Get the total duration of the current video.
|
||||||
|
async fn get_duration(&self) -> Result<f64, MpvError>;
|
||||||
|
|
||||||
|
/// Get the current position in the playlist.
|
||||||
|
async fn get_playlist_pos(&self) -> Result<usize, MpvError>;
|
||||||
|
|
||||||
|
// BOOLEAN GETTERS
|
||||||
|
|
||||||
|
/// Check whether the player is muted.
|
||||||
|
async fn is_muted(&self) -> Result<bool, MpvError>;
|
||||||
|
|
||||||
|
/// Check whether the player is currently playing.
|
||||||
|
async fn is_playing(&self) -> Result<bool, MpvError>;
|
||||||
|
|
||||||
|
/// Check whether the player is looping the current playlist.
|
||||||
|
async fn playlist_is_looping(&self) -> Result<LoopProperty, MpvError>;
|
||||||
|
|
||||||
|
/// Check whether the player is looping the current video.
|
||||||
|
async fn file_is_looping(&self) -> Result<LoopProperty, MpvError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MpvExt for Mpv {
|
impl MpvExt for Mpv {
|
||||||
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError> {
|
// COMMANDS
|
||||||
self.get_property("metadata")
|
|
||||||
.await?
|
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError> {
|
||||||
.ok_or(MpvError::MissingMpvData)
|
self.run_command(MpvCommand::Seek { seconds, option }).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_playlist(&self) -> Result<Playlist, MpvError> {
|
async fn playlist_shuffle(&self) -> Result<(), MpvError> {
|
||||||
self.get_property::<Vec<PlaylistEntry>>("playlist")
|
self.run_command(MpvCommand::PlaylistShuffle).await
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)
|
|
||||||
.map(Playlist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn kill(&self) -> Result<(), MpvError> {
|
async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::Quit).await
|
self.run_command(MpvCommand::PlaylistRemove(id)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next(&self) -> Result<(), MpvError> {
|
async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::PlaylistNext).await
|
let data = self.get_property("playlist-pos").await?;
|
||||||
}
|
let current_id = match parse_property("playlist-pos", data)? {
|
||||||
|
Property::PlaylistPos(Some(current_id)) => Ok(current_id),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}?;
|
||||||
|
|
||||||
async fn observe_property(&self, id: usize, property: &str) -> Result<(), MpvError> {
|
self.run_command(MpvCommand::PlaylistMove {
|
||||||
self.run_command(MpvCommand::Observe {
|
from: id,
|
||||||
id,
|
to: current_id + 1,
|
||||||
property: property.to_string(),
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unobserve_property(&self, id: usize) -> Result<(), MpvError> {
|
async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::Unobserve(id)).await
|
self.set_property("playlist-pos", id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_playback(&self, option: Switch) -> Result<(), MpvError> {
|
async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError> {
|
||||||
let enabled = match option {
|
self.run_command(MpvCommand::PlaylistMove { from, to })
|
||||||
Switch::On => "yes",
|
.await
|
||||||
Switch::Off => "no",
|
|
||||||
Switch::Toggle => self
|
|
||||||
.get_property::<String>("pause")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)
|
|
||||||
.map(|s| match s.as_str() {
|
|
||||||
"yes" => "no",
|
|
||||||
"no" => "yes",
|
|
||||||
_ => "no",
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
self.set_property("pause", enabled).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prev(&self) -> Result<(), MpvError> {
|
async fn playlist_clear(&self) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::PlaylistPrev).await
|
self.run_command(MpvCommand::PlaylistClear).await
|
||||||
}
|
|
||||||
|
|
||||||
async fn restart(&self) -> Result<(), MpvError> {
|
|
||||||
self.run_command(MpvCommand::Seek {
|
|
||||||
seconds: 0f64,
|
|
||||||
option: SeekOptions::Absolute,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn playlist_add(
|
async fn playlist_add(
|
||||||
|
@ -227,121 +247,50 @@ impl MpvExt for Mpv {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn playlist_clear(&self) -> Result<(), MpvError> {
|
async fn restart(&self) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::PlaylistClear).await
|
self.run_command(MpvCommand::Seek {
|
||||||
}
|
seconds: 0f64,
|
||||||
|
option: SeekOptions::Absolute,
|
||||||
async fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), MpvError> {
|
|
||||||
self.run_command(MpvCommand::PlaylistMove { from, to })
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn playlist_play_id(&self, id: usize) -> Result<(), MpvError> {
|
|
||||||
self.set_property("playlist-pos", id).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn playlist_play_next(&self, id: usize) -> Result<(), MpvError> {
|
|
||||||
let current_id = self
|
|
||||||
.get_property::<usize>("playlist-pos")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)?;
|
|
||||||
|
|
||||||
self.run_command(MpvCommand::PlaylistMove {
|
|
||||||
from: id,
|
|
||||||
to: current_id + 1,
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn playlist_remove_id(&self, id: usize) -> Result<(), MpvError> {
|
async fn prev(&self) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::PlaylistRemove(id)).await
|
self.run_command(MpvCommand::PlaylistPrev).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn playlist_shuffle(&self) -> Result<(), MpvError> {
|
async fn observe_property(&self, id: usize, property: &str) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::PlaylistShuffle).await
|
self.run_command(MpvCommand::Observe {
|
||||||
|
id,
|
||||||
|
property: property.to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), MpvError> {
|
async fn unobserve_property(&self, id: usize) -> Result<(), MpvError> {
|
||||||
self.run_command(MpvCommand::Seek { seconds, option }).await
|
self.run_command(MpvCommand::Unobserve(id)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError> {
|
async fn next(&self) -> Result<(), MpvError> {
|
||||||
let enabled = match option {
|
self.run_command(MpvCommand::PlaylistNext).await
|
||||||
Switch::On => "inf",
|
|
||||||
Switch::Off => "no",
|
|
||||||
Switch::Toggle => self
|
|
||||||
.get_property::<String>("loop-file")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)
|
|
||||||
.map(|s| match s.as_str() {
|
|
||||||
"inf" => "no",
|
|
||||||
"no" => "inf",
|
|
||||||
_ => "no",
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
self.set_property("loop-file", enabled).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError> {
|
async fn kill(&self) -> Result<(), MpvError> {
|
||||||
let enabled = match option {
|
self.run_command(MpvCommand::Quit).await
|
||||||
Switch::On => "inf",
|
|
||||||
Switch::Off => "no",
|
|
||||||
Switch::Toggle => self
|
|
||||||
.get_property::<String>("loop-playlist")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)
|
|
||||||
.map(|s| match s.as_str() {
|
|
||||||
"inf" => "no",
|
|
||||||
"no" => "inf",
|
|
||||||
_ => "no",
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
self.set_property("loop-playlist", enabled).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_mute(&self, option: Switch) -> Result<(), MpvError> {
|
async fn stop(&self) -> Result<(), MpvError> {
|
||||||
let enabled = match option {
|
self.run_command(MpvCommand::Stop).await
|
||||||
Switch::On => "yes",
|
|
||||||
Switch::Off => "no",
|
|
||||||
Switch::Toggle => self
|
|
||||||
.get_property::<String>("mute")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)
|
|
||||||
.map(|s| match s.as_str() {
|
|
||||||
"yes" => "no",
|
|
||||||
"no" => "yes",
|
|
||||||
_ => "no",
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
self.set_property("mute", enabled).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_speed(
|
// SETTERS
|
||||||
&self,
|
|
||||||
input_speed: f64,
|
|
||||||
option: NumberChangeOptions,
|
|
||||||
) -> Result<(), MpvError> {
|
|
||||||
let speed = self
|
|
||||||
.get_property::<f64>("speed")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)?;
|
|
||||||
|
|
||||||
match option {
|
|
||||||
NumberChangeOptions::Increase => self.set_property("speed", speed + input_speed).await,
|
|
||||||
NumberChangeOptions::Decrease => self.set_property("speed", speed - input_speed).await,
|
|
||||||
NumberChangeOptions::Absolute => self.set_property("speed", input_speed).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_volume(
|
async fn set_volume(
|
||||||
&self,
|
&self,
|
||||||
input_volume: f64,
|
input_volume: f64,
|
||||||
option: NumberChangeOptions,
|
option: NumberChangeOptions,
|
||||||
) -> Result<(), MpvError> {
|
) -> Result<(), MpvError> {
|
||||||
let volume = self
|
let volume = self.get_volume().await?;
|
||||||
.get_property::<f64>("volume")
|
|
||||||
.await?
|
|
||||||
.ok_or(MpvError::MissingMpvData)?;
|
|
||||||
|
|
||||||
match option {
|
match option {
|
||||||
NumberChangeOptions::Increase => {
|
NumberChangeOptions::Increase => {
|
||||||
|
@ -354,7 +303,181 @@ impl MpvExt for Mpv {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stop(&self) -> Result<(), MpvError> {
|
async fn set_speed(
|
||||||
self.run_command(MpvCommand::Stop).await
|
&self,
|
||||||
|
input_speed: f64,
|
||||||
|
option: NumberChangeOptions,
|
||||||
|
) -> Result<(), MpvError> {
|
||||||
|
let speed = self.get_speed().await?;
|
||||||
|
|
||||||
|
match option {
|
||||||
|
NumberChangeOptions::Increase => self.set_property("speed", speed + input_speed).await,
|
||||||
|
NumberChangeOptions::Decrease => self.set_property("speed", speed - input_speed).await,
|
||||||
|
NumberChangeOptions::Absolute => self.set_property("speed", input_speed).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_playback(&self, option: Switch) -> Result<(), MpvError> {
|
||||||
|
let enabled = match option {
|
||||||
|
Switch::On => "yes",
|
||||||
|
Switch::Off => "no",
|
||||||
|
Switch::Toggle => {
|
||||||
|
if self.is_playing().await? {
|
||||||
|
"no"
|
||||||
|
} else {
|
||||||
|
"yes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.set_property("pause", enabled).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_mute(&self, option: Switch) -> Result<(), MpvError> {
|
||||||
|
let enabled = match option {
|
||||||
|
Switch::On => "yes",
|
||||||
|
Switch::Off => "no",
|
||||||
|
Switch::Toggle => {
|
||||||
|
if self.is_muted().await? {
|
||||||
|
"no"
|
||||||
|
} else {
|
||||||
|
"yes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.set_property("mute", enabled).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_loop_playlist(&self, option: Switch) -> Result<(), MpvError> {
|
||||||
|
let enabled = match option {
|
||||||
|
Switch::On => "inf",
|
||||||
|
Switch::Off => "no",
|
||||||
|
Switch::Toggle => match self.playlist_is_looping().await? {
|
||||||
|
LoopProperty::Inf => "no",
|
||||||
|
LoopProperty::N(_) => "no",
|
||||||
|
LoopProperty::No => "inf",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.set_property("loop-playlist", enabled).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_loop_file(&self, option: Switch) -> Result<(), MpvError> {
|
||||||
|
let enabled = match option {
|
||||||
|
Switch::On => "inf",
|
||||||
|
Switch::Off => "no",
|
||||||
|
Switch::Toggle => match self.file_is_looping().await? {
|
||||||
|
LoopProperty::Inf => "no",
|
||||||
|
LoopProperty::N(_) => "no",
|
||||||
|
LoopProperty::No => "inf",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.set_property("loop-file", enabled).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// GETTERS
|
||||||
|
|
||||||
|
async fn get_playlist(&self) -> Result<Playlist, MpvError> {
|
||||||
|
let data = self.get_property("playlist").await?;
|
||||||
|
match parse_property("playlist", data)? {
|
||||||
|
Property::Playlist(value) => Ok(Playlist(value)),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, MpvError> {
|
||||||
|
let data = self.get_property("metadata").await?;
|
||||||
|
match parse_property("metadata", data)? {
|
||||||
|
Property::Metadata(Some(value)) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_file_path(&self) -> Result<String, MpvError> {
|
||||||
|
let data = self.get_property("path").await?;
|
||||||
|
match parse_property("path", data)? {
|
||||||
|
Property::Path(Some(value)) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_volume(&self) -> Result<f64, MpvError> {
|
||||||
|
let data = self.get_property("volume").await?;
|
||||||
|
match parse_property("volume", data)? {
|
||||||
|
Property::Volume(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_speed(&self) -> Result<f64, MpvError> {
|
||||||
|
let data = self.get_property("speed").await?;
|
||||||
|
match parse_property("speed", data)? {
|
||||||
|
Property::Speed(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_time_pos(&self) -> Result<f64, MpvError> {
|
||||||
|
let data = self.get_property("time-pos").await?;
|
||||||
|
match parse_property("time-pos", data)? {
|
||||||
|
Property::TimePos(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_time_remaining(&self) -> Result<f64, MpvError> {
|
||||||
|
let data = self.get_property("time-remaining").await?;
|
||||||
|
match parse_property("time-remaining", data)? {
|
||||||
|
Property::TimeRemaining(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_duration(&self) -> Result<f64, MpvError> {
|
||||||
|
let data = self.get_property("duration").await?;
|
||||||
|
match parse_property("duration", data)? {
|
||||||
|
Property::Duration(Some(value)) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_playlist_pos(&self) -> Result<usize, MpvError> {
|
||||||
|
let data = self.get_property("playlist-pos").await?;
|
||||||
|
match parse_property("playlist-pos", data)? {
|
||||||
|
Property::PlaylistPos(Some(value)) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOOLEAN GETTERS
|
||||||
|
|
||||||
|
async fn is_muted(&self) -> Result<bool, MpvError> {
|
||||||
|
let data = self.get_property("mute").await?;
|
||||||
|
match parse_property("mute", data)? {
|
||||||
|
Property::Mute(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn is_playing(&self) -> Result<bool, MpvError> {
|
||||||
|
let data = self.get_property("pause").await?;
|
||||||
|
match parse_property("pause", data)? {
|
||||||
|
Property::Pause(value) => Ok(!value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn playlist_is_looping(&self) -> Result<LoopProperty, MpvError> {
|
||||||
|
let data = self.get_property("loop-playlist").await?;
|
||||||
|
match parse_property("loop-playlist", data)? {
|
||||||
|
Property::LoopPlaylist(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn file_is_looping(&self) -> Result<LoopProperty, MpvError> {
|
||||||
|
let data = self.get_property("loop-file").await?;
|
||||||
|
match parse_property("loop-file", data)? {
|
||||||
|
Property::LoopFile(value) => Ok(value),
|
||||||
|
prop => Err(MpvError::UnexpectedProperty(prop)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
mod core_api;
|
mod core_api;
|
||||||
mod error;
|
mod error;
|
||||||
mod event_parser;
|
mod event_parser;
|
||||||
mod event_property_parser;
|
|
||||||
mod highlevel_api_extension;
|
mod highlevel_api_extension;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod message_parser;
|
mod message_parser;
|
||||||
|
mod property_parser;
|
||||||
|
|
||||||
pub use core_api::*;
|
pub use core_api::*;
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use event_parser::*;
|
pub use event_parser::*;
|
||||||
pub use event_property_parser::*;
|
|
||||||
pub use highlevel_api_extension::*;
|
pub use highlevel_api_extension::*;
|
||||||
|
pub use property_parser::*;
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
//! JSON parsing logic for properties returned in [`Event::PropertyChange`]
|
//! JSON parsing logic for properties returned by
|
||||||
|
//! [[`Event::PropertyChange`], and used internally in `MpvExt`
|
||||||
|
//! to parse the response from `Mpv::get_property()`.
|
||||||
//!
|
//!
|
||||||
//! This module is used to parse the json data from the `data` field of the
|
//! This module is used to parse the json data from the `data` field of
|
||||||
//! [`Event::PropertyChange`] variant. Mpv has about 1000 different properties
|
//! known properties. Mpv has about 1000 different properties
|
||||||
//! as of `v0.38.0`, so this module will only implement the most common ones.
|
//! as of `v0.38.0`, so this module will only implement the most common ones.
|
||||||
|
|
||||||
|
// TODO: reuse this logic for providing a more typesafe response API to `Mpv::get_property()`
|
||||||
|
// Although this data is currently of type `Option<`
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Event, MpvDataType, MpvError, PlaylistEntry};
|
use crate::{MpvDataType, MpvError, PlaylistEntry};
|
||||||
|
|
||||||
/// All possible properties that can be observed through the event system.
|
/// An incomplete list of properties that mpv can return.
|
||||||
///
|
///
|
||||||
/// Not all properties are guaranteed to be implemented.
|
/// Unimplemented properties will be returned with it's data
|
||||||
/// If something is missing, please open an issue.
|
/// as a `Property::Unknown` variant.
|
||||||
///
|
|
||||||
/// Otherwise, the property will be returned as a `Property::Unknown` variant.
|
|
||||||
///
|
///
|
||||||
/// See <https://mpv.io/manual/master/#properties> for
|
/// See <https://mpv.io/manual/master/#properties> for
|
||||||
/// the upstream list of properties.
|
/// the upstream list of properties.
|
||||||
|
@ -31,6 +34,8 @@ pub enum Property {
|
||||||
PlaylistPos(Option<usize>),
|
PlaylistPos(Option<usize>),
|
||||||
LoopFile(LoopProperty),
|
LoopFile(LoopProperty),
|
||||||
LoopPlaylist(LoopProperty),
|
LoopPlaylist(LoopProperty),
|
||||||
|
TimePos(f64),
|
||||||
|
TimeRemaining(f64),
|
||||||
Speed(f64),
|
Speed(f64),
|
||||||
Volume(f64),
|
Volume(f64),
|
||||||
Mute(bool),
|
Mute(bool),
|
||||||
|
@ -48,17 +53,12 @@ pub enum LoopProperty {
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
|
/// Parse a highlevel [`Property`] object from mpv data.
|
||||||
pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError> {
|
///
|
||||||
let (id, name, data) = match event {
|
/// This is intended to be used with the `data` field of
|
||||||
Event::PropertyChange { id, name, data } => (id, name, data),
|
/// `Event::PropertyChange` and the response from `Mpv::get_property_value()`.
|
||||||
// TODO: return proper error
|
pub fn parse_property(name: &str, data: Option<MpvDataType>) -> Result<Property, MpvError> {
|
||||||
_ => {
|
match name {
|
||||||
panic!("Event is not a PropertyChange event")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match name.as_str() {
|
|
||||||
"path" => {
|
"path" => {
|
||||||
let path = match data {
|
let path = match data {
|
||||||
Some(MpvDataType::String(s)) => Some(s),
|
Some(MpvDataType::String(s)) => Some(s),
|
||||||
|
@ -73,7 +73,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
return Err(MpvError::MissingMpvData);
|
return Err(MpvError::MissingMpvData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Path(path)))
|
Ok(Property::Path(path))
|
||||||
}
|
}
|
||||||
"pause" => {
|
"pause" => {
|
||||||
let pause = match data {
|
let pause = match data {
|
||||||
|
@ -88,7 +88,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
return Err(MpvError::MissingMpvData);
|
return Err(MpvError::MissingMpvData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Pause(pause)))
|
Ok(Property::Pause(pause))
|
||||||
}
|
}
|
||||||
"playback-time" => {
|
"playback-time" => {
|
||||||
let playback_time = match data {
|
let playback_time = match data {
|
||||||
|
@ -101,7 +101,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::PlaybackTime(playback_time)))
|
Ok(Property::PlaybackTime(playback_time))
|
||||||
}
|
}
|
||||||
"duration" => {
|
"duration" => {
|
||||||
let duration = match data {
|
let duration = match data {
|
||||||
|
@ -114,7 +114,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Duration(duration)))
|
Ok(Property::Duration(duration))
|
||||||
}
|
}
|
||||||
"metadata" => {
|
"metadata" => {
|
||||||
let metadata = match data {
|
let metadata = match data {
|
||||||
|
@ -127,7 +127,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Metadata(metadata)))
|
Ok(Property::Metadata(metadata))
|
||||||
}
|
}
|
||||||
"playlist" => {
|
"playlist" => {
|
||||||
let playlist = match data {
|
let playlist = match data {
|
||||||
|
@ -140,7 +140,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Playlist(playlist)))
|
Ok(Property::Playlist(playlist))
|
||||||
}
|
}
|
||||||
"playlist-pos" => {
|
"playlist-pos" => {
|
||||||
let playlist_pos = match data {
|
let playlist_pos = match data {
|
||||||
|
@ -155,7 +155,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::PlaylistPos(playlist_pos)))
|
Ok(Property::PlaylistPos(playlist_pos))
|
||||||
}
|
}
|
||||||
"loop-file" => {
|
"loop-file" => {
|
||||||
let loop_file = match data.to_owned() {
|
let loop_file = match data.to_owned() {
|
||||||
|
@ -177,7 +177,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
},
|
},
|
||||||
None => MpvError::MissingMpvData,
|
None => MpvError::MissingMpvData,
|
||||||
})?;
|
})?;
|
||||||
Ok((id, Property::LoopFile(loop_file)))
|
Ok(Property::LoopFile(loop_file))
|
||||||
}
|
}
|
||||||
"loop-playlist" => {
|
"loop-playlist" => {
|
||||||
let loop_playlist = match data.to_owned() {
|
let loop_playlist = match data.to_owned() {
|
||||||
|
@ -200,7 +200,37 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
None => MpvError::MissingMpvData,
|
None => MpvError::MissingMpvData,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok((id, Property::LoopPlaylist(loop_playlist)))
|
Ok(Property::LoopPlaylist(loop_playlist))
|
||||||
|
}
|
||||||
|
"time-pos" => {
|
||||||
|
let time_pos = match data {
|
||||||
|
Some(MpvDataType::Double(d)) => d,
|
||||||
|
Some(data) => {
|
||||||
|
return Err(MpvError::DataContainsUnexpectedType {
|
||||||
|
expected_type: "f64".to_owned(),
|
||||||
|
received: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(MpvError::MissingMpvData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Property::TimePos(time_pos))
|
||||||
|
}
|
||||||
|
"time-remaining" => {
|
||||||
|
let time_remaining = match data {
|
||||||
|
Some(MpvDataType::Double(d)) => d,
|
||||||
|
Some(data) => {
|
||||||
|
return Err(MpvError::DataContainsUnexpectedType {
|
||||||
|
expected_type: "f64".to_owned(),
|
||||||
|
received: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(MpvError::MissingMpvData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Property::TimeRemaining(time_remaining))
|
||||||
}
|
}
|
||||||
"speed" => {
|
"speed" => {
|
||||||
let speed = match data {
|
let speed = match data {
|
||||||
|
@ -215,7 +245,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
return Err(MpvError::MissingMpvData);
|
return Err(MpvError::MissingMpvData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Speed(speed)))
|
Ok(Property::Speed(speed))
|
||||||
}
|
}
|
||||||
"volume" => {
|
"volume" => {
|
||||||
let volume = match data {
|
let volume = match data {
|
||||||
|
@ -230,7 +260,7 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
return Err(MpvError::MissingMpvData);
|
return Err(MpvError::MissingMpvData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Volume(volume)))
|
Ok(Property::Volume(volume))
|
||||||
}
|
}
|
||||||
"mute" => {
|
"mute" => {
|
||||||
let mute = match data {
|
let mute = match data {
|
||||||
|
@ -245,10 +275,13 @@ pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError>
|
||||||
return Err(MpvError::MissingMpvData);
|
return Err(MpvError::MissingMpvData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((id, Property::Mute(mute)))
|
Ok(Property::Mute(mute))
|
||||||
}
|
}
|
||||||
// TODO: add missing cases
|
// TODO: add missing cases
|
||||||
_ => Ok((id, Property::Unknown { name, data })),
|
_ => Ok(Property::Unknown {
|
||||||
|
name: name.to_owned(),
|
||||||
|
data,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use futures::{stream::StreamExt, Stream};
|
use futures::{stream::StreamExt, Stream};
|
||||||
use mpvipc::{parse_event_property, Event, Mpv, MpvError, MpvExt};
|
use mpvipc::{parse_property, Event, Mpv, MpvError, MpvExt};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tokio::time::{timeout, Duration};
|
use tokio::time::{timeout, Duration};
|
||||||
|
@ -38,8 +38,8 @@ where
|
||||||
match event {
|
match event {
|
||||||
Some(Ok(event)) => {
|
Some(Ok(event)) => {
|
||||||
match event {
|
match event {
|
||||||
Event::PropertyChange { id: MPV_CHANNEL_ID, .. } => {
|
Event::PropertyChange { id: MPV_CHANNEL_ID, name, data } => {
|
||||||
let property = parse_event_property(event).unwrap().1;
|
let property = parse_property(&name, data).unwrap();
|
||||||
if !on_property(property.clone()) {
|
if !on_property(property.clone()) {
|
||||||
return Err(PropertyCheckingThreadError::UnexpectedPropertyError(property))
|
return Err(PropertyCheckingThreadError::UnexpectedPropertyError(property))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use std::panic;
|
|
||||||
|
|
||||||
use futures::{stream::StreamExt, SinkExt};
|
use futures::{stream::StreamExt, SinkExt};
|
||||||
use mpvipc::{parse_event_property, Mpv, MpvDataType, MpvExt, Property};
|
use mpvipc::{Event, Mpv, MpvDataType, MpvExt};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
use tokio::{net::UnixStream, task::JoinHandle};
|
use tokio::{net::UnixStream, task::JoinHandle};
|
||||||
|
@ -51,19 +49,14 @@ async fn test_observe_event_successful() {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let event = mpv2.get_event_stream().await.next().await.unwrap().unwrap();
|
let event = mpv2.get_event_stream().await.next().await.unwrap().unwrap();
|
||||||
|
|
||||||
let data = match parse_event_property(event) {
|
assert_eq!(
|
||||||
Ok((_, Property::Unknown { name, data })) => {
|
event,
|
||||||
assert_eq!(name, "volume");
|
Event::PropertyChange {
|
||||||
data
|
id: 1,
|
||||||
|
name: "volume".to_string(),
|
||||||
|
data: Some(MpvDataType::Double(64.0))
|
||||||
}
|
}
|
||||||
Ok((_, property)) => panic!("{:?}", property),
|
)
|
||||||
Err(err) => panic!("{:?}", err),
|
|
||||||
};
|
|
||||||
match data {
|
|
||||||
Some(MpvDataType::Double(data)) => assert_eq!(data, 64.0),
|
|
||||||
Some(data) => panic!("Unexpected value: {:?}", data),
|
|
||||||
None => panic!("No data"),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mpv.set_property("volume", 64.0).await.unwrap();
|
mpv.set_property("volume", 64.0).await.unwrap();
|
||||||
|
|
Loading…
Reference in New Issue