use std::process::Command;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PlayerctlError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Command error: {0}")]
CommandError(String),
#[error("Other error: {0}")]
Other(String),
}
pub type Result<T> = std::result::Result<T, PlayerctlError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrackStatus {
Playing,
Paused,
Stopped,
}
#[derive(Default)]
pub struct TrackMetadata {
pub title: String,
pub album: String,
pub artist: String,
pub url: String,
pub length: String,
}
pub struct Playerctl;
impl Playerctl {
pub fn play() -> Result<()> {
run_command("play")?;
Ok(())
}
pub fn pause() -> Result<()> {
run_command("pause")?;
Ok(())
}
pub fn play_pause() -> Result<()> {
run_command("play-pause")?;
Ok(())
}
pub fn stop() -> Result<()> {
run_command("stop")?;
Ok(())
}
pub fn next() -> Result<()> {
run_command("next")?;
Ok(())
}
pub fn previous() -> Result<()> {
run_command("previous")?;
Ok(())
}
pub fn position(secs: f32) -> Result<()> {
if secs < 0. {
run_command(&format!("position {}-", -secs))?;
} else {
run_command(&format!("position {}+", secs))?;
}
Ok(())
}
pub fn volume(percent: f32) -> Result<()> {
if percent < 0. {
run_command(&format!("volume {}-", -percent))?;
} else {
run_command(&format!("volume {}+", percent))?;
}
Ok(())
}
pub fn status() -> Result<TrackStatus> {
let status = run_command("status")?;
match status.unwrap().as_str().trim() {
"Playing" => Ok(TrackStatus::Playing),
"Paused" => Ok(TrackStatus::Paused),
_ => Ok(TrackStatus::Stopped),
}
}
pub fn metadata() -> Result<TrackMetadata> {
let title = run_command("metadata title")?;
let album = run_command("metadata album")?;
let artist = run_command("metadata artist")?;
let url = run_command("metadata xesam:url")?;
let length = run_command("metadata mpris:length")?;
Ok(TrackMetadata {
title: title.unwrap_or_default(),
album: album.unwrap_or_default(),
artist: artist.unwrap_or_default(),
url: url.unwrap_or_default(),
length: length.unwrap_or_default(),
})
}
}
fn run_command(command: &str) -> Result<Option<String>> {
let args: Vec<&str> = command.split_whitespace().collect();
let output = Command::new("playerctl").args(args).output()?;
if output.status.success() {
Ok(Some(
String::from_utf8_lossy(&output.stdout).trim().to_string(),
))
} else {
Err(PlayerctlError::CommandError(format!(
"Command failed with status {}: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
)))
}
}