Skip to main content

freya_video/
viewer.rs

1use std::{
2    any::Any,
3    borrow::Cow,
4    collections::HashMap,
5    rc::Rc,
6};
7
8use freya_components::loader::CircularLoader;
9use freya_core::{
10    elements::image::{
11        AspectRatio,
12        ImageData,
13        SamplingMode,
14    },
15    integration::*,
16    prelude::*,
17};
18use freya_engine::prelude::{
19    ClipOp,
20    CubicResampler,
21    FilterMode,
22    MipmapMode,
23    Paint,
24    SamplingOptions,
25    SkRect,
26};
27use torin::prelude::Size2D;
28
29use crate::{
30    PlaybackState,
31    VideoFrame,
32    VideoPlayer,
33};
34
35/// Renders the current frame of a [`VideoPlayer`].
36#[derive(PartialEq)]
37pub struct VideoViewer {
38    player: VideoPlayer,
39
40    layout: LayoutData,
41    image_data: ImageData,
42    accessibility: AccessibilityData,
43
44    key: DiffKey,
45}
46
47impl VideoViewer {
48    pub fn new(player: VideoPlayer) -> Self {
49        Self {
50            player,
51            layout: LayoutData::default(),
52            image_data: ImageData::default(),
53            accessibility: AccessibilityData::default(),
54            key: DiffKey::None,
55        }
56    }
57}
58
59impl KeyExt for VideoViewer {
60    fn write_key(&mut self) -> &mut DiffKey {
61        &mut self.key
62    }
63}
64
65impl LayoutExt for VideoViewer {
66    fn get_layout(&mut self) -> &mut LayoutData {
67        &mut self.layout
68    }
69}
70
71impl ContainerSizeExt for VideoViewer {}
72
73impl ImageExt for VideoViewer {
74    fn get_image_data(&mut self) -> &mut ImageData {
75        &mut self.image_data
76    }
77}
78
79impl AccessibilityExt for VideoViewer {
80    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
81        &mut self.accessibility
82    }
83}
84
85impl Component for VideoViewer {
86    fn render(&self) -> impl IntoElement {
87        match (self.player.frame(), self.player.state()) {
88            (Some(frame), _) => video(frame)
89                .accessibility(self.accessibility.clone())
90                .a11y_role(AccessibilityRole::Video)
91                .a11y_focusable(true)
92                .layout(self.layout.clone())
93                .image_data(self.image_data.clone())
94                .into_element(),
95            (None, PlaybackState::Errored) => "Failed to decode video".into_element(),
96            (None, _) => rect()
97                .layout(self.layout.clone())
98                .center()
99                .child(CircularLoader::new())
100                .into(),
101        }
102    }
103
104    fn render_key(&self) -> DiffKey {
105        self.key.clone().or(self.default_key())
106    }
107}
108
109/// Low-level element that paints a single [`VideoFrame`]. Prefer [`VideoViewer`].
110pub fn video(frame: VideoFrame) -> Video {
111    Video {
112        key: DiffKey::None,
113        element: VideoElement {
114            frame,
115            accessibility: AccessibilityData::default(),
116            layout: LayoutData::default(),
117            event_handlers: HashMap::default(),
118            image_data: ImageData::default(),
119        },
120    }
121}
122
123pub struct Video {
124    key: DiffKey,
125    element: VideoElement,
126}
127
128impl Video {
129    pub fn try_downcast(element: &dyn ElementExt) -> Option<VideoElement> {
130        (element as &dyn Any)
131            .downcast_ref::<VideoElement>()
132            .cloned()
133    }
134}
135
136impl From<Video> for Element {
137    fn from(value: Video) -> Self {
138        Element::Element {
139            key: value.key,
140            element: Rc::new(value.element),
141            elements: vec![],
142        }
143    }
144}
145
146impl KeyExt for Video {
147    fn write_key(&mut self) -> &mut DiffKey {
148        &mut self.key
149    }
150}
151
152impl LayoutExt for Video {
153    fn get_layout(&mut self) -> &mut LayoutData {
154        &mut self.element.layout
155    }
156}
157
158impl ContainerExt for Video {}
159
160impl ImageExt for Video {
161    fn get_image_data(&mut self) -> &mut ImageData {
162        &mut self.element.image_data
163    }
164}
165
166impl AccessibilityExt for Video {
167    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
168        &mut self.element.accessibility
169    }
170}
171
172impl EventHandlersExt for Video {
173    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
174        &mut self.element.event_handlers
175    }
176}
177
178impl MaybeExt for Video {}
179
180#[derive(Clone)]
181pub struct VideoElement {
182    accessibility: AccessibilityData,
183    layout: LayoutData,
184    event_handlers: FxHashMap<EventName, EventHandlerType>,
185    frame: VideoFrame,
186    image_data: ImageData,
187}
188
189impl PartialEq for VideoElement {
190    fn eq(&self, other: &Self) -> bool {
191        self.accessibility == other.accessibility
192            && self.layout == other.layout
193            && self.image_data == other.image_data
194            && self.frame == other.frame
195    }
196}
197
198impl ElementExt for VideoElement {
199    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
200        let Some(video) = (other.as_ref() as &dyn Any).downcast_ref::<VideoElement>() else {
201            return false;
202        };
203        self != video
204    }
205
206    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
207        let Some(video) = (other.as_ref() as &dyn Any).downcast_ref::<VideoElement>() else {
208            return DiffModifies::all();
209        };
210
211        let mut diff = DiffModifies::empty();
212
213        if self.accessibility != video.accessibility {
214            diff.insert(DiffModifies::ACCESSIBILITY);
215        }
216
217        if self.layout != video.layout {
218            diff.insert(DiffModifies::LAYOUT);
219        }
220
221        if self.frame != video.frame {
222            diff.insert(DiffModifies::LAYOUT);
223            diff.insert(DiffModifies::STYLE);
224        }
225
226        diff
227    }
228
229    fn layout(&'_ self) -> Cow<'_, LayoutData> {
230        Cow::Borrowed(&self.layout)
231    }
232
233    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
234        None
235    }
236
237    fn style(&'_ self) -> Cow<'_, StyleState> {
238        Cow::Owned(StyleState::default())
239    }
240
241    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
242        Cow::Owned(TextStyleData::default())
243    }
244
245    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
246        Cow::Borrowed(&self.accessibility)
247    }
248
249    fn should_measure_inner_children(&self) -> bool {
250        false
251    }
252
253    fn should_hook_measurement(&self) -> bool {
254        true
255    }
256
257    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
258        let image = self.frame.image();
259        let image_width = image.width() as f32;
260        let image_height = image.height() as f32;
261
262        let width_ratio = context.area_size.width / image_width;
263        let height_ratio = context.area_size.height / image_height;
264        let scaled = |ratio: f32| Size2D::new(image_width * ratio, image_height * ratio);
265
266        let size = match self.image_data.aspect_ratio {
267            AspectRatio::Max => scaled(width_ratio.max(height_ratio)),
268            AspectRatio::Min => scaled(width_ratio.min(height_ratio)),
269            AspectRatio::Fit => Size2D::new(image_width, image_height),
270            AspectRatio::None => *context.area_size,
271        };
272
273        Some((size, Rc::new(size)))
274    }
275
276    fn clip(&self, context: ClipContext) {
277        let area = context.visible_area;
278        context.canvas.clip_rect(
279            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
280            ClipOp::Intersect,
281            true,
282        );
283    }
284
285    fn render(&self, context: RenderContext) {
286        let size = context
287            .layout_node
288            .data
289            .as_ref()
290            .unwrap()
291            .downcast_ref::<Size2D>()
292            .unwrap();
293        let area = context.layout_node.visible_area();
294
295        let mut rect = SkRect::new(
296            area.min_x(),
297            area.min_y(),
298            area.min_x() + size.width,
299            area.min_y() + size.height,
300        );
301        if self.image_data.image_cover == ImageCover::Center {
302            let offset_x = (size.width - area.width()) / 2.;
303            let offset_y = (size.height - area.height()) / 2.;
304            rect.left -= offset_x;
305            rect.right -= offset_x;
306            rect.top -= offset_y;
307            rect.bottom -= offset_y;
308        }
309
310        let mut paint = Paint::default();
311        paint.set_anti_alias(true);
312
313        context.canvas.save();
314        context.canvas.clip_rect(
315            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
316            ClipOp::Intersect,
317            true,
318        );
319        context.canvas.draw_image_rect_with_sampling_options(
320            self.frame.image(),
321            None,
322            rect,
323            sampling_options(&self.image_data.sampling_mode),
324            &paint,
325        );
326        context.canvas.restore();
327    }
328}
329
330fn sampling_options(mode: &SamplingMode) -> SamplingOptions {
331    match mode {
332        SamplingMode::Nearest => SamplingOptions::new(FilterMode::Nearest, MipmapMode::None),
333        SamplingMode::Bilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::None),
334        SamplingMode::Trilinear => SamplingOptions::new(FilterMode::Linear, MipmapMode::Linear),
335        SamplingMode::Mitchell => SamplingOptions::from(CubicResampler::mitchell()),
336        SamplingMode::CatmullRom => SamplingOptions::from(CubicResampler::catmull_rom()),
337    }
338}