playdate_graphics/
video.rs

1//! Playdate video API
2
3use core::ffi::{c_char, c_int, c_float};
4
5use sys::ffi::LCDVideoPlayer;
6use sys::ffi::{CString, CStr};
7use fs::Path;
8
9use crate::Graphics;
10use crate::bitmap::{AnyBitmap, BitmapRef};
11use crate::error::ApiError;
12use crate::error::Error;
13
14
15#[derive(Debug, Clone, Copy)]
16pub struct Video<Api: api::Api = api::Default>(Api);
17
18impl Video<api::Default> {
19	/// Creates default [`Video`] without type parameter requirement.
20	///
21	/// Uses ZST [`api::Default`].
22	#[allow(non_snake_case)]
23	pub fn Default() -> Self { Self(Default::default()) }
24}
25
26impl Video<api::Cache> {
27	/// Creates [`Video`] without type parameter requirement.
28	///
29	/// Uses [`api::Cache`].
30	#[allow(non_snake_case)]
31	pub fn Cached() -> Self { Self(Default::default()) }
32}
33
34impl<Api: Default + api::Api> Default for Video<Api> {
35	fn default() -> Self { Self(Default::default()) }
36}
37
38impl<Api: Default + api::Api> Video<Api> {
39	pub fn new() -> Self { Self(Default::default()) }
40}
41
42impl<Api: api::Api> Video<Api> {
43	pub fn new_with(api: Api) -> Self { Self(api) }
44}
45
46impl<Api: api::Api + Copy> Video<Api> {
47	/// Opens the `pdv` file at path and returns a new [`VideoPlayer`] for rendering its frames.
48	///
49	/// Calls [`sys::ffi::playdate_video::loadVideo`].
50	#[doc(alias = "sys::ffi::playdate_video::loadVideo")]
51	pub fn load<P: AsRef<Path>>(&self, path: P) -> Result<VideoPlayer<Api>, ApiError> {
52		VideoPlayer::load_with(self.0, path)
53	}
54}
55
56
57impl<Api: crate::api::Api> Graphics<Api> {
58	/// Creates a new [`Video`] instance with [cached api](api::Cache).
59	///
60	/// Equivalent to [`sys::ffi::playdate_graphics::video`]
61	#[doc(alias = "sys::ffi::playdate_graphics::video")]
62	pub fn video(&self) -> Video<api::Cache> { Video::new_with(self.0.video::<api::Cache>()) }
63
64	/// Creates a new [`Video`] instance using given `api`.
65	///
66	/// Equivalent to [`sys::ffi::playdate_graphics::video`]
67	#[doc(alias = "sys::ffi::playdate_graphics::video")]
68	pub fn video_with<VideoApi: api::Api>(&self, api: VideoApi) -> Video<VideoApi> { Video::new_with(api) }
69}
70
71
72#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))]
73pub struct VideoPlayer<Api: api::Api = api::Default, const FREE_ON_DROP: bool = true>(*mut LCDVideoPlayer, Api);
74
75impl<Api: api::Api, const FOD: bool> Drop for VideoPlayer<Api, FOD> {
76	fn drop(&mut self) {
77		if FOD && !self.0.is_null() {
78			let f = self.1.free_player();
79			unsafe { f(self.0) };
80			self.0 = core::ptr::null_mut();
81		}
82	}
83}
84
85
86impl<Api: api::Api + Copy> VideoPlayer<Api, true> {
87	/// Convert this video player into the same but that will not be freed on drop.
88	/// That means that only C-part of the player will __not__ be freed.
89	///
90	/// __Safety is guaranteed by the caller.__
91	pub fn into_shared(mut self) -> VideoPlayer<Api, false> {
92		let res = VideoPlayer(self.0, self.1);
93		self.0 = core::ptr::null_mut();
94		res
95	}
96}
97
98
99impl<Api: api::Api> VideoPlayer<Api, true> {
100	/// Opens the `pdv` file at path and returns a new video player object for rendering its frames.
101	///
102	/// Calls [`sys::ffi::playdate_video::loadVideo`].
103	#[doc(alias = "sys::ffi::playdate_video::loadVideo")]
104	pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, ApiError>
105		where Api: Default {
106		let api = Api::default();
107		Self::load_with(api, path)
108	}
109
110	/// Opens the `pdv` file at path and returns a new video player object for rendering its frames.
111	///
112	/// Calls [`sys::ffi::playdate_video::loadVideo`].
113	#[doc(alias = "sys::ffi::playdate_video::loadVideo")]
114	pub fn load_with<P: AsRef<Path>>(api: Api, path: P) -> Result<Self, ApiError> {
115		let path = CString::new(path.as_ref())?;
116
117		let f = api.load_video();
118		let ptr = unsafe { f(path.as_ptr() as *mut c_char) };
119		if ptr.is_null() {
120			// Maybe we able to `get_error` for null pointer?
121			Err(Error::Alloc.into())
122		} else {
123			Ok(Self(ptr, api))
124		}
125	}
126}
127
128
129impl<Api: api::Api, const FOD: bool> VideoPlayer<Api, FOD> {
130	/// Sets the rendering destination for the video player to the given bitmap.
131	///
132	/// If the function fails, it returns [`Error::Video`] if err-message supplied by C-API,
133	/// or [`Error::Unknown`] in other cases.
134	///
135	/// Calls [`sys::ffi::playdate_video::setContext`].
136	#[doc(alias = "sys::ffi::playdate_video::setContext")]
137	pub fn set_context<'a, 'b: 'a>(&'a self, bitmap: &'b impl AnyBitmap) -> Result<(), Error> {
138		let f = self.1.set_context();
139		if unsafe { f(self.0, bitmap.as_raw()) } != 0 {
140			Ok(())
141		} else {
142			Err(self.get_error().unwrap_or(Error::Unknown))
143		}
144	}
145
146	/// Gets the rendering destination for the video player.
147	///
148	/// If no rendering context has been set allocates a context bitmap with the same dimensions as the video will be allocated.
149	///
150	/// Calls [`sys::ffi::playdate_video::getContext`].
151	#[doc(alias = "sys::ffi::playdate_video::getContext")]
152	pub fn get_context(&self) -> Result<BitmapRef<'_>, Error> {
153		let f = self.1.get_context();
154		let ptr = unsafe { f(self.0) };
155		if ptr.is_null() {
156			Err(Error::Alloc)
157		} else {
158			Ok(BitmapRef::from(ptr))
159		}
160	}
161
162
163	/// Calls [`sys::ffi::playdate_video::useScreenContext`].
164	#[doc(alias = "sys::ffi::playdate_video::useScreenContext")]
165	pub fn use_screen_context(&self) {
166		let f = self.1.use_screen_context();
167		unsafe { f(self.0) }
168	}
169
170	/// Renders frame number `n` into the current context.
171	///
172	/// In case of error, it returns [`Error::Video`] if err-message supplied by C-API,
173	/// or [`Error::Unknown`] in other cases.
174	///
175	/// Calls [`sys::ffi::playdate_video::renderFrame`].
176	#[doc(alias = "sys::ffi::playdate_video::renderFrame")]
177	pub fn render_frame(&self, n: c_int) -> Result<(), Error> {
178		let f = self.1.render_frame();
179		if unsafe { f(self.0, n) } != 0 {
180			Ok(())
181		} else {
182			Err(self.get_error().unwrap_or(Error::Unknown))
183		}
184	}
185
186	/// Retrieves information about the video.
187	///
188	/// Calls [`sys::ffi::playdate_video::renderFrame`].
189	#[doc(alias = "sys::ffi::playdate_video::renderFrame")]
190	pub fn info(&self) -> VideoPlayerOutInfo {
191		let mut info = VideoPlayerOutInfo::default();
192		self.info_to(&mut info);
193		info
194	}
195
196	/// Retrieves information about the video, by passing values into given `info`.
197	///
198	/// Calls [`sys::ffi::playdate_video::renderFrame`].
199	#[doc(alias = "sys::ffi::playdate_video::renderFrame")]
200	pub fn info_to(&self, info: &mut VideoPlayerOutInfo) {
201		let f = self.1.get_info();
202		unsafe {
203			f(
204			  self.0,
205			  &mut info.width,
206			  &mut info.height,
207			  &mut info.frame_rate,
208			  &mut info.frame_count,
209			  &mut info.current_frame,
210			)
211		};
212	}
213
214	/// Retrieves information about the video, by passing optional mutable references.
215	///
216	/// Example:
217	/// ```no_run
218	/// let mut frame_count = Some(0);
219	/// let mut current_frame = Some(0);
220	/// player.info_raw(None, None, None,
221	///                 frame_count.as_mut(),
222	///                 current_frame.as_mut()
223	///                );
224	/// println!( "{}/{}", current_frame.unwrap(), frame_count.unwrap());
225	/// ```
226	/// Calls [`sys::ffi::playdate_video::renderFrame`].
227	#[doc(alias = "sys::ffi::playdate_video::renderFrame")]
228	pub fn info_raw(&self,
229	                width: Option<&mut c_int>,
230	                height: Option<&mut c_int>,
231	                frame_rate: Option<&mut c_float>,
232	                frame_count: Option<&mut c_int>,
233	                current_frame: Option<&mut c_int>) {
234		let f = self.1.get_info();
235		unsafe {
236			use core::ptr::null_mut;
237			f(
238			  self.0,
239			  width.map_or(null_mut() as _, |v| v as *mut _),
240			  height.map_or(null_mut() as _, |v| v as *mut _),
241			  frame_rate.map_or(null_mut() as _, |v| v as *mut _),
242			  frame_count.map_or(null_mut() as _, |v| v as *mut _),
243			  current_frame.map_or(null_mut() as _, |v| v as *mut _),
244			)
245		};
246	}
247
248
249	/// Returns [`Error`] with text describing the most recent error.
250	///
251	/// Inner text is borrowed by C, so it should be used immediately or converted to something owned.
252	///
253	/// See also [`VideoPlayer::get_error_cstr`].
254	///
255	/// Calls [`sys::ffi::playdate_video::getError`].
256	#[must_use = "Error message is borrowed from C part, must be used immediately or converted to owned string."]
257	#[inline(always)]
258	pub fn get_error(&self) -> Option<Error> { self.get_error_cstr().map(Error::video_from) }
259
260	/// Returns [`CStr`] describing the most recent error.
261	///
262	/// String-slice is borrowed by C, so it should be used immediately or converted to something owned.
263	///
264	/// Calls [`sys::ffi::playdate_video::getError`].
265	#[doc(alias = "sys::ffi::playdate_video::getError")]
266	#[must_use = "Error message is borrowed from C part, must be used immediately or converted to owned string."]
267	pub fn get_error_cstr(&self) -> Option<&CStr> {
268		let f = self.1.get_error();
269		let ptr = unsafe { f(self.0) };
270		if ptr.is_null() {
271			None
272		} else {
273			unsafe { CStr::from_ptr(ptr as _) }.into()
274		}
275	}
276}
277
278
279#[derive(Debug, Clone, Default)]
280pub struct VideoPlayerOutInfo {
281	pub width: c_int,
282	pub height: c_int,
283	pub frame_rate: c_float,
284	pub frame_count: c_int,
285	pub current_frame: c_int,
286}
287
288
289pub mod api {
290	use core::ffi::c_char;
291	use core::ffi::c_float;
292	use core::ffi::c_int;
293	use core::ptr::NonNull;
294
295	use sys::ffi::LCDBitmap;
296	use sys::ffi::LCDVideoPlayer;
297	use sys::ffi::playdate_video;
298
299
300	/// Default video api end-point, ZST.
301	///
302	/// All calls approximately costs ~4 derefs.
303	#[derive(Debug, Clone, Copy, core::default::Default)]
304	pub struct Default;
305	impl Api for Default {}
306
307
308	/// Cached video api end-point.
309	///
310	/// Stores one reference, so size on stack is eq `usize`.
311	///
312	/// All calls approximately costs ~1 deref.
313	#[derive(Clone, Copy)]
314	#[cfg_attr(feature = "bindings-derive-debug", derive(Debug))]
315	pub struct Cache(&'static playdate_video);
316
317	impl core::default::Default for Cache {
318		fn default() -> Self { Self(sys::api!(graphics.video)) }
319	}
320
321	impl From<*const playdate_video> for Cache {
322		#[inline(always)]
323		fn from(ptr: *const playdate_video) -> Self { Self(unsafe { ptr.as_ref() }.expect("video")) }
324	}
325
326	impl From<&'static playdate_video> for Cache {
327		#[inline(always)]
328		fn from(r: &'static playdate_video) -> Self { Self(r) }
329	}
330
331	impl From<NonNull<playdate_video>> for Cache {
332		#[inline(always)]
333		fn from(ptr: NonNull<playdate_video>) -> Self { Self(unsafe { ptr.as_ref() }) }
334	}
335
336	impl From<&'_ NonNull<playdate_video>> for Cache {
337		#[inline(always)]
338		fn from(ptr: &NonNull<playdate_video>) -> Self { Self(unsafe { ptr.as_ref() }) }
339	}
340
341
342	impl Api for Cache {
343		#[inline(always)]
344		fn load_video(&self) -> unsafe extern "C" fn(path: *const c_char) -> *mut LCDVideoPlayer {
345			self.0.loadVideo.expect("loadVideo")
346		}
347
348		#[inline(always)]
349		fn free_player(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) {
350			self.0.freePlayer.expect("freePlayer")
351		}
352
353		#[inline(always)]
354		fn set_context(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer, context: *mut LCDBitmap) -> c_int {
355			self.0.setContext.expect("setContext")
356		}
357
358		#[inline(always)]
359		fn use_screen_context(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) {
360			self.0.useScreenContext.expect("useScreenContext")
361		}
362
363		#[inline(always)]
364		fn render_frame(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer, n: c_int) -> c_int {
365			self.0.renderFrame.expect("renderFrame")
366		}
367
368		#[inline(always)]
369		fn get_error(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) -> *const c_char {
370			self.0.getError.expect("getError")
371		}
372
373		#[inline(always)]
374		fn get_info(
375			&self)
376			-> unsafe extern "C" fn(p: *mut LCDVideoPlayer,
377			                        outWidth: *mut c_int,
378			                        outHeight: *mut c_int,
379			                        outFrameRate: *mut c_float,
380			                        outFrameCount: *mut c_int,
381			                        outCurrentFrame: *mut c_int) {
382			*sys::api!(graphics.video.getInfo)
383		}
384
385		#[inline(always)]
386		fn get_context(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) -> *mut LCDBitmap {
387			self.0.getContext.expect("getContext")
388		}
389	}
390
391
392	pub trait Api {
393		/// Equivalent to [`sys::ffi::playdate_video::loadVideo`]
394		#[doc(alias = "sys::ffi::playdate_video::loadVideo")]
395		#[inline(always)]
396		fn load_video(&self) -> unsafe extern "C" fn(path: *const c_char) -> *mut LCDVideoPlayer {
397			*sys::api!(graphics.video.loadVideo)
398		}
399
400		/// Equivalent to [`sys::ffi::playdate_video::freePlayer`]
401		#[doc(alias = "sys::ffi::playdate_video::freePlayer")]
402		#[inline(always)]
403		fn free_player(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) {
404			*sys::api!(graphics.video.freePlayer)
405		}
406
407		/// Equivalent to [`sys::ffi::playdate_video::setContext`]
408		#[doc(alias = "sys::ffi::playdate_video::setContext")]
409		#[inline(always)]
410		fn set_context(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer, context: *mut LCDBitmap) -> c_int {
411			*sys::api!(graphics.video.setContext)
412		}
413
414		/// Equivalent to [`sys::ffi::playdate_video::useScreenContext`]
415		#[doc(alias = "sys::ffi::playdate_video::useScreenContext")]
416		#[inline(always)]
417		fn use_screen_context(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) {
418			*sys::api!(graphics.video.useScreenContext)
419		}
420
421		/// Equivalent to [`sys::ffi::playdate_video::renderFrame`]
422		#[doc(alias = "sys::ffi::playdate_video::renderFrame")]
423		#[inline(always)]
424		fn render_frame(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer, n: c_int) -> c_int {
425			*sys::api!(graphics.video.renderFrame)
426		}
427
428		/// Equivalent to [`sys::ffi::playdate_video::getError`]
429		#[doc(alias = "sys::ffi::playdate_video::getError")]
430		#[inline(always)]
431		fn get_error(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) -> *const c_char {
432			*sys::api!(graphics.video.getError)
433		}
434
435		/// Equivalent to [`sys::ffi::playdate_video::getInfo`]
436		#[doc(alias = "sys::ffi::playdate_video::getInfo")]
437		#[inline(always)]
438		fn get_info(
439			&self)
440			-> unsafe extern "C" fn(p: *mut LCDVideoPlayer,
441			                        outWidth: *mut c_int,
442			                        outHeight: *mut c_int,
443			                        outFrameRate: *mut c_float,
444			                        outFrameCount: *mut c_int,
445			                        outCurrentFrame: *mut c_int) {
446			*sys::api!(graphics.video.getInfo)
447		}
448
449		/// Equivalent to [`sys::ffi::playdate_video::getContext`]
450		#[doc(alias = "sys::ffi::playdate_video::getContext")]
451		#[inline(always)]
452		fn get_context(&self) -> unsafe extern "C" fn(p: *mut LCDVideoPlayer) -> *mut LCDBitmap {
453			*sys::api!(graphics.video.getContext)
454		}
455	}
456}