libmpv/mpv/
protocol.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 super::*;
20
21use std::alloc::{self, Layout};
22use std::ffi::CString;
23use std::marker::PhantomData;
24use std::mem;
25use std::os::raw as ctype;
26use std::panic;
27use std::panic::RefUnwindSafe;
28use std::ptr::{self, NonNull};
29use std::slice;
30use std::sync::{atomic::Ordering, Mutex};
31
32impl Mpv {
33    /// Create a context with which custom protocols can be registered.
34    ///
35    /// # Panics
36    /// Panics if a context already exists
37    pub fn create_protocol_context<T, U>(&self) -> ProtocolContext<T, U>
38    where
39        T: RefUnwindSafe,
40        U: RefUnwindSafe,
41    {
42        match self.protocols_guard.compare_exchange(
43            false,
44            true,
45            Ordering::AcqRel,
46            Ordering::Acquire,
47        ) {
48            Ok(_) => ProtocolContext::new(self.ctx, PhantomData::<&Self>),
49            Err(_) => panic!("A protocol context already exists"),
50        }
51    }
52}
53
54/// Return a persistent `T` that is passed to all other `Stream*` functions, panic on errors.
55pub type StreamOpen<T, U> = fn(&mut U, &str) -> T;
56/// Do any necessary cleanup.
57pub type StreamClose<T> = fn(Box<T>);
58/// Seek to the given offset. Return the new offset, or either `MpvError::Generic` if seeking
59/// failed or panic.
60pub type StreamSeek<T> = fn(&mut T, i64) -> i64;
61/// Target buffer with fixed capacity.
62/// Return either the number of read bytes, `0` on EOF, or either `-1` or panic on error.
63pub type StreamRead<T> = fn(&mut T, &mut [ctype::c_char]) -> i64;
64/// Return the total size of the stream in bytes. Panic on error.
65pub type StreamSize<T> = fn(&mut T) -> i64;
66
67unsafe extern "C" fn open_wrapper<T, U>(
68    user_data: *mut ctype::c_void,
69    uri: *mut ctype::c_char,
70    info: *mut libmpv_sys::mpv_stream_cb_info,
71) -> ctype::c_int
72where
73    T: RefUnwindSafe,
74    U: RefUnwindSafe,
75{
76    let data = user_data as *mut ProtocolData<T, U>;
77
78    (*info).cookie = user_data;
79    (*info).read_fn = Some(read_wrapper::<T, U>);
80    (*info).seek_fn = Some(seek_wrapper::<T, U>);
81    (*info).size_fn = Some(size_wrapper::<T, U>);
82    (*info).close_fn = Some(close_wrapper::<T, U>);
83
84    let ret = panic::catch_unwind(|| {
85        let uri = mpv_cstr_to_str!(uri as *const _).unwrap();
86        ptr::write(
87            (*data).cookie,
88            ((*data).open_fn)(&mut (*data).user_data, uri),
89        );
90    });
91
92    if ret.is_ok() {
93        0
94    } else {
95        mpv_error::Generic as _
96    }
97}
98
99unsafe extern "C" fn read_wrapper<T, U>(
100    cookie: *mut ctype::c_void,
101    buf: *mut ctype::c_char,
102    nbytes: u64,
103) -> i64
104where
105    T: RefUnwindSafe,
106    U: RefUnwindSafe,
107{
108    let data = cookie as *mut ProtocolData<T, U>;
109
110    let ret = panic::catch_unwind(|| {
111        let slice = slice::from_raw_parts_mut(buf, nbytes as _);
112        ((*data).read_fn)(&mut *(*data).cookie, slice)
113    });
114    if let Ok(ret) = ret {
115        ret
116    } else {
117        -1
118    }
119}
120
121unsafe extern "C" fn seek_wrapper<T, U>(cookie: *mut ctype::c_void, offset: i64) -> i64
122where
123    T: RefUnwindSafe,
124    U: RefUnwindSafe,
125{
126    let data = cookie as *mut ProtocolData<T, U>;
127
128    if (*data).seek_fn.is_none() {
129        return mpv_error::Unsupported as _;
130    }
131
132    let ret =
133        panic::catch_unwind(|| (*(*data).seek_fn.as_ref().unwrap())(&mut *(*data).cookie, offset));
134    if let Ok(ret) = ret {
135        ret
136    } else {
137        mpv_error::Generic as _
138    }
139}
140
141unsafe extern "C" fn size_wrapper<T, U>(cookie: *mut ctype::c_void) -> i64
142where
143    T: RefUnwindSafe,
144    U: RefUnwindSafe,
145{
146    let data = cookie as *mut ProtocolData<T, U>;
147
148    if (*data).size_fn.is_none() {
149        return mpv_error::Unsupported as _;
150    }
151
152    let ret = panic::catch_unwind(|| (*(*data).size_fn.as_ref().unwrap())(&mut *(*data).cookie));
153    if let Ok(ret) = ret {
154        ret
155    } else {
156        mpv_error::Unsupported as _
157    }
158}
159
160#[allow(unused_must_use)]
161unsafe extern "C" fn close_wrapper<T, U>(cookie: *mut ctype::c_void)
162where
163    T: RefUnwindSafe,
164    U: RefUnwindSafe,
165{
166    let data = Box::from_raw(cookie as *mut ProtocolData<T, U>);
167
168    panic::catch_unwind(|| ((*data).close_fn)(Box::from_raw((*data).cookie)));
169}
170
171struct ProtocolData<T, U> {
172    cookie: *mut T,
173    user_data: U,
174
175    open_fn: StreamOpen<T, U>,
176    close_fn: StreamClose<T>,
177    read_fn: StreamRead<T>,
178    seek_fn: Option<StreamSeek<T>>,
179    size_fn: Option<StreamSize<T>>,
180}
181
182/// This context holds state relevant to custom protocols.
183/// It is created by calling `Mpv::create_protocol_context`.
184pub struct ProtocolContext<'parent, T: RefUnwindSafe, U: RefUnwindSafe> {
185    ctx: NonNull<libmpv_sys::mpv_handle>,
186    protocols: Mutex<Vec<Protocol<T, U>>>,
187    _does_not_outlive: PhantomData<&'parent Mpv>,
188}
189
190unsafe impl<'parent, T: RefUnwindSafe, U: RefUnwindSafe> Send for ProtocolContext<'parent, T, U> {}
191unsafe impl<'parent, T: RefUnwindSafe, U: RefUnwindSafe> Sync for ProtocolContext<'parent, T, U> {}
192
193impl<'parent, T: RefUnwindSafe, U: RefUnwindSafe> ProtocolContext<'parent, T, U> {
194    fn new(
195        ctx: NonNull<libmpv_sys::mpv_handle>,
196        marker: PhantomData<&'parent Mpv>,
197    ) -> ProtocolContext<'parent, T, U> {
198        ProtocolContext {
199            ctx,
200            protocols: Mutex::new(Vec::new()),
201            _does_not_outlive: marker,
202        }
203    }
204
205    /// Register a custom `Protocol`. Once a protocol has been registered, it lives as long as
206    /// `Mpv`.
207    ///
208    /// Returns `Error::Mpv(MpvError::InvalidParameter)` if a protocol with the same name has
209    /// already been registered.
210    pub fn register(&self, protocol: Protocol<T, U>) -> Result<()> {
211        let mut protocols = self.protocols.lock().unwrap();
212        protocol.register(self.ctx.as_ptr())?;
213        protocols.push(protocol);
214        Ok(())
215    }
216}
217
218/// `Protocol` holds all state used by a custom protocol.
219pub struct Protocol<T: Sized + RefUnwindSafe, U: RefUnwindSafe> {
220    name: String,
221    data: *mut ProtocolData<T, U>,
222}
223
224impl<T: RefUnwindSafe, U: RefUnwindSafe> Protocol<T, U> {
225    /// `name` is the prefix of the protocol, e.g. `name://path`.
226    ///
227    /// `user_data` is data that will be passed to `open_fn`.
228    ///
229    /// # Safety
230    /// Do not call libmpv functions in any supplied function.
231    /// All panics of the provided functions are catched and can be used as generic error returns.
232    pub unsafe fn new(
233        name: String,
234        user_data: U,
235        open_fn: StreamOpen<T, U>,
236        close_fn: StreamClose<T>,
237        read_fn: StreamRead<T>,
238        seek_fn: Option<StreamSeek<T>>,
239        size_fn: Option<StreamSize<T>>,
240    ) -> Protocol<T, U> {
241        let c_layout = Layout::from_size_align(mem::size_of::<T>(), mem::align_of::<T>()).unwrap();
242        let cookie = alloc::alloc(c_layout) as *mut T;
243        let data = Box::into_raw(Box::new(ProtocolData {
244            cookie,
245            user_data,
246
247            open_fn,
248            close_fn,
249            read_fn,
250            seek_fn,
251            size_fn,
252        }));
253
254        Protocol { name, data }
255    }
256
257    fn register(&self, ctx: *mut libmpv_sys::mpv_handle) -> Result<()> {
258        let name = CString::new(&self.name[..])?;
259        unsafe {
260            mpv_err(
261                (),
262                libmpv_sys::mpv_stream_cb_add_ro(
263                    ctx,
264                    name.as_ptr(),
265                    self.data as *mut _,
266                    Some(open_wrapper::<T, U>),
267                ),
268            )
269        }
270    }
271}