playerctl_rust_wrapper/lib.rs
1//! Playerctl Wrapper
2//!
3//! A `playerctl` rust wrapper allowing the control of players and current
4//! playing track.
5//!
6//! See the [playerctl](https://github.com/altdesktop/playerctl) project for
7//! more information.
8
9use std::process::Command;
10use thiserror::Error;
11
12/// Playerctl errors.
13#[derive(Debug, Error)]
14pub enum PlayerctlError {
15 #[error("IO error: {0}")]
16 IoError(#[from] std::io::Error),
17 #[error("Command error: {0}")]
18 CommandError(String),
19 #[error("Other error: {0}")]
20 Other(String),
21}
22pub type Result<T> = std::result::Result<T, PlayerctlError>;
23
24/// The current track status.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum TrackStatus {
27 /// Track is playing.
28 Playing,
29 /// Track is paused.
30 Paused,
31 /// Track is stopped.
32 Stopped,
33}
34
35/// Track metadata struct.
36#[derive(Default)]
37pub struct TrackMetadata {
38 /// Track's title.
39 pub title: String,
40 /// Track's album.
41 pub album: String,
42 /// Track's artist.
43 pub artist: String,
44 /// Track's source URL.
45 pub url: String,
46 /// Track's length.
47 pub length: String,
48}
49
50/// # Playerctl
51///
52/// Playerctl wrapper struct allowing to send commands and control
53/// the player.
54///
55/// # Examples
56///
57/// ## Stop the current player
58///
59/// ```
60/// Playerctl::stop().unwrap();
61/// ```
62///
63/// ## Advance and rewind 10 seconds
64///
65/// ```
66/// Playerctl::position(10.).unwrap();
67/// Playerctl::position(-10.).unwrap();
68/// ```
69///
70/// ## Get metadata
71///
72/// ```
73/// let metadata = Playerctl::metadata().unwrap();
74///
75/// println!("Track title: {}", metadata.title);
76/// ```
77pub struct Playerctl;
78
79impl Playerctl {
80 /// Command the player to play.
81 ///
82 /// ```
83 /// Playerctl::play().unwrap();
84 /// ```
85 pub fn play() -> Result<()> {
86 run_command("play")?;
87 Ok(())
88 }
89
90 /// Command the player to pause.
91 ///
92 /// ```
93 /// Playerctl::pause().unwrap();
94 /// ```
95 pub fn pause() -> Result<()> {
96 run_command("pause")?;
97 Ok(())
98 }
99
100 /// Command the player to toggle between play/pause.
101 ///
102 /// ```
103 /// Playerctl::play_pause().unwrap();
104 /// ```
105 pub fn play_pause() -> Result<()> {
106 run_command("play-pause")?;
107 Ok(())
108 }
109
110 /// Command the player to stop.
111 ///
112 /// ```
113 /// Playerctl::stop().unwrap();
114 /// ```
115 pub fn stop() -> Result<()> {
116 run_command("stop")?;
117 Ok(())
118 }
119
120 /// Command the player to skip to the next track.
121 ///
122 /// ```
123 /// Playerctl::next().unwrap();
124 /// ```
125 pub fn next() -> Result<()> {
126 run_command("next")?;
127 Ok(())
128 }
129
130 /// Command the player to skip to the previous track.
131 ///
132 /// ```
133 /// Playerctl::previous().unwrap();
134 /// ```
135 pub fn previous() -> Result<()> {
136 run_command("previous")?;
137 Ok(())
138 }
139
140 /// Command the player to seek forward/backward OFFSET in seconds.
141 ///
142 /// ```
143 /// Playerctl::position(10.).unwrap();
144 /// ```
145 pub fn position(secs: f32) -> Result<()> {
146 if secs < 0. {
147 run_command(&format!("position {}-", -secs))?;
148 } else {
149 run_command(&format!("position {}+", secs))?;
150 }
151
152 Ok(())
153 }
154
155 /// Set the volume to LEVEL from 0.0 to 1.0.
156 ///
157 /// ```
158 /// Playerctl::volume(10.).unwrap();
159 /// ```
160 pub fn volume(percent: f32) -> Result<()> {
161 if percent < 0. {
162 run_command(&format!("volume {}-", -percent))?;
163 } else {
164 run_command(&format!("volume {}+", percent))?;
165 }
166
167 Ok(())
168 }
169
170 /// Get the play status of the player.
171 ///
172 /// ```
173 /// Playerctl::status(10.).unwrap();
174 /// ```
175 pub fn status() -> Result<TrackStatus> {
176 let status = run_command("status")?;
177
178 match status.unwrap().as_str().trim() {
179 "Playing" => Ok(TrackStatus::Playing),
180 "Paused" => Ok(TrackStatus::Paused),
181 _ => Ok(TrackStatus::Stopped),
182 }
183 }
184
185 /// Get metadata information for the current track.
186 ///
187 /// ```
188 /// let metadata = Playerctl::metadata().unwrap();
189 ///
190 /// println!("Title: {}", metadata.title);
191 /// ```
192 pub fn metadata() -> Result<TrackMetadata> {
193 let title = run_command("metadata title")?;
194 let album = run_command("metadata album")?;
195 let artist = run_command("metadata artist")?;
196 let url = run_command("metadata xesam:url")?;
197 let length = run_command("metadata mpris:length")?;
198
199 Ok(TrackMetadata {
200 title: title.unwrap_or_default(),
201 album: album.unwrap_or_default(),
202 artist: artist.unwrap_or_default(),
203 url: url.unwrap_or_default(),
204 length: length.unwrap_or_default(),
205 })
206 }
207}
208
209/// Run a playerctl command.
210///
211/// ```
212/// Playerctl::play().expect("Failed to run command");
213/// ```
214fn run_command(command: &str) -> Result<Option<String>> {
215 let args: Vec<&str> = command.split_whitespace().collect();
216
217 let output = Command::new("playerctl").args(args).output()?;
218
219 if output.status.success() {
220 Ok(Some(
221 String::from_utf8_lossy(&output.stdout).trim().to_string(),
222 ))
223 } else {
224 Err(PlayerctlError::CommandError(format!(
225 "Command failed with status {}: {}",
226 output.status,
227 String::from_utf8_lossy(&output.stderr)
228 )))
229 }
230}