libmpv/
mpv.rs

1// Copyright (C) 2016  ParadoxSpiral
2//
3// This file is part of mpv-rs.
4//
5// This library is free software; you can redistribute it and/or
6// modify it under the terms of the GNU Lesser General Public
7// License as published by the Free Software Foundation; either
8// version 2.1 of the License, or (at your option) any later version.
9//
10// This library is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public
16// License along with this library; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
19use 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
32/// Event handling
33pub mod events;
34/// Custom protocols (`protocol://$url`) for playback
35#[cfg(feature = "protocols")]
36pub mod protocol;
37/// Custom rendering
38#[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
61/// This trait describes which types are allowed to be passed to getter mpv APIs.
62pub 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
72/// This trait describes which types are allowed to be passed to setter mpv APIs.
73pub 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/// Wrapper around an `&str` returned by mpv, that properly deallocates it with mpv's allocator.
309#[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)]
351/// Subset of `mpv_format` used by the public API.
352pub 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)]
373/// How a `File` is inserted into the playlist.
374pub enum FileState {
375    /// Replace the current track.
376    Replace,
377    /// Append to the current playlist.
378    Append,
379    /// If current playlist is empty: play, otherwise append to playlist.
380    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
393/// Context passed to the `initializer` of `Mpv::with_initialzer`.
394pub struct MpvInitializer {
395    ctx: *mut libmpv_sys::mpv_handle,
396}
397
398impl MpvInitializer {
399    /// Set the value of a property.
400    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
411/// The central mpv context.
412pub struct Mpv {
413    /// The handle to the mpv core
414    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    /// Create a new `Mpv`.
433    /// The default settings can be probed by running: `$ mpv --show-profile=libmpv`.
434    pub fn new() -> Result<Mpv> {
435        Mpv::with_initializer(|_| Ok(()))
436    }
437
438    /// Create a new `Mpv`.
439    /// The same as `Mpv::new`, but you can set properties before `Mpv` is initialized.
440    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    /// Load a configuration file. The path has to be absolute, and a file.
471    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    /// Send a command to the `Mpv` instance. This uses `mpv_command_string` internally,
481    /// so that the syntax is the same as described in the [manual for the input.conf](https://mpv.io/manual/master/#list-of-input-commands).
482    ///
483    /// Note that you may have to escape strings with `""` when they contain spaces.
484    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    /// Set the value of a property.
499    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    /// Get the value of a property.
510    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    /// Internal time in microseconds, this has an arbitrary offset, and will never go backwards.
522    ///
523    /// This can be called at any time, even if it was stated that no API function should be called.
524    pub fn get_internal_time(&self) -> i64 {
525        unsafe { libmpv_sys::mpv_get_time_us(self.ctx.as_ptr()) }
526    }
527
528    // --- Convenience property functions ---
529    //
530
531    /// Add -or subtract- any value from a property. Over/underflow clamps to max/min.
532    pub fn add_property(&self, property: &str, value: isize) -> Result<()> {
533        self.command("add", &[property, &format!("{}", value)])
534    }
535
536    /// Cycle through a given property. `up` specifies direction. On
537    /// overflow, set the property back to the minimum, on underflow set it to the maximum.
538    pub fn cycle_property(&self, property: &str, up: bool) -> Result<()> {
539        self.command("cycle", &[property, if up { "up" } else { "down" }])
540    }
541
542    /// Multiply any property with any positive factor.
543    pub fn multiply_property(&self, property: &str, factor: usize) -> Result<()> {
544        self.command("multiply", &[property, &format!("{}", factor)])
545    }
546
547    /// Pause playback at runtime.
548    pub fn pause(&self) -> Result<()> {
549        self.set_property("pause", true)
550    }
551
552    /// Unpause playback at runtime.
553    pub fn unpause(&self) -> Result<()> {
554        self.set_property("pause", false)
555    }
556
557    // --- Seek functions ---
558    //
559
560    /// Seek forward relatively from current position in seconds.
561    /// This is less exact than `seek_absolute`, see [mpv manual]
562    /// (https://mpv.io/manual/master/#command-interface-
563    /// [relative|absolute|absolute-percent|relative-percent|exact|keyframes]).
564    pub fn seek_forward(&self, secs: ctype::c_double) -> Result<()> {
565        self.command("seek", &[&format!("{}", secs), "relative"])
566    }
567
568    /// See `seek_forward`.
569    pub fn seek_backward(&self, secs: ctype::c_double) -> Result<()> {
570        self.command("seek", &[&format!("-{}", secs), "relative"])
571    }
572
573    /// Seek to a given absolute secs.
574    pub fn seek_absolute(&self, secs: ctype::c_double) -> Result<()> {
575        self.command("seek", &[&format!("{}", secs), "absolute"])
576    }
577
578    /// Seek to a given relative percent position (may be negative).
579    /// If `percent` of the playtime is bigger than the remaining playtime, the next file is played.
580    /// out of bounds values are clamped to either 0 or 100.
581    pub fn seek_percent(&self, percent: isize) -> Result<()> {
582        self.command("seek", &[&format!("{}", percent), "relative-percent"])
583    }
584
585    /// Seek to the given percentage of the playtime.
586    pub fn seek_percent_absolute(&self, percent: usize) -> Result<()> {
587        self.command("seek", &[&format!("{}", percent), "relative-percent"])
588    }
589
590    /// Revert the previous `seek_` call, can also revert itself.
591    pub fn seek_revert(&self) -> Result<()> {
592        self.command("revert-seek", &[])
593    }
594
595    /// Mark the current position as the position that will be seeked to by `seek_revert`.
596    pub fn seek_revert_mark(&self) -> Result<()> {
597        self.command("revert-seek", &["mark"])
598    }
599
600    /// Seek exactly one frame, and pause.
601    /// Noop on audio only streams.
602    pub fn seek_frame(&self) -> Result<()> {
603        self.command("frame-step", &[])
604    }
605
606    /// See `seek_frame`.
607    /// [Note performance considerations.](https://mpv.io/manual/master/#command-interface-frame-back-step)
608    pub fn seek_frame_backward(&self) -> Result<()> {
609        self.command("frame-back-step", &[])
610    }
611
612    // --- Screenshot functions ---
613    //
614
615    /// "Save the video image, in its original resolution, and with subtitles.
616    /// Some video outputs may still include the OSD in the output under certain circumstances.".
617    ///
618    /// "[O]ptionally save it to a given file. The format of the file will be
619    /// guessed by the extension (and --screenshot-format is ignored - the behaviour when the
620    /// extension is missing or unknown is arbitrary). If the file already exists, it's overwritten.
621    /// Like all input command parameters, the filename is subject to property expansion as
622    /// described in [Property Expansion](https://mpv.io/manual/master/#property-expansion)."
623    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    /// "Like subtitles, but typically without OSD or subtitles. The exact behavior
632    /// depends on the selected video output."
633    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    /// "Save the contents of the mpv window. Typically scaled, with OSD and subtitles. The exact
642    /// behaviour depends on the selected video output, and if no support is available,
643    /// this will act like video.".
644    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    // --- Playlist functions ---
653    //
654
655    /// Play the next item of the current playlist.
656    /// Does nothing if the current item is the last item.
657    pub fn playlist_next_weak(&self) -> Result<()> {
658        self.command("playlist-next", &["weak"])
659    }
660
661    /// Play the next item of the current playlist.
662    /// Terminates playback if the current item is the last item.
663    pub fn playlist_next_force(&self) -> Result<()> {
664        self.command("playlist-next", &["force"])
665    }
666
667    /// See `playlist_next_weak`.
668    pub fn playlist_previous_weak(&self) -> Result<()> {
669        self.command("playlist-prev", &["weak"])
670    }
671
672    /// See `playlist_next_force`.
673    pub fn playlist_previous_force(&self) -> Result<()> {
674        self.command("playlist-prev", &["force"])
675    }
676
677    /// The given files are loaded sequentially, returning the index of the current file
678    /// and the error in case of an error. [More information.](https://mpv.io/manual/master/#command-interface-[replace|append|append-play)
679    ///
680    /// # Arguments
681    /// The `files` tuple slice consists of:
682    ///     * a string slice - the path
683    ///     * a `FileState` - how the file will be opened
684    ///     * an optional string slice - any additional options that will be set for this file
685    ///
686    /// # Peculiarities
687    /// `loadfile` is kind of asynchronous, any additional option is set during loading,
688    /// [specifics](https://github.com/mpv-player/mpv/issues/4089).
689    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    /// Load the given playlist file, that either replaces the current playlist, or appends to it.
709    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    /// Remove every, except the current, item from the playlist.
718    pub fn playlist_clear(&self) -> Result<()> {
719        self.command("playlist-clear", &[])
720    }
721
722    /// Remove the currently selected item from the playlist.
723    pub fn playlist_remove_current(&self) -> Result<()> {
724        self.command("playlist-remove", &["current"])
725    }
726
727    /// Remove item at `position` from the playlist.
728    pub fn playlist_remove_index(&self, position: usize) -> Result<()> {
729        self.command("playlist-remove", &[&format!("{}", position)])
730    }
731
732    /// Move item `old` to the position of item `new`.
733    pub fn playlist_move(&self, old: usize, new: usize) -> Result<()> {
734        self.command("playlist-move", &[&format!("{}", new), &format!("{}", old)])
735    }
736
737    /// Shuffle the playlist.
738    pub fn playlist_shuffle(&self) -> Result<()> {
739        self.command("playlist-shuffle", &[])
740    }
741
742    // --- Subtitle functions ---
743    //
744
745    /// Add and select the subtitle immediately.
746    /// Specifying a language requires specifying a title.
747    ///
748    /// # Panics
749    /// If a language but not title was specified.
750    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    /// See `AddSelect`. "Don't select the subtitle.
767    /// (Or in some special situations, let the default stream selection mechanism decide.)".
768    ///
769    /// Returns an `Error::InvalidArgument` if a language, but not a title, was provided.
770    ///
771    /// # Panics
772    /// If a language but not title was specified.
773    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    /// See `AddSelect`. "Select the subtitle. If a subtitle with the same file name was
790    /// already added, that one is selected, instead of loading a duplicate entry.
791    /// (In this case, title/language are ignored, and if the [sub] was changed since it was loaded,
792    /// these changes won't be reflected.)".
793    pub fn subtitle_add_cached(&self, path: &str) -> Result<()> {
794        self.command("sub-add", &[&format!("\"{}\"", path), "cached"])
795    }
796
797    /// "Remove the given subtitle track. If the id argument is missing, remove the current
798    /// track. (Works on external subtitle files only.)"
799    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    /// "Reload the given subtitle track. If the id argument is missing, reload the current
808    /// track. (Works on external subtitle files only.)"
809    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    /// "Change subtitle timing such, that the subtitle event after the next `isize` subtitle
818    /// events is displayed. `isize` can be negative to step backwards."
819    pub fn subtitle_step(&self, skip: isize) -> Result<()> {
820        self.command("sub-step", &[&format!("{}", skip)])
821    }
822
823    /// "Seek to the next subtitle. This is similar to sub-step, except that it seeks video and
824    /// audio instead of adjusting the subtitle delay.
825    /// For embedded subtitles (like with matroska), this works only with subtitle events that
826    /// have already been displayed, or are within a short prefetch range."
827    pub fn subtitle_seek_forward(&self) -> Result<()> {
828        self.command("sub-seek", &["1"])
829    }
830
831    /// See `SeekForward`.
832    pub fn subtitle_seek_backward(&self) -> Result<()> {
833        self.command("sub-seek", &["-1"])
834    }
835}