mpvipc/
lib.rs

1pub mod ipc;
2
3use ipc::*;
4use serde_json::{json, Value};
5use std::collections::HashMap;
6use std::fmt::{self, Display};
7use std::io::{BufReader, Read};
8use std::os::unix::net::UnixStream;
9
10#[derive(Debug)]
11pub enum Event {
12    Shutdown,
13    StartFile,
14    EndFile,
15    FileLoaded,
16    TracksChanged,
17    TrackSwitched,
18    Idle,
19    Pause,
20    Unpause,
21    Tick,
22    VideoReconfig,
23    AudioReconfig,
24    MetadataUpdate,
25    Seek,
26    PlaybackRestart,
27    PropertyChange { id: usize, property: Property },
28    ChapterChange,
29    ClientMessage { args: Vec<String> },
30    Unimplemented,
31}
32
33#[derive(Debug)]
34pub enum Property {
35    Path(Option<String>),
36    Pause(bool),
37    PlaybackTime(Option<f64>),
38    Duration(Option<f64>),
39    Metadata(Option<HashMap<String, MpvDataType>>),
40    Unknown { name: String, data: MpvDataType },
41}
42
43pub enum MpvCommand {
44    LoadFile {
45        file: String,
46        option: PlaylistAddOptions,
47    },
48    LoadList {
49        file: String,
50        option: PlaylistAddOptions,
51    },
52    PlaylistClear,
53    PlaylistMove {
54        from: usize,
55        to: usize,
56    },
57    Observe {
58        id: isize,
59        property: String,
60    },
61    PlaylistNext,
62    PlaylistPrev,
63    PlaylistRemove(usize),
64    PlaylistShuffle,
65    Quit,
66    ScriptMessage(Vec<String>),
67    ScriptMessageTo {
68        target: String,
69        args: Vec<String>,
70    },
71    Seek {
72        seconds: f64,
73        option: SeekOptions,
74    },
75    Stop,
76    Unobserve(isize),
77}
78
79#[derive(Debug)]
80pub enum MpvDataType {
81    Array(Vec<MpvDataType>),
82    Bool(bool),
83    Double(f64),
84    HashMap(HashMap<String, MpvDataType>),
85    Null,
86    Playlist(Playlist),
87    String(String),
88    Usize(usize),
89}
90
91pub enum NumberChangeOptions {
92    Absolute,
93    Increase,
94    Decrease,
95}
96
97pub enum PlaylistAddOptions {
98    Replace,
99    Append,
100}
101
102pub enum PlaylistAddTypeOptions {
103    File,
104    Playlist,
105}
106
107pub enum SeekOptions {
108    Relative,
109    Absolute,
110    RelativePercent,
111    AbsolutePercent,
112}
113
114pub enum Switch {
115    On,
116    Off,
117    Toggle,
118}
119
120#[derive(Debug)]
121pub enum ErrorCode {
122    MpvError(String),
123    JsonParseError(String),
124    ConnectError(String),
125    JsonContainsUnexptectedType,
126    UnexpectedResult,
127    UnexpectedValue,
128    MissingValue,
129    UnsupportedType,
130    ValueDoesNotContainBool,
131    ValueDoesNotContainF64,
132    ValueDoesNotContainHashMap,
133    ValueDoesNotContainPlaylist,
134    ValueDoesNotContainString,
135    ValueDoesNotContainUsize,
136}
137
138pub struct Mpv {
139    stream: UnixStream,
140    reader: BufReader<UnixStream>,
141    name: String,
142}
143#[derive(Debug)]
144pub struct Playlist(pub Vec<PlaylistEntry>);
145#[derive(Debug)]
146pub struct Error(pub ErrorCode);
147
148impl Drop for Mpv {
149    fn drop(&mut self) {
150        self.disconnect();
151    }
152}
153
154impl fmt::Debug for Mpv {
155    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
156        fmt.debug_tuple("Mpv").field(&self.name).finish()
157    }
158}
159
160impl Clone for Mpv {
161    fn clone(&self) -> Self {
162        let stream = self.stream.try_clone().expect("cloning UnixStream");
163        let cloned_stream = stream.try_clone().expect("cloning UnixStream");
164        Mpv {
165            stream,
166            reader: BufReader::new(cloned_stream),
167            name: self.name.clone(),
168        }
169    }
170
171    fn clone_from(&mut self, source: &Self) {
172        let stream = source.stream.try_clone().expect("cloning UnixStream");
173        let cloned_stream = stream.try_clone().expect("cloning UnixStream");
174        *self = Mpv {
175            stream,
176            reader: BufReader::new(cloned_stream),
177            name: source.name.clone(),
178        }
179    }
180}
181
182impl Display for Error {
183    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184        Display::fmt(&self.0, f)
185    }
186}
187
188impl std::error::Error for Error {}
189
190impl Display for ErrorCode {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192        match *self {
193            ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
194            ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
195            ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
196            ErrorCode::JsonContainsUnexptectedType => {
197                f.write_str("Mpv sent a value with an unexpected type")
198            }
199            ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
200            ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
201            ErrorCode::MissingValue => f.write_str("Missing value"),
202            ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
203            ErrorCode::ValueDoesNotContainBool => {
204                f.write_str("The received value is not of type \'std::bool\'")
205            }
206            ErrorCode::ValueDoesNotContainF64 => {
207                f.write_str("The received value is not of type \'std::f64\'")
208            }
209            ErrorCode::ValueDoesNotContainHashMap => {
210                f.write_str("The received value is not of type \'std::collections::HashMap\'")
211            }
212            ErrorCode::ValueDoesNotContainPlaylist => {
213                f.write_str("The received value is not of type \'mpvipc::Playlist\'")
214            }
215            ErrorCode::ValueDoesNotContainString => {
216                f.write_str("The received value is not of type \'std::string::String\'")
217            }
218            ErrorCode::ValueDoesNotContainUsize => {
219                f.write_str("The received value is not of type \'std::usize\'")
220            }
221        }
222    }
223}
224
225pub trait GetPropertyTypeHandler: Sized {
226    fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, Error>;
227}
228
229impl GetPropertyTypeHandler for bool {
230    fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, Error> {
231        get_mpv_property::<bool>(instance, property)
232    }
233}
234
235impl GetPropertyTypeHandler for String {
236    fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, Error> {
237        get_mpv_property::<String>(instance, property)
238    }
239}
240
241impl GetPropertyTypeHandler for f64 {
242    fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, Error> {
243        get_mpv_property::<f64>(instance, property)
244    }
245}
246
247impl GetPropertyTypeHandler for usize {
248    fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, Error> {
249        get_mpv_property::<usize>(instance, property)
250    }
251}
252
253impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
254    fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, Error> {
255        get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
256    }
257}
258
259impl GetPropertyTypeHandler for HashMap<String, MpvDataType> {
260    fn get_property_generic(
261        instance: &Mpv,
262        property: &str,
263    ) -> Result<HashMap<String, MpvDataType>, Error> {
264        get_mpv_property::<HashMap<String, MpvDataType>>(instance, property)
265    }
266}
267
268pub trait SetPropertyTypeHandler<T> {
269    fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>;
270}
271
272impl SetPropertyTypeHandler<bool> for bool {
273    fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> {
274        set_mpv_property(instance, property, json!(value))
275    }
276}
277
278impl SetPropertyTypeHandler<String> for String {
279    fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> {
280        set_mpv_property(instance, property, json!(value))
281    }
282}
283
284impl SetPropertyTypeHandler<f64> for f64 {
285    fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> {
286        set_mpv_property(instance, property, json!(value))
287    }
288}
289
290impl SetPropertyTypeHandler<usize> for usize {
291    fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> {
292        set_mpv_property(instance, property, json!(value))
293    }
294}
295
296impl Mpv {
297    pub fn connect(socket: &str) -> Result<Mpv, Error> {
298        match UnixStream::connect(socket) {
299            Ok(stream) => {
300                let cloned_stream = stream.try_clone().expect("cloning UnixStream");
301                return Ok(Mpv {
302                    stream,
303                    reader: BufReader::new(cloned_stream),
304                    name: String::from(socket),
305                });
306            }
307            Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
308        }
309    }
310
311    pub fn disconnect(&self) {
312        let mut stream = &self.stream;
313        stream
314            .shutdown(std::net::Shutdown::Both)
315            .expect("socket disconnect");
316        let mut buffer = [0; 32];
317        for _ in 0..stream.bytes().count() {
318            stream.read(&mut buffer[..]).unwrap();
319        }
320    }
321
322    pub fn get_stream_ref(&self) -> &UnixStream {
323        &self.stream
324    }
325
326    pub fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error> {
327        match get_mpv_property(self, "metadata") {
328            Ok(map) => Ok(map),
329            Err(err) => Err(err),
330        }
331    }
332
333    pub fn get_playlist(&self) -> Result<Playlist, Error> {
334        match get_mpv_property::<Vec<PlaylistEntry>>(self, "playlist") {
335            Ok(entries) => Ok(Playlist(entries)),
336            Err(msg) => Err(msg),
337        }
338    }
339
340    /// # Description
341    ///
342    /// Retrieves the property value from mpv.
343    ///
344    /// ## Supported types
345    /// - String
346    /// - bool
347    /// - HashMap<String, String> (e.g. for the 'metadata' property)
348    /// - Vec<PlaylistEntry> (for the 'playlist' property)
349    /// - usize
350    /// - f64
351    ///
352    /// ## Input arguments
353    ///
354    /// - **property** defines the mpv property that should be retrieved
355    ///
356    /// # Example
357    /// ```
358    /// use mpvipc::{Mpv, Error};
359    /// fn main() -> Result<(), Error> {
360    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
361    ///     let paused: bool = mpv.get_property("pause")?;
362    ///     let title: String = mpv.get_property("media-title")?;
363    ///     Ok(())
364    /// }
365    /// ```
366    pub fn get_property<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, Error> {
367        T::get_property_generic(self, property)
368    }
369
370    /// # Description
371    ///
372    /// Retrieves the property value from mpv.
373    /// The result is always of type String, regardless of the type of the value of the mpv property
374    ///
375    /// ## Input arguments
376    ///
377    /// - **property** defines the mpv property that should be retrieved
378    ///
379    /// # Example
380    ///
381    /// ```
382    /// use mpvipc::{Mpv, Error};
383    /// fn main() -> Result<(), Error> {
384    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
385    ///     let title = mpv.get_property_string("media-title")?;
386    ///     Ok(())
387    /// }
388    /// ```
389    pub fn get_property_string(&self, property: &str) -> Result<String, Error> {
390        get_mpv_property_string(self, property)
391    }
392
393    pub fn kill(&self) -> Result<(), Error> {
394        self.run_command(MpvCommand::Quit)
395    }
396
397    /// # Description
398    ///
399    /// Waits until an mpv event occurs and returns the Event.
400    ///
401    /// # Example
402    ///
403    /// ```ignore
404    /// let mut mpv = Mpv::connect("/tmp/mpvsocket")?;
405    /// loop {
406    ///     let event = mpv.event_listen()?;
407    ///     println!("{:?}", event);
408    /// }
409    /// ```
410    pub fn event_listen(&mut self) -> Result<Event, Error> {
411        listen(self)
412    }
413
414    pub fn event_listen_raw(&mut self) -> String {
415        listen_raw(self)
416    }
417
418    pub fn next(&self) -> Result<(), Error> {
419        self.run_command(MpvCommand::PlaylistNext)
420    }
421
422    pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> {
423        self.run_command(MpvCommand::Observe {
424            id: id,
425            property: property.to_string(),
426        })
427    }
428
429    pub fn unobserve_property(&self, id: isize) -> Result<(), Error> {
430        self.run_command(MpvCommand::Unobserve(id))
431    }
432
433    pub fn pause(&self) -> Result<(), Error> {
434        set_mpv_property(self, "pause", json!(true))
435    }
436
437    pub fn prev(&self) -> Result<(), Error> {
438        self.run_command(MpvCommand::PlaylistPrev)
439    }
440
441    pub fn restart(&self) -> Result<(), Error> {
442        self.run_command(MpvCommand::Seek {
443            seconds: 0f64,
444            option: SeekOptions::Absolute,
445        })
446    }
447
448    /// # Description
449    ///
450    /// Runs mpv commands. The arguments are passed as a String-Vector reference:
451    ///
452    /// ## Input arguments
453    ///
454    /// - **command**   defines the mpv command that should be executed
455    /// - **args**      a slice of &str's which define the arguments
456    ///
457    /// # Example
458    /// ```
459    /// use mpvipc::{Mpv, Error};
460    /// fn main() -> Result<(), Error> {
461    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
462    ///
463    ///     //Run command 'playlist-shuffle' which takes no arguments
464    ///     mpv.run_command(MpvCommand::PlaylistShuffle)?;
465    ///
466    ///     //Run command 'seek' which in this case takes two arguments
467    ///     mpv.run_command(MpvCommand::Seek {
468    ///         seconds: 0f64,
469    ///         option: SeekOptions::Absolute,
470    ///     })?;
471    ///     Ok(())
472    /// }
473    /// ```
474    pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
475        match command {
476            MpvCommand::LoadFile { file, option } => run_mpv_command(
477                self,
478                "loadfile",
479                &[
480                    file.as_ref(),
481                    match option {
482                        PlaylistAddOptions::Append => "append",
483                        PlaylistAddOptions::Replace => "replace",
484                    },
485                ],
486            ),
487            MpvCommand::LoadList { file, option } => run_mpv_command(
488                self,
489                "loadlist",
490                &[
491                    file.as_ref(),
492                    match option {
493                        PlaylistAddOptions::Append => "append",
494                        PlaylistAddOptions::Replace => "replace",
495                    },
496                ],
497            ),
498            MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property),
499            MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]),
500            MpvCommand::PlaylistMove { from, to } => {
501                run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()])
502            }
503            MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]),
504            MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]),
505            MpvCommand::PlaylistRemove(id) => {
506                run_mpv_command(self, "playlist-remove", &[&id.to_string()])
507            }
508            MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]),
509            MpvCommand::Quit => run_mpv_command(self, "quit", &[]),
510            MpvCommand::ScriptMessage(args) => {
511                let str_args: Vec<_> = args.iter().map(String::as_str).collect();
512                run_mpv_command(self, "script-message", &str_args)
513            }
514            MpvCommand::ScriptMessageTo { target, args } => {
515                let mut cmd_args: Vec<_> = vec![target.as_str()];
516                let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
517                cmd_args.append(&mut str_args);
518                run_mpv_command(self, "script-message-to", &cmd_args)
519            }
520            MpvCommand::Seek { seconds, option } => run_mpv_command(
521                self,
522                "seek",
523                &[
524                    &seconds.to_string(),
525                    match option {
526                        SeekOptions::Absolute => "absolute",
527                        SeekOptions::Relative => "relative",
528                        SeekOptions::AbsolutePercent => "absolute-percent",
529                        SeekOptions::RelativePercent => "relative-percent",
530                    },
531                ],
532            ),
533            MpvCommand::Stop => run_mpv_command(self, "stop", &[]),
534            MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id),
535        }
536    }
537
538    /// Run a custom command.
539    /// This should only be used if the desired command is not implemented
540    /// with [MpvCommand].
541    pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> {
542        run_mpv_command(self, command, args)
543    }
544
545    pub fn playlist_add(
546        &self,
547        file: &str,
548        file_type: PlaylistAddTypeOptions,
549        option: PlaylistAddOptions,
550    ) -> Result<(), Error> {
551        match file_type {
552            PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile {
553                file: file.to_string(),
554                option,
555            }),
556
557            PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList {
558                file: file.to_string(),
559                option,
560            }),
561        }
562    }
563
564    pub fn playlist_clear(&self) -> Result<(), Error> {
565        self.run_command(MpvCommand::PlaylistClear)
566    }
567
568    pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> {
569        self.run_command(MpvCommand::PlaylistMove { from, to })
570    }
571
572    pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> {
573        set_mpv_property(self, "playlist-pos", json!(id))
574    }
575
576    pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> {
577        match get_mpv_property::<usize>(self, "playlist-pos") {
578            Ok(current_id) => self.run_command(MpvCommand::PlaylistMove {
579                from: id,
580                to: current_id + 1,
581            }),
582            Err(msg) => Err(msg),
583        }
584    }
585
586    pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> {
587        self.run_command(MpvCommand::PlaylistRemove(id))
588    }
589
590    pub fn playlist_shuffle(&self) -> Result<(), Error> {
591        self.run_command(MpvCommand::PlaylistShuffle)
592    }
593
594    pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> {
595        self.run_command(MpvCommand::Seek { seconds, option })
596    }
597
598    pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> {
599        let mut enabled = false;
600        match option {
601            Switch::On => enabled = true,
602            Switch::Off => {}
603            Switch::Toggle => match get_mpv_property_string(self, "loop-file") {
604                Ok(value) => match value.as_ref() {
605                    "false" => {
606                        enabled = true;
607                    }
608                    _ => {
609                        enabled = false;
610                    }
611                },
612                Err(msg) => return Err(msg),
613            },
614        }
615        set_mpv_property(self, "loop-file", json!(enabled))
616    }
617
618    pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> {
619        let mut enabled = false;
620        match option {
621            Switch::On => enabled = true,
622            Switch::Off => {}
623            Switch::Toggle => match get_mpv_property_string(self, "loop-playlist") {
624                Ok(value) => match value.as_ref() {
625                    "false" => {
626                        enabled = true;
627                    }
628                    _ => {
629                        enabled = false;
630                    }
631                },
632                Err(msg) => return Err(msg),
633            },
634        }
635        set_mpv_property(self, "loop-playlist", json!(enabled))
636    }
637
638    pub fn set_mute(&self, option: Switch) -> Result<(), Error> {
639        let mut enabled = false;
640        match option {
641            Switch::On => enabled = true,
642            Switch::Off => {}
643            Switch::Toggle => match get_mpv_property::<bool>(self, "mute") {
644                Ok(value) => {
645                    enabled = !value;
646                }
647                Err(msg) => return Err(msg),
648            },
649        }
650        set_mpv_property(self, "mute", json!(enabled))
651    }
652
653    /// # Description
654    ///
655    /// Sets the mpv property _<property>_ to _<value>_.
656    ///
657    /// ## Supported types
658    /// - String
659    /// - bool
660    /// - f64
661    /// - usize
662    ///
663    /// ## Input arguments
664    ///
665    /// - **property** defines the mpv property that should be retrieved
666    /// - **value** defines the value of the given mpv property _<property>_
667    ///
668    /// # Example
669    /// ```
670    /// use mpvipc::{Mpv, Error};
671    /// fn main() -> Result<(), Error> {
672    ///     let mpv = Mpv::connect("/tmp/mpvsocket")?;
673    ///     mpv.set_property("pause", true)?;
674    ///     Ok(())
675    /// }
676    /// ```
677    pub fn set_property<T: SetPropertyTypeHandler<T>>(
678        &self,
679        property: &str,
680        value: T,
681    ) -> Result<(), Error> {
682        T::set_property_generic(self, property, value)
683    }
684
685    pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> {
686        match get_mpv_property::<f64>(self, "speed") {
687            Ok(speed) => match option {
688                NumberChangeOptions::Increase => {
689                    set_mpv_property(self, "speed", json!(speed + input_speed))
690                }
691
692                NumberChangeOptions::Decrease => {
693                    set_mpv_property(self, "speed", json!(speed - input_speed))
694                }
695
696                NumberChangeOptions::Absolute => {
697                    set_mpv_property(self, "speed", json!(input_speed))
698                }
699            },
700            Err(msg) => Err(msg),
701        }
702    }
703
704    pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> {
705        match get_mpv_property::<f64>(self, "volume") {
706            Ok(volume) => match option {
707                NumberChangeOptions::Increase => {
708                    set_mpv_property(self, "volume", json!(volume + input_volume))
709                }
710
711                NumberChangeOptions::Decrease => {
712                    set_mpv_property(self, "volume", json!(volume - input_volume))
713                }
714
715                NumberChangeOptions::Absolute => {
716                    set_mpv_property(self, "volume", json!(input_volume))
717                }
718            },
719            Err(msg) => Err(msg),
720        }
721    }
722
723    pub fn stop(&self) -> Result<(), Error> {
724        self.run_command(MpvCommand::Stop)
725    }
726
727    pub fn toggle(&self) -> Result<(), Error> {
728        run_mpv_command(self, "cycle", &["pause"])
729    }
730}