use std::cell::{Cell, RefCell};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use crate::image::RasterImage;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VideoInfo {
pub width: u32,
pub height: u32,
pub fps: f64,
pub duration: f64,
pub has_audio: bool,
}
pub trait VideoSource {
fn info(&self) -> VideoInfo;
fn frame(&self, index: u64) -> Option<Rc<RasterImage>>;
}
pub type VideoOpener = Rc<dyn Fn(&Path) -> Result<Rc<dyn VideoSource>, String>>;
thread_local! {
static OPENER: RefCell<Option<VideoOpener>> = const { RefCell::new(None) };
}
pub fn set_video_opener(opener: VideoOpener) {
OPENER.with(|o| *o.borrow_mut() = Some(opener));
}
pub(crate) fn open(path: &Path) -> (Rc<dyn VideoSource>, Option<String>) {
let opened = OPENER.with(|o| o.borrow().clone()).map(|open| open(path));
match opened {
Some(Ok(source)) => (source, None),
Some(Err(e)) => (Rc::new(Missing), Some(e)),
None => (
Rc::new(Missing),
Some("video decoding needs the native engine with the `video` feature".into()),
),
}
}
struct Missing;
impl VideoSource for Missing {
fn info(&self) -> VideoInfo {
VideoInfo {
width: 1280,
height: 720,
fps: 30.0,
duration: 0.0,
has_audio: false,
}
}
fn frame(&self, _: u64) -> Option<Rc<RasterImage>> {
None
}
}
#[doc(hidden)]
#[derive(Default)]
pub struct Playback {
pub(crate) playing: Cell<bool>,
pub(crate) generation: Cell<u32>,
pub(crate) jumped: Cell<bool>,
pub(crate) segment: Cell<Option<(usize, f64, f64)>>,
}
#[doc(hidden)]
pub struct VideoData {
pub(crate) source: Rc<dyn VideoSource>,
pub(crate) path: PathBuf,
pub(crate) problem: Option<String>,
pub(crate) muted: bool,
pub(crate) playback: Rc<Playback>,
}
impl VideoData {
pub(crate) fn index_at(&self, t: f64) -> u64 {
let info = self.source.info();
let last = (info.duration * info.fps).ceil().max(1.0) as u64 - 1;
((t.max(0.0) * info.fps + 1e-6).floor() as u64).min(last)
}
}