Skip to main content

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}