1use std::convert::TryInto;
20use std::marker::PhantomData;
21
22macro_rules! mpv_cstr_to_str {
23 ($cstr: expr) => {
24 std::ffi::CStr::from_ptr($cstr)
25 .to_str()
26 .map_err(Error::from)
27 };
28}
29
30mod errors;
31
32pub mod events;
34#[cfg(feature = "protocols")]
36pub mod protocol;
37#[cfg(feature = "render")]
39pub mod render;
40
41pub use self::errors::*;
42use super::*;
43
44use std::{
45 ffi::CString,
46 mem::MaybeUninit,
47 ops::Deref,
48 os::raw as ctype,
49 ptr::{self, NonNull},
50 sync::atomic::AtomicBool,
51};
52
53fn mpv_err<T>(ret: T, err: ctype::c_int) -> Result<T> {
54 if err == 0 {
55 Ok(ret)
56 } else {
57 Err(Error::Raw(err))
58 }
59}
60
61pub unsafe trait GetData: Sized {
63 #[doc(hidden)]
64 fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(mut fun: F) -> Result<Self> {
65 let mut val = MaybeUninit::uninit();
66 let _ = fun(val.as_mut_ptr() as *mut _)?;
67 Ok(unsafe { val.assume_init() })
68 }
69 fn get_format() -> Format;
70}
71
72pub unsafe trait SetData: Sized {
74 #[doc(hidden)]
75 fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(
76 mut self,
77 mut fun: F,
78 ) -> Result<T> {
79 fun(&mut self as *mut Self as _)
80 }
81 fn get_format() -> Format;
82}
83
84unsafe impl GetData for f64 {
85 fn get_format() -> Format {
86 Format::Double
87 }
88}
89
90unsafe impl SetData for f64 {
91 fn get_format() -> Format {
92 Format::Double
93 }
94}
95
96unsafe impl GetData for i64 {
97 fn get_format() -> Format {
98 Format::Int64
99 }
100}
101
102#[derive(Debug)]
103pub enum MpvNodeValue<'a> {
104 String(&'a str),
105 Flag(bool),
106 Int64(i64),
107 Double(f64),
108 Array(MpvNodeArrayIter<'a>),
109 Map(MpvNodeMapIter<'a>),
110 None,
111}
112
113#[derive(Debug)]
114pub struct MpvNodeArrayIter<'parent> {
115 curr: i32,
116 list: libmpv_sys::mpv_node_list,
117 _does_not_outlive: PhantomData<&'parent MpvNode>,
118}
119
120impl Iterator for MpvNodeArrayIter<'_> {
121 type Item = MpvNode;
122
123 fn next(&mut self) -> Option<MpvNode> {
124 if self.curr >= self.list.num {
125 None
126 } else {
127 let offset = self.curr.try_into().ok()?;
128 self.curr += 1;
129 Some(MpvNode(unsafe { *self.list.values.offset(offset) }))
130 }
131 }
132}
133
134#[derive(Debug)]
135pub struct MpvNodeMapIter<'parent> {
136 curr: i32,
137 list: libmpv_sys::mpv_node_list,
138 _does_not_outlive: PhantomData<&'parent MpvNode>,
139}
140
141impl<'parent> Iterator for MpvNodeMapIter<'parent> {
142 type Item = (&'parent str, MpvNode);
143
144 fn next(&mut self) -> Option<(&'parent str, MpvNode)> {
145 if self.curr >= self.list.num {
146 None
147 } else {
148 let offset = self.curr.try_into().ok()?;
149 let (key, value) = unsafe {
150 (
151 mpv_cstr_to_str!(*self.list.keys.offset(offset)),
152 *self.list.values.offset(offset),
153 )
154 };
155 self.curr += 1;
156 Some((key.ok()?, MpvNode(value)))
157 }
158 }
159}
160
161#[derive(Debug)]
162pub struct MpvNode(libmpv_sys::mpv_node);
163
164impl Drop for MpvNode {
165 fn drop(&mut self) {
166 unsafe { libmpv_sys::mpv_free_node_contents(&mut self.0 as *mut libmpv_sys::mpv_node) };
167 }
168}
169
170impl MpvNode {
171 pub fn value(&self) -> Result<MpvNodeValue<'_>> {
172 let node = self.0;
173 Ok(match node.format {
174 mpv_format::Flag => MpvNodeValue::Flag(unsafe { node.u.flag } == 1),
175 mpv_format::Int64 => MpvNodeValue::Int64(unsafe { node.u.int64 }),
176 mpv_format::Double => MpvNodeValue::Double(unsafe { node.u.double_ }),
177 mpv_format::String => {
178 let text = unsafe { mpv_cstr_to_str!(node.u.string) }?;
179 MpvNodeValue::String(text)
180 }
181
182 mpv_format::Array => MpvNodeValue::Array(MpvNodeArrayIter {
183 list: unsafe { *node.u.list },
184 curr: 0,
185 _does_not_outlive: PhantomData,
186 }),
187
188 mpv_format::Map => MpvNodeValue::Map(MpvNodeMapIter {
189 list: unsafe { *node.u.list },
190 curr: 0,
191 _does_not_outlive: PhantomData,
192 }),
193 mpv_format::None => MpvNodeValue::None,
194 _ => return Err(Error::Raw(mpv_error::PropertyError)),
195 })
196 }
197
198 pub fn to_bool(&self) -> Option<bool> {
199 if let MpvNodeValue::Flag(value) = self.value().ok()? {
200 Some(value)
201 } else {
202 None
203 }
204 }
205 pub fn to_i64(&self) -> Option<i64> {
206 if let MpvNodeValue::Int64(value) = self.value().ok()? {
207 Some(value)
208 } else {
209 None
210 }
211 }
212 pub fn to_f64(&self) -> Option<f64> {
213 if let MpvNodeValue::Double(value) = self.value().ok()? {
214 Some(value)
215 } else {
216 None
217 }
218 }
219
220 pub fn to_str(&self) -> Option<&str> {
221 if let MpvNodeValue::String(value) = self.value().ok()? {
222 Some(value)
223 } else {
224 None
225 }
226 }
227
228 pub fn to_array(&self) -> Option<MpvNodeArrayIter<'_>> {
229 if let MpvNodeValue::Array(value) = self.value().ok()? {
230 Some(value)
231 } else {
232 None
233 }
234 }
235
236 pub fn to_map(&self) -> Option<MpvNodeMapIter<'_>> {
237 if let MpvNodeValue::Map(value) = self.value().ok()? {
238 Some(value)
239 } else {
240 None
241 }
242 }
243}
244
245unsafe impl GetData for MpvNode {
246 fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(
247 mut fun: F,
248 ) -> Result<MpvNode> {
249 let mut val = MaybeUninit::uninit();
250 let _ = fun(val.as_mut_ptr() as *mut _)?;
251 Ok(MpvNode(unsafe { val.assume_init() }))
252 }
253
254 fn get_format() -> Format {
255 Format::Node
256 }
257}
258
259unsafe impl SetData for i64 {
260 fn get_format() -> Format {
261 Format::Int64
262 }
263}
264
265unsafe impl GetData for bool {
266 fn get_format() -> Format {
267 Format::Flag
268 }
269}
270
271unsafe impl SetData for bool {
272 fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> {
273 let mut cpy: i64 = if self { 1 } else { 0 };
274 fun(&mut cpy as *mut i64 as *mut _)
275 }
276
277 fn get_format() -> Format {
278 Format::Flag
279 }
280}
281
282unsafe impl GetData for String {
283 fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(mut fun: F) -> Result<String> {
284 let ptr = &mut ptr::null();
285 let _ = fun(ptr as *mut *const ctype::c_char as _)?;
286
287 let ret = unsafe { mpv_cstr_to_str!(*ptr) }?.to_owned();
288 unsafe { libmpv_sys::mpv_free(*ptr as *mut _) };
289 Ok(ret)
290 }
291
292 fn get_format() -> Format {
293 Format::String
294 }
295}
296
297unsafe impl SetData for String {
298 fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> {
299 let string = CString::new(self)?;
300 fun((&mut string.as_ptr()) as *mut *const ctype::c_char as *mut _)
301 }
302
303 fn get_format() -> Format {
304 Format::String
305 }
306}
307
308#[derive(Debug, Hash, Eq, PartialEq)]
310pub struct MpvStr<'a>(&'a str);
311impl<'a> Deref for MpvStr<'a> {
312 type Target = str;
313
314 fn deref(&self) -> &str {
315 self.0
316 }
317}
318impl<'a> Drop for MpvStr<'a> {
319 fn drop(&mut self) {
320 unsafe { libmpv_sys::mpv_free(self.0.as_ptr() as *mut u8 as _) };
321 }
322}
323
324unsafe impl<'a> GetData for MpvStr<'a> {
325 fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(
326 mut fun: F,
327 ) -> Result<MpvStr<'a>> {
328 let ptr = &mut ptr::null();
329 let _ = fun(ptr as *mut *const ctype::c_char as _)?;
330
331 Ok(MpvStr(unsafe { mpv_cstr_to_str!(*ptr) }?))
332 }
333
334 fn get_format() -> Format {
335 Format::String
336 }
337}
338
339unsafe impl<'a> SetData for &'a str {
340 fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> {
341 let string = CString::new(self)?;
342 fun((&mut string.as_ptr()) as *mut *const ctype::c_char as *mut _)
343 }
344
345 fn get_format() -> Format {
346 Format::String
347 }
348}
349
350#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
351pub enum Format {
353 String,
354 Flag,
355 Int64,
356 Double,
357 Node,
358}
359
360impl Format {
361 fn as_mpv_format(&self) -> MpvFormat {
362 match *self {
363 Format::String => mpv_format::String,
364 Format::Flag => mpv_format::Flag,
365 Format::Int64 => mpv_format::Int64,
366 Format::Double => mpv_format::Double,
367 Format::Node => mpv_format::Node,
368 }
369 }
370}
371
372#[derive(Clone, Copy, Debug, PartialEq, Eq)]
373pub enum FileState {
375 Replace,
377 Append,
379 AppendPlay,
381}
382
383impl FileState {
384 fn val(&self) -> &str {
385 match *self {
386 FileState::Replace => "replace",
387 FileState::Append => "append",
388 FileState::AppendPlay => "append-play",
389 }
390 }
391}
392
393pub struct MpvInitializer {
395 ctx: *mut libmpv_sys::mpv_handle,
396}
397
398impl MpvInitializer {
399 pub fn set_property<T: SetData>(&self, name: &str, data: T) -> Result<()> {
401 let name = CString::new(name)?;
402 let format = T::get_format().as_mpv_format() as _;
403 data.call_as_c_void(|ptr| {
404 mpv_err((), unsafe {
405 libmpv_sys::mpv_set_property(self.ctx, name.as_ptr(), format, ptr)
406 })
407 })
408 }
409}
410
411pub struct Mpv {
413 pub ctx: NonNull<libmpv_sys::mpv_handle>,
415 events_guard: AtomicBool,
416 #[cfg(feature = "protocols")]
417 protocols_guard: AtomicBool,
418}
419
420unsafe impl Send for Mpv {}
421unsafe impl Sync for Mpv {}
422
423impl Drop for Mpv {
424 fn drop(&mut self) {
425 unsafe {
426 libmpv_sys::mpv_terminate_destroy(self.ctx.as_ptr());
427 }
428 }
429}
430
431impl Mpv {
432 pub fn new() -> Result<Mpv> {
435 Mpv::with_initializer(|_| Ok(()))
436 }
437
438 pub fn with_initializer<F: FnOnce(MpvInitializer) -> Result<()>>(
441 initializer: F,
442 ) -> Result<Mpv> {
443 let api_version = unsafe { libmpv_sys::mpv_client_api_version() };
444 if crate::MPV_CLIENT_API_MAJOR != api_version >> 16 {
445 return Err(Error::VersionMismatch {
446 linked: crate::MPV_CLIENT_API_VERSION,
447 loaded: api_version,
448 });
449 }
450
451 let ctx = unsafe { libmpv_sys::mpv_create() };
452 if ctx.is_null() {
453 return Err(Error::Null);
454 }
455
456 initializer(MpvInitializer { ctx })?;
457 mpv_err((), unsafe { libmpv_sys::mpv_initialize(ctx) }).map_err(|err| {
458 unsafe { libmpv_sys::mpv_terminate_destroy(ctx) };
459 err
460 })?;
461
462 Ok(Mpv {
463 ctx: unsafe { NonNull::new_unchecked(ctx) },
464 events_guard: AtomicBool::new(false),
465 #[cfg(feature = "protocols")]
466 protocols_guard: AtomicBool::new(false),
467 })
468 }
469
470 pub fn load_config(&self, path: &str) -> Result<()> {
472 let file = CString::new(path)?.into_raw();
473 let ret = mpv_err((), unsafe {
474 libmpv_sys::mpv_load_config_file(self.ctx.as_ptr(), file)
475 });
476 unsafe { CString::from_raw(file) };
477 ret
478 }
479
480 pub fn command(&self, name: &str, args: &[&str]) -> Result<()> {
485 let mut cmd = name.to_owned();
486
487 for elem in args {
488 cmd.push_str(" ");
489 cmd.push_str(elem);
490 }
491
492 let raw = CString::new(cmd)?;
493 mpv_err((), unsafe {
494 libmpv_sys::mpv_command_string(self.ctx.as_ptr(), raw.as_ptr())
495 })
496 }
497
498 pub fn set_property<T: SetData>(&self, name: &str, data: T) -> Result<()> {
500 let name = CString::new(name)?;
501 let format = T::get_format().as_mpv_format() as _;
502 data.call_as_c_void(|ptr| {
503 mpv_err((), unsafe {
504 libmpv_sys::mpv_set_property(self.ctx.as_ptr(), name.as_ptr(), format, ptr)
505 })
506 })
507 }
508
509 pub fn get_property<T: GetData>(&self, name: &str) -> Result<T> {
511 let name = CString::new(name)?;
512
513 let format = T::get_format().as_mpv_format() as _;
514 T::get_from_c_void(|ptr| {
515 mpv_err((), unsafe {
516 libmpv_sys::mpv_get_property(self.ctx.as_ptr(), name.as_ptr(), format, ptr)
517 })
518 })
519 }
520
521 pub fn get_internal_time(&self) -> i64 {
525 unsafe { libmpv_sys::mpv_get_time_us(self.ctx.as_ptr()) }
526 }
527
528 pub fn add_property(&self, property: &str, value: isize) -> Result<()> {
533 self.command("add", &[property, &format!("{}", value)])
534 }
535
536 pub fn cycle_property(&self, property: &str, up: bool) -> Result<()> {
539 self.command("cycle", &[property, if up { "up" } else { "down" }])
540 }
541
542 pub fn multiply_property(&self, property: &str, factor: usize) -> Result<()> {
544 self.command("multiply", &[property, &format!("{}", factor)])
545 }
546
547 pub fn pause(&self) -> Result<()> {
549 self.set_property("pause", true)
550 }
551
552 pub fn unpause(&self) -> Result<()> {
554 self.set_property("pause", false)
555 }
556
557 pub fn seek_forward(&self, secs: ctype::c_double) -> Result<()> {
565 self.command("seek", &[&format!("{}", secs), "relative"])
566 }
567
568 pub fn seek_backward(&self, secs: ctype::c_double) -> Result<()> {
570 self.command("seek", &[&format!("-{}", secs), "relative"])
571 }
572
573 pub fn seek_absolute(&self, secs: ctype::c_double) -> Result<()> {
575 self.command("seek", &[&format!("{}", secs), "absolute"])
576 }
577
578 pub fn seek_percent(&self, percent: isize) -> Result<()> {
582 self.command("seek", &[&format!("{}", percent), "relative-percent"])
583 }
584
585 pub fn seek_percent_absolute(&self, percent: usize) -> Result<()> {
587 self.command("seek", &[&format!("{}", percent), "relative-percent"])
588 }
589
590 pub fn seek_revert(&self) -> Result<()> {
592 self.command("revert-seek", &[])
593 }
594
595 pub fn seek_revert_mark(&self) -> Result<()> {
597 self.command("revert-seek", &["mark"])
598 }
599
600 pub fn seek_frame(&self) -> Result<()> {
603 self.command("frame-step", &[])
604 }
605
606 pub fn seek_frame_backward(&self) -> Result<()> {
609 self.command("frame-back-step", &[])
610 }
611
612 pub fn screenshot_subtitles(&self, path: Option<&str>) -> Result<()> {
624 if let Some(path) = path {
625 self.command("screenshot", &[&format!("\"{}\"", path), "subtitles"])
626 } else {
627 self.command("screenshot", &["subtitles"])
628 }
629 }
630
631 pub fn screenshot_video(&self, path: Option<&str>) -> Result<()> {
634 if let Some(path) = path {
635 self.command("screenshot", &[&format!("\"{}\"", path), "video"])
636 } else {
637 self.command("screenshot", &["video"])
638 }
639 }
640
641 pub fn screenshot_window(&self, path: Option<&str>) -> Result<()> {
645 if let Some(path) = path {
646 self.command("screenshot", &[&format!("\"{}\"", path), "window"])
647 } else {
648 self.command("screenshot", &["window"])
649 }
650 }
651
652 pub fn playlist_next_weak(&self) -> Result<()> {
658 self.command("playlist-next", &["weak"])
659 }
660
661 pub fn playlist_next_force(&self) -> Result<()> {
664 self.command("playlist-next", &["force"])
665 }
666
667 pub fn playlist_previous_weak(&self) -> Result<()> {
669 self.command("playlist-prev", &["weak"])
670 }
671
672 pub fn playlist_previous_force(&self) -> Result<()> {
674 self.command("playlist-prev", &["force"])
675 }
676
677 pub fn playlist_load_files(&self, files: &[(&str, FileState, Option<&str>)]) -> Result<()> {
690 for (i, elem) in files.iter().enumerate() {
691 let args = elem.2.unwrap_or("");
692
693 let ret = self.command(
694 "loadfile",
695 &[&format!("\"{}\"", elem.0), elem.1.val(), args],
696 );
697
698 if let Err(err) = ret {
699 return Err(Error::Loadfiles {
700 index: i,
701 error: ::std::rc::Rc::new(err),
702 });
703 }
704 }
705 Ok(())
706 }
707
708 pub fn playlist_load_list(&self, path: &str, replace: bool) -> Result<()> {
710 if replace {
711 self.command("loadlist", &[&format!("\"{}\"", path), "replace"])
712 } else {
713 self.command("loadlist", &[&format!("\"{}\"", path), "append"])
714 }
715 }
716
717 pub fn playlist_clear(&self) -> Result<()> {
719 self.command("playlist-clear", &[])
720 }
721
722 pub fn playlist_remove_current(&self) -> Result<()> {
724 self.command("playlist-remove", &["current"])
725 }
726
727 pub fn playlist_remove_index(&self, position: usize) -> Result<()> {
729 self.command("playlist-remove", &[&format!("{}", position)])
730 }
731
732 pub fn playlist_move(&self, old: usize, new: usize) -> Result<()> {
734 self.command("playlist-move", &[&format!("{}", new), &format!("{}", old)])
735 }
736
737 pub fn playlist_shuffle(&self) -> Result<()> {
739 self.command("playlist-shuffle", &[])
740 }
741
742 pub fn subtitle_add_select(
751 &self,
752 path: &str,
753 title: Option<&str>,
754 lang: Option<&str>,
755 ) -> Result<()> {
756 match (title, lang) {
757 (None, None) => self.command("sub-add", &[&format!("\"{}\"", path), "select"]),
758 (Some(t), None) => self.command("sub-add", &[&format!("\"{}\"", path), "select", t]),
759 (None, Some(_)) => panic!("Given subtitle language, but missing title"),
760 (Some(t), Some(l)) => {
761 self.command("sub-add", &[&format!("\"{}\"", path), "select", t, l])
762 }
763 }
764 }
765
766 pub fn subtitle_add_auto(
774 &self,
775 path: &str,
776 title: Option<&str>,
777 lang: Option<&str>,
778 ) -> Result<()> {
779 match (title, lang) {
780 (None, None) => self.command("sub-add", &[&format!("\"{}\"", path), "auto"]),
781 (Some(t), None) => self.command("sub-add", &[&format!("\"{}\"", path), "auto", t]),
782 (Some(t), Some(l)) => {
783 self.command("sub-add", &[&format!("\"{}\"", path), "auto", t, l])
784 }
785 (None, Some(_)) => panic!("Given subtitle language, but missing title"),
786 }
787 }
788
789 pub fn subtitle_add_cached(&self, path: &str) -> Result<()> {
794 self.command("sub-add", &[&format!("\"{}\"", path), "cached"])
795 }
796
797 pub fn subtitle_remove(&self, index: Option<usize>) -> Result<()> {
800 if let Some(idx) = index {
801 self.command("sub-remove", &[&format!("{}", idx)])
802 } else {
803 self.command("sub-remove", &[])
804 }
805 }
806
807 pub fn subtitle_reload(&self, index: Option<usize>) -> Result<()> {
810 if let Some(idx) = index {
811 self.command("sub-reload", &[&format!("{}", idx)])
812 } else {
813 self.command("sub-reload", &[])
814 }
815 }
816
817 pub fn subtitle_step(&self, skip: isize) -> Result<()> {
820 self.command("sub-step", &[&format!("{}", skip)])
821 }
822
823 pub fn subtitle_seek_forward(&self) -> Result<()> {
828 self.command("sub-seek", &["1"])
829 }
830
831 pub fn subtitle_seek_backward(&self) -> Result<()> {
833 self.command("sub-seek", &["-1"])
834 }
835}