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}