vlc_rc/
client.rs

1//! A client connection to the VLC interface.
2//!
3//! ## Types
4//!
5//! ### Connection types:
6//!
7//! * [`Client`] - Represents a connection to VLC's TCP interface.
8//!
9//! ### Media types:
10//!
11//! * [`Track`] - Represents a media track in a VLC player's playlist.
12//! * [`Playlist`] - A collection of tracks.
13//! * [`Subtitle`] - A subtitle track associated with a media file.
14//! * [`Subtitles`] - A collection of subtitle tracks.
15//!
16//! When using the library, you'd typically construct a new [`Client`] and then proceed to issue commands by using the client's methods.
17
18mod media;
19mod socket;
20
21pub use media::Playlist;
22pub use media::Subtitle;
23pub use media::Subtitles;
24pub use media::Track;
25pub use media::MAX_VOLUME;
26pub use media::MIN_VOLUME;
27
28use std::io::prelude::*;
29use std::net::ToSocketAddrs;
30
31use crate::Result;
32
33use media::FromParts;
34use socket::IoSocket;
35use socket::PROMPT;
36
37/// A connection to a VLC player's TCP interface.
38pub struct Client {
39    socket: IoSocket,
40}
41
42impl Client {
43    /// Establishes a connection to a VLC player's TCP interface at the given address.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use vlc_rc::Client;
49    ///
50    /// let player = Client::connect("127.0.0.1:9090").unwrap();
51    /// ```
52    pub fn connect<A>(addr: A) -> Result<Client>
53    where
54        A: ToSocketAddrs,
55    {
56        Ok(Self { socket: IoSocket::connect(addr)? })
57    }
58
59    /// Gets a list of tracks in the VLC player's playlist.
60    ///
61    /// # Examples
62    ///
63    /// ```
64    /// use vlc_rc::Client;
65    ///
66    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
67    ///
68    /// let playlist = player.playlist().unwrap();
69    /// for track in playlist {
70    ///     println!("{}", track);
71    /// }
72    /// ```
73    pub fn playlist(&mut self) -> Result<Playlist> {
74        writeln!(self.socket, "playlist")?;
75        self.socket.flush()?;
76
77        let mut buf = Vec::new();
78        self.socket.read_until(PROMPT, &mut buf)?;
79
80        let out = String::from_utf8_lossy(&buf);
81
82        Ok(out.lines().filter_map(Track::from_parts).collect())
83    }
84
85    /// Gets a list of subtitle tracks for the current media file.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use vlc_rc::Client;
91    ///
92    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
93    ///
94    /// let subtitles = player.subtitles().unwrap();
95    /// for strack in subtitles {
96    ///     println!("{}", strack);
97    /// }
98    /// ```
99    pub fn subtitles(&mut self) -> Result<Subtitles> {
100        writeln!(self.socket, "strack")?;
101        self.socket.flush()?;
102
103        let mut buf = Vec::new();
104        self.socket.read_until(PROMPT, &mut buf)?;
105
106        let out = String::from_utf8_lossy(&buf);
107
108        Ok(out.lines().filter_map(Subtitle::from_parts).collect())
109    }
110
111    /// Gets the VLC player's current volume.
112    /// # Examples
113    ///
114    /// ```
115    /// use vlc_rc::Client;
116    ///
117    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
118    ///
119    /// let volume = player.get_volume().unwrap();
120    /// println!("the current volume is {}", volume);
121    /// ```
122    pub fn get_volume(&mut self) -> Result<u8> {
123        writeln!(self.socket, "volume")?;
124        self.socket.flush()?;
125
126        let mut line = String::new();
127        self.socket.read_line(&mut line)?;
128
129        let volume = line.trim().parse::<u16>()?;
130
131        if volume <= (MAX_VOLUME as u16) {
132            Ok(volume as u8)
133        } else {
134            Ok(MAX_VOLUME)
135        }
136    }
137
138    /// Sets the VLC player's volume to the given amount.
139    ///
140    /// If `amt` is greater than [`MAX_VOLUME`], it defaults to the max volume.
141    ///
142    /// # Examples
143    ///
144    /// ```
145    /// use vlc_rc::Client;
146    ///
147    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
148    ///
149    /// player.set_volume(50).unwrap();
150    /// assert_eq!(player.get_volume().unwrap(), 50);
151    /// ```
152    pub fn set_volume(&mut self, mut amt: u8) -> Result<()> {
153        if amt > MAX_VOLUME {
154            amt = MAX_VOLUME;
155        }
156
157        // Spam the interface until we get the desired output.
158        while self.get_volume()? != amt {
159            writeln!(self.socket, "volume {}", amt)?;
160            self.socket.flush()?;
161        }
162        Ok(())
163    }
164
165    /// Returns whether or not the current media track is playing.
166    ///
167    /// Note that if the track is paused, the method still returns `true`.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// use vlc_rc::Client;
173    ///
174    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
175    ///
176    /// let is_playing = player.is_playing().unwrap();
177    /// if is_playing {
178    ///     println!("the track is currently playing!");
179    /// } else {
180    ///     println!("the track is currently stopped!");
181    /// }
182    /// ```
183    pub fn is_playing(&mut self) -> Result<bool> {
184        writeln!(self.socket, "is_playing")?;
185        self.socket.flush()?;
186
187        let mut line = String::new();
188        self.socket.read_line(&mut line)?;
189
190        Ok(line.trim() == "1")
191    }
192
193    /// Plays the current media track.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use vlc_rc::Client;
199    ///
200    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
201    ///
202    /// player.play().unwrap();
203    /// assert_eq!(player.is_playing().unwrap(), true);
204    /// ```
205    pub fn play(&mut self) -> Result<()> {
206        // Only issue the 'play' command if the playlist is not empty.
207        if !self.playlist()?.is_empty() {
208            // Spam the interface until we get the desired output.
209            while !self.is_playing()? {
210                writeln!(self.socket, "play")?;
211                self.socket.flush()?;
212            }
213        }
214        Ok(())
215    }
216
217    /// Stops the current media track's playback.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use vlc_rc::Client;
223    ///
224    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
225    ///
226    /// player.stop().unwrap();
227    /// assert_eq!(player.is_playing().unwrap(), false);
228    /// ```
229    pub fn stop(&mut self) -> Result<()> {
230        // Spam the interface until we get the desired output.
231        while self.is_playing()? {
232            writeln!(self.socket, "stop")?;
233            self.socket.flush()?;
234        }
235        Ok(())
236    }
237
238    /// Pauses the current track's playback.
239    ///
240    /// Does nothing if the track is stopped.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// use vlc_rc::Client;
246    ///
247    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
248    ///
249    /// player.pause().unwrap();
250    /// ```
251    pub fn pause(&mut self) -> Result<()> {
252        if self.is_playing()? {
253            // The 'pause' command works as a toggle, so we need to ensure that the track is playing before we execute it to get the desired behavior.
254            writeln!(self.socket, "play")?;
255            writeln!(self.socket, "pause")?;
256            self.socket.flush()?;
257        }
258        Ok(())
259    }
260
261    /// Gets the elapsed time since the track's beginning (in seconds).
262    ///
263    /// Returns `None` if the current track is stopped.
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// use vlc_rc::Client;
269    ///
270    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
271    ///
272    /// let seconds = player.get_time().unwrap();
273    /// ```
274    pub fn get_time(&mut self) -> Result<Option<u32>> {
275        writeln!(self.socket, "get_time")?;
276        self.socket.flush()?;
277
278        let mut line = String::new();
279        self.socket.read_line(&mut line)?;
280
281        Ok(line.trim().parse().ok())
282    }
283
284    /// Moves the track's playback forward by the given amount (in seconds).
285    ///
286    /// # Examples
287    ///
288    /// ```
289    /// use vlc_rc::Client;
290    ///
291    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
292    ///
293    /// player.forward(5).unwrap();
294    /// ```
295    pub fn forward(&mut self, secs: u32) -> Result<()> {
296        writeln!(self.socket, "seek +{}", secs)?;
297        self.socket.flush()?;
298
299        Ok(())
300    }
301
302    /// Moves the track's playback backward by the given amount (in seconds).
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// use vlc_rc::Client;
308    ///
309    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
310    ///
311    /// player.rewind(5).unwrap();
312    /// ```
313    pub fn rewind(&mut self, secs: u32) -> Result<()> {
314        writeln!(self.socket, "seek -{}", secs)?;
315        self.socket.flush()?;
316
317        Ok(())
318    }
319
320    /// Gets the current media track's title.
321    ///
322    /// Returns `None` if the media player is stopped.
323    ///
324    /// # Examples
325    ///
326    /// ```
327    /// use vlc_rc::Client;
328    ///
329    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
330    ///
331    /// let current_track = player.get_title().unwrap();
332    /// if let Some(title) = current_track {
333    ///     println!("the track '{}' is currently playing!", title);
334    /// }
335    /// ```
336    pub fn get_title(&mut self) -> Result<Option<String>> {
337        writeln!(self.socket, "get_title")?;
338        self.socket.flush()?;
339
340        let mut line = String::new();
341        self.socket.read_line(&mut line)?;
342
343        // If the line is empty, it means that the player is currently stopped - so we can just return `None`.
344        if line.trim().len() != 0 {
345            Ok(Some(line.trim().to_owned()))
346        } else {
347            Ok(None)
348        }
349    }
350
351    /// Plays the next track in the playlist.
352    ///
353    /// # Examples
354    ///
355    /// ```
356    /// use vlc_rc::Client;
357    ///
358    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
359    ///
360    /// player.next().unwrap();
361    /// ```
362    pub fn next(&mut self) -> Result<()> {
363        writeln!(self.socket, "next")?;
364        self.socket.flush()?;
365
366        Ok(())
367    }
368
369    /// Plays the previous track in the playlist.
370    ///
371    /// # Examples
372    ///
373    /// ```
374    /// use vlc_rc::Client;
375    ///
376    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
377    ///
378    /// player.prev().unwrap();
379    /// ```
380    pub fn prev(&mut self) -> Result<()> {
381        writeln!(self.socket, "prev")?;
382        self.socket.flush()?;
383
384        Ok(())
385    }
386
387    /// Toggles the media player's fullscreen mode on/off.
388    ///
389    ///  # Examples
390    ///
391    /// ```
392    /// use vlc_rc::Client;
393    ///
394    /// let mut player = Client::connect("127.0.0.1:9090").unwrap();
395    ///
396    /// player.fullscreen(true).unwrap();
397    /// println!("fullscreen is on!");
398    ///
399    /// player.fullscreen(false).unwrap();
400    /// println!("fullscreen is off!");
401    /// ```
402    pub fn fullscreen(&mut self, on: bool) -> Result<()> {
403        writeln!(self.socket, "fullscreen {}", if on { "on" } else { "off" })?;
404        self.socket.flush()?;
405
406        Ok(())
407    }
408}
409
410impl Drop for Client {
411    fn drop(&mut self) {
412        if let Ok(_) = self.socket.shutdown() {}
413    }
414}
415
416#[cfg(test)]
417mod test {
418    use std::env;
419
420    use super::Client;
421    use super::Result;
422
423    fn connect() -> Result<Client> {
424        let addr = env::var("TEST_ADDR")
425            .expect("use the 'test.sh' bash script to run tests!");
426
427        Client::connect(addr)
428    }
429
430    #[test]
431    fn get_and_set_volume() -> Result<()> {
432        let mut client = connect()?;
433
434        client.set_volume(25)?;
435        assert_eq!(client.get_volume()?, 25);
436
437        client.set_volume(0)?;
438        assert_eq!(client.get_volume()?, 0);
439
440        Ok(())
441    }
442
443    #[test]
444    fn play_and_stop() -> Result<()> {
445        let mut client = connect()?;
446
447        client.play()?;
448        assert_eq!(client.is_playing()?, true);
449
450        client.stop()?;
451        assert_eq!(client.is_playing()?, false);
452
453        Ok(())
454    }
455
456    #[test]
457    fn forward() -> Result<()> {
458        let mut client = connect()?;
459
460        client.pause()?;
461
462        let before = match client.get_time()? {
463            Some(t) => t,
464            _ => return Ok(()),
465        };
466
467        client.forward(5)?;
468
469        let after = match client.get_time()? {
470            Some(t) => t,
471            _ => return Ok(()),
472        };
473
474        assert_eq!(after, before + 5);
475
476        Ok(())
477    }
478
479    #[test]
480    fn rewind() -> Result<()> {
481        let mut client = connect()?;
482
483        client.forward(10)?;
484
485        let before = match client.get_time()? {
486            Some(t) => t,
487            _ => return Ok(()),
488        };
489
490        client.rewind(5)?;
491
492        let after = match client.get_time()? {
493            Some(t) => t,
494            _ => return Ok(()),
495        };
496
497        assert_eq!(after, (before).checked_sub(5).unwrap_or(0));
498
499        Ok(())
500    }
501}