1use std::cell::{Cell, RefCell};
14use std::path::{Path, PathBuf};
15use std::rc::Rc;
16
17use crate::image::RasterImage;
18
19#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct VideoInfo {
22 pub width: u32,
24 pub height: u32,
26 pub fps: f64,
28 pub duration: f64,
30 pub has_audio: bool,
32}
33
34pub trait VideoSource {
36 fn info(&self) -> VideoInfo;
38 fn frame(&self, index: u64) -> Option<Rc<RasterImage>>;
41}
42
43pub type VideoOpener = Rc<dyn Fn(&Path) -> Result<Rc<dyn VideoSource>, String>>;
45
46thread_local! {
47 static OPENER: RefCell<Option<VideoOpener>> = const { RefCell::new(None) };
48}
49
50pub fn set_video_opener(opener: VideoOpener) {
53 OPENER.with(|o| *o.borrow_mut() = Some(opener));
54}
55
56pub(crate) fn open(path: &Path) -> (Rc<dyn VideoSource>, Option<String>) {
58 let opened = OPENER.with(|o| o.borrow().clone()).map(|open| open(path));
59 match opened {
60 Some(Ok(source)) => (source, None),
61 Some(Err(e)) => (Rc::new(Missing), Some(e)),
62 None => (
63 Rc::new(Missing),
64 Some("video decoding needs the native engine with the `video` feature".into()),
65 ),
66 }
67}
68
69struct Missing;
71
72impl VideoSource for Missing {
73 fn info(&self) -> VideoInfo {
74 VideoInfo {
75 width: 1280,
76 height: 720,
77 fps: 30.0,
78 duration: 0.0,
79 has_audio: false,
80 }
81 }
82
83 fn frame(&self, _: u64) -> Option<Rc<RasterImage>> {
84 None
85 }
86}
87
88#[doc(hidden)]
90#[derive(Default)]
91pub struct Playback {
92 pub(crate) playing: Cell<bool>,
93 pub(crate) generation: Cell<u32>,
95 pub(crate) jumped: Cell<bool>,
97 pub(crate) segment: Cell<Option<(usize, f64, f64)>>,
99}
100
101#[doc(hidden)]
103pub struct VideoData {
104 pub(crate) source: Rc<dyn VideoSource>,
105 pub(crate) path: PathBuf,
106 pub(crate) problem: Option<String>,
108 pub(crate) muted: bool,
109 pub(crate) playback: Rc<Playback>,
110}
111
112impl VideoData {
113 pub(crate) fn index_at(&self, t: f64) -> u64 {
115 let info = self.source.info();
116 let last = (info.duration * info.fps).ceil().max(1.0) as u64 - 1;
117 ((t.max(0.0) * info.fps + 1e-6).floor() as u64).min(last)
118 }
119}