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 pub fn get_property<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, Error> {
367 T::get_property_generic(self, property)
368 }
369
370 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 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 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 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 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}