azul_core/video.rs
1//! POD types for the video-playback surface
2//! (SUPER_PLAN_2 §4 Priority 6 + research).
3//!
4//! Same "dumb widget" architecture as camera/screencap
5//! (`azul_layout::widgets::video::VideoWidget`): a background thread decodes
6//! the source (vk-video - GPU decode + HTTP-range fetch) and its writeback
7//! uploads each frame into the shared GL-texture `ImageRef` + recomposites.
8//! Defined here in `azul-core` so the config crosses the FFI without
9//! `azul-layout` (or vk-video) as a dependency.
10//!
11//! Unlike the camera/screencap configs this carries a `source` string, so
12//! it's `Clone` but not `Copy`.
13
14use crate::resources::RawImageFormat;
15use crate::url::Url;
16use azul_css::{AzString, U8Vec};
17#[allow(variant_size_differences)]
18// repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
19/// Where a video widget pulls its H.264/MP4 data from — strongly typed so the
20/// decode worker matches on it directly (no `RefAny` downcast). Mirrors
21/// [`crate::screencap::ScreenCaptureSource`].
22#[repr(C, u8)]
23#[derive(Debug, Clone, PartialEq, Eq)]
24#[allow(clippy::large_enum_variant)] // #[repr(C,u8)] FFI enum: boxing a variant changes the C ABI/api.json
25pub enum VideoSource {
26 /// An HTTP(S) URL, fetched on the decode thread via an HTTP range request.
27 Url(Url),
28 /// A local filesystem path.
29 File(AzString),
30 /// Raw MP4 bytes already in memory.
31 Bytes(U8Vec),
32}
33
34impl Default for VideoSource {
35 fn default() -> Self {
36 Self::Url(Url::default())
37 }
38}
39
40/// Requested video-playback configuration.
41#[repr(C)]
42#[derive(Debug, Clone, PartialEq)]
43pub struct VideoConfig {
44 /// Where to load the video from (URL / file path / in-memory bytes).
45 pub source: VideoSource,
46 /// Seek / scrub position in seconds. Changing it across a relayout makes the
47 /// widget's merge callback tell the decode worker to seek (scrubbing
48 /// timeline) — the decoder survives relayout like the map's tile cache.
49 pub timestamp: f32,
50 /// Start playing automatically on mount.
51 pub autoplay: bool,
52 /// Restart from the beginning when the stream ends.
53 pub looping: bool,
54 /// Texture format the decoder delivers. `BGRA8` is the portable default;
55 /// `Nv12` (a later `RawImageFormat` addition) is the zero-copy path.
56 pub output_format: RawImageFormat,
57}
58
59impl Default for VideoConfig {
60 fn default() -> Self {
61 Self {
62 source: VideoSource::default(),
63 timestamp: 0.0,
64 autoplay: true,
65 looping: false,
66 output_format: RawImageFormat::BGRA8,
67 }
68 }
69}
70
71impl VideoConfig {
72 /// A default config playing `source` (autoplay on, no loop, BGRA8, t=0).
73 #[must_use]
74 pub fn new(source: VideoSource) -> Self {
75 Self {
76 source,
77 ..Self::default()
78 }
79 }
80}
81
82/// One captured or decoded frame - tightly-packed RGBA8 pixels
83/// (`width * height * 4`).
84///
85/// The unit a capture/decode worker produces, the
86/// `set_on_frame` hook hands to user code (effects / save / send), and (P8)
87/// azul-meet sends over UDP. Defined here (like [`crate::audio::AudioFrame`])
88/// so it crosses the FFI without `azul-layout` as a dependency.
89#[repr(C)]
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct VideoFrame {
92 /// Frame width in px.
93 pub width: u32,
94 /// Frame height in px.
95 pub height: u32,
96 /// Tightly-packed RGBA8 pixel bytes (`width * height * 4`).
97 pub bytes: U8Vec,
98}
99
100impl VideoFrame {
101 /// A frame wrapping `bytes` (tightly-packed RGBA8, `width * height * 4`).
102 #[must_use]
103 pub const fn new(width: u32, height: u32, bytes: U8Vec) -> Self {
104 Self {
105 width,
106 height,
107 bytes,
108 }
109 }
110}
111
112// FFI Option wrapper for a frame-pull hook / accessor. `copy = false` (U8Vec).
113impl_option!(VideoFrame, OptionVideoFrame, copy = false, [Clone, Debug]);
114
115/// One CONSUMER of a capture source's frames: a requested output size.
116///
117/// A camera or a screen share has many consumers of the same captured
118/// frame at once — the on-screen preview tile (sized by layout), a remote
119/// participant who asked for 500x200, a recorder at full size. The device
120/// captures ONCE, at the smallest size that covers every consumer
121/// (`azul_layout::image_scale::covering_size`), and every consumer gets its
122/// own resample of that one frame (`azul_layout::image_scale::fan_out`): the
123/// camera never captures more than the largest consumer needs, and nothing
124/// is ever sent bigger than the consumer asked for. Registered on a capture
125/// widget with `CameraWidget::with_consumer` /
126/// `ScreenCaptureWidget::with_consumer`; each cut frame is handed to the
127/// widget's `on_consumer_frame` hook as a [`ConsumerFrame`].
128///
129/// `id` is caller-chosen and handed back with every cut frame so one hook can
130/// serve many consumers ("client Bob" = 7, "the recorder" = 8). Id 0
131/// ([`FrameConsumer::PREVIEW_ID`]) is reserved for the widget's own on-screen
132/// tile, whose size follows layout.
133#[repr(C)]
134#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
135pub struct FrameConsumer {
136 /// Caller-chosen id, handed back with every cut frame. 0 is the preview.
137 pub id: u32,
138 /// Requested output width in px (0 = invalid, the consumer is skipped).
139 pub width: u32,
140 /// Requested output height in px (0 = invalid, the consumer is skipped).
141 pub height: u32,
142}
143
144impl FrameConsumer {
145 /// The id of the widget's own on-screen preview: its size follows the
146 /// laid-out node (device pixels), never the caller.
147 pub const PREVIEW_ID: u32 = 0;
148
149 /// A consumer `id` that wants `width` x `height` frames.
150 #[must_use]
151 pub const fn new(id: u32, width: u32, height: u32) -> Self {
152 Self { id, width, height }
153 }
154
155 /// `false` for a zero-sized request (skipped by the fan-out) or the
156 /// reserved preview id.
157 #[must_use]
158 pub const fn is_valid(&self) -> bool {
159 self.id != Self::PREVIEW_ID && self.width > 0 && self.height > 0
160 }
161}
162
163impl_vec!(
164 FrameConsumer,
165 FrameConsumerVec,
166 FrameConsumerVecDestructor,
167 FrameConsumerVecDestructorType,
168 FrameConsumerVecSlice,
169 OptionFrameConsumer
170);
171impl_vec_debug!(FrameConsumer, FrameConsumerVec);
172impl_vec_clone!(FrameConsumer, FrameConsumerVec, FrameConsumerVecDestructor);
173impl_vec_partialeq!(FrameConsumer, FrameConsumerVec);
174impl_vec_eq!(FrameConsumer, FrameConsumerVec);
175impl_vec_partialord!(FrameConsumer, FrameConsumerVec);
176impl_vec_ord!(FrameConsumer, FrameConsumerVec);
177impl_vec_hash!(FrameConsumer, FrameConsumerVec);
178
179impl_option!(
180 FrameConsumer,
181 OptionFrameConsumer,
182 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
183);
184
185/// A frame cut to one consumer's requested size: the [`FrameConsumer`] it
186/// was cut for (so one hook can route by `consumer.id`) and the resampled
187/// RGBA8 pixels (`consumer.width * consumer.height * 4`).
188#[repr(C)]
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct ConsumerFrame {
191 /// Which consumer this frame was cut for.
192 pub consumer: FrameConsumer,
193 /// The frame at the consumer's size.
194 pub frame: VideoFrame,
195}
196
197impl ConsumerFrame {
198 /// Pair a cut frame with the consumer it was cut for.
199 #[must_use]
200 pub const fn new(consumer: FrameConsumer, frame: VideoFrame) -> Self {
201 Self { consumer, frame }
202 }
203}
204
205impl_option!(
206 ConsumerFrame,
207 OptionConsumerFrame,
208 copy = false,
209 [Clone, Debug]
210);
211
212// FFI `Vec<VideoFrame>` wrapper — the list a batch decode (`DecodedVideo`,
213// `dll::desktop::extra::video_codec::pipeline`) hands back across the C ABI.
214// `VideoFrame` derives Debug + Clone + PartialEq, so mirror exactly those Vec
215// trait impls (no PartialOrd: `VideoFrame` isn't `PartialOrd`).
216impl_vec!(
217 VideoFrame,
218 VideoFrameVec,
219 VideoFrameVecDestructor,
220 VideoFrameVecDestructorType,
221 VideoFrameVecSlice,
222 OptionVideoFrame
223);
224impl_vec_debug!(VideoFrame, VideoFrameVec);
225impl_vec_clone!(VideoFrame, VideoFrameVec, VideoFrameVecDestructor);
226impl_vec_partialeq!(VideoFrame, VideoFrameVec);
227
228#[cfg(test)]
229#[path = "video_test.rs"]
230mod video_test;