1use crate::resources::RawImageFormat;
15use crate::url::Url;
16use azul_css::{AzString, U8Vec};
17#[allow(variant_size_differences)] #[repr(C, u8)]
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[allow(clippy::large_enum_variant)] pub enum VideoSource {
25 Url(Url),
27 File(AzString),
29 Bytes(U8Vec),
31}
32
33impl Default for VideoSource {
34 fn default() -> Self {
35 Self::Url(Url::default())
36 }
37}
38
39#[repr(C)]
41#[derive(Debug, Clone, PartialEq)]
42pub struct VideoConfig {
43 pub source: VideoSource,
45 pub timestamp: f32,
49 pub autoplay: bool,
51 pub looping: bool,
53 pub output_format: RawImageFormat,
56}
57
58impl Default for VideoConfig {
59 fn default() -> Self {
60 Self {
61 source: VideoSource::default(),
62 timestamp: 0.0,
63 autoplay: true,
64 looping: false,
65 output_format: RawImageFormat::BGRA8,
66 }
67 }
68}
69
70impl VideoConfig {
71 #[must_use] pub fn new(source: VideoSource) -> Self {
73 Self {
74 source,
75 ..Self::default()
76 }
77 }
78}
79
80#[repr(C)]
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct VideoFrame {
90 pub width: u32,
92 pub height: u32,
94 pub bytes: U8Vec,
96}
97
98impl VideoFrame {
99 #[must_use] pub const fn new(width: u32, height: u32, bytes: U8Vec) -> Self {
101 Self {
102 width,
103 height,
104 bytes,
105 }
106 }
107}
108
109impl_option!(VideoFrame, OptionVideoFrame, copy = false, [Clone, Debug]);
111
112impl_vec!(VideoFrame, VideoFrameVec, VideoFrameVecDestructor, VideoFrameVecDestructorType, VideoFrameVecSlice, OptionVideoFrame);
117impl_vec_debug!(VideoFrame, VideoFrameVec);
118impl_vec_clone!(VideoFrame, VideoFrameVec, VideoFrameVecDestructor);
119impl_vec_partialeq!(VideoFrame, VideoFrameVec);
120
121#[cfg(test)]
122mod autotest_generated {
123 use alloc::{string::String, vec, vec::Vec};
124
125 use super::*;
126
127 fn u8v(bytes: Vec<u8>) -> U8Vec {
132 U8Vec::from_vec(bytes)
133 }
134
135 fn frame(width: u32, height: u32, bytes: Vec<u8>) -> VideoFrame {
136 VideoFrame::new(width, height, u8v(bytes))
137 }
138
139 fn source_bytes(source: &VideoSource) -> &U8Vec {
142 match source {
143 VideoSource::Bytes(b) => b,
144 other => panic!("expected VideoSource::Bytes, got {other:?}"),
145 }
146 }
147
148 fn declared_len(width: u32, height: u32) -> u128 {
151 u128::from(width) * u128::from(height) * 4
152 }
153
154 #[test]
159 fn config_new_fields_match_args() {
160 let url = Url::from_parts("https", "example.com", 443, "/movie.mp4");
161 let c = VideoConfig::new(VideoSource::Url(url.clone()));
162
163 assert_eq!(c.source, VideoSource::Url(url));
164 assert!((c.timestamp - 0.0).abs() < f32::EPSILON);
165 assert!(c.autoplay);
166 assert!(!c.looping);
167 assert_eq!(c.output_format, RawImageFormat::BGRA8);
168 }
169
170 #[test]
173 fn config_new_only_overrides_source() {
174 let d = VideoConfig::default();
175
176 for source in [
177 VideoSource::Url(Url::from_parts("http", "a.tld", 8080, "/v")),
178 VideoSource::File(AzString::from_const_str("/tmp/clip.mp4")),
179 VideoSource::Bytes(u8v(vec![0xFF; 8])),
180 ] {
181 let c = VideoConfig::new(source.clone());
182 assert_eq!(c.source, source, "source must round-trip unchanged");
183 assert_eq!(c.timestamp.to_bits(), d.timestamp.to_bits());
184 assert_eq!(c.autoplay, d.autoplay);
185 assert_eq!(c.looping, d.looping);
186 assert_eq!(c.output_format, d.output_format);
187 }
188 }
189
190 #[test]
192 fn config_new_with_default_source_equals_default() {
193 assert_eq!(VideoConfig::new(VideoSource::default()), VideoConfig::default());
194 assert_eq!(VideoSource::default(), VideoSource::Url(Url::default()));
195 }
196
197 #[test]
201 fn config_new_unicode_file_path_roundtrip() {
202 let path = String::from("/tmp/vídeos/𝔘𝔫𝔦/影片 🎬/مقطع/e\u{0301}.mp4");
203 let c = VideoConfig::new(VideoSource::File(AzString::from(path.clone())));
204
205 match &c.source {
206 VideoSource::File(s) => {
207 assert_eq!(s.as_str(), path.as_str());
208 assert_eq!(s.as_str().len(), path.len(), "byte length preserved");
209 assert_eq!(s.as_str().chars().count(), path.chars().count());
210 }
211 other => panic!("expected File, got {other:?}"),
212 }
213 }
214
215 #[test]
219 fn config_new_file_path_with_interior_nul_preserved() {
220 let path = String::from("/tmp/a\0b\r\n\t.mp4");
221 let c = VideoConfig::new(VideoSource::File(AzString::from(path.clone())));
222
223 match &c.source {
224 VideoSource::File(s) => {
225 assert_eq!(s.as_str(), path.as_str());
226 assert_eq!(s.as_str().len(), 15);
227 assert!(s.as_str().contains('\0'));
228 }
229 other => panic!("expected File, got {other:?}"),
230 }
231 }
232
233 #[test]
235 fn config_new_empty_sources_no_panic() {
236 let empty_file = VideoConfig::new(VideoSource::File(AzString::from(String::new())));
237 match &empty_file.source {
238 VideoSource::File(s) => assert_eq!(s.as_str(), ""),
239 other => panic!("expected File, got {other:?}"),
240 }
241
242 let empty_bytes = VideoConfig::new(VideoSource::Bytes(u8v(Vec::new())));
243 let b = source_bytes(&empty_bytes.source);
244 assert!(b.is_empty());
245 assert_eq!(b.len(), 0);
246 assert_eq!(b.as_ref(), &[] as &[u8]);
247 }
248
249 #[test]
252 fn config_new_huge_bytes_source_preserved() {
253 const N: usize = 1 << 20;
254 let data: Vec<u8> = (0..N).map(|i| (i % 251) as u8).collect();
255
256 let c = VideoConfig::new(VideoSource::Bytes(u8v(data.clone())));
257 let b = source_bytes(&c.source);
258
259 let last = ((N - 1) % 251) as u8;
260 assert_eq!(b.len(), N);
261 assert_eq!(b.as_ref(), data.as_slice());
262 assert_eq!(b.get(0), Some(&0));
263 assert_eq!(b.get(N - 1), Some(&last));
264 assert_eq!(b.get(N), None, "one past the end must be None, not UB");
265 }
266
267 #[test]
271 fn config_clone_of_bytes_is_deep_and_survives_original_drop() {
272 let original = VideoConfig::new(VideoSource::Bytes(u8v(vec![1, 2, 3, 4, 5])));
273 let original_ptr = source_bytes(&original.source).as_ptr();
274
275 let cloned = original.clone();
276 let cloned_ptr = source_bytes(&cloned.source).as_ptr();
277 assert_ne!(original_ptr, cloned_ptr, "clone must not alias the original buffer");
278
279 drop(original);
280
281 assert_eq!(source_bytes(&cloned.source).as_ref(), &[1, 2, 3, 4, 5]);
282 }
283
284 #[test]
288 fn config_source_variants_are_distinct() {
289 let text = "https://example.com/v.mp4";
290 let as_file = VideoConfig::new(VideoSource::File(AzString::from_const_str(text)));
291 let as_url = VideoConfig::new(VideoSource::Url(Url::from_parts(
292 "https",
293 "example.com",
294 443,
295 "/v.mp4",
296 )));
297
298 assert_ne!(as_file, as_url);
299 assert_ne!(as_file.source, as_url.source);
300 assert_eq!(as_url.source, as_url.source.clone());
301 }
302
303 #[test]
306 fn config_extreme_timestamps_stored_verbatim() {
307 let base = VideoConfig::new(VideoSource::default());
308
309 for t in [
310 f32::INFINITY,
311 f32::NEG_INFINITY,
312 f32::MAX,
313 f32::MIN,
314 f32::MIN_POSITIVE,
315 f32::MIN_POSITIVE / 2.0, -1.0,
317 1e30,
318 ] {
319 let c = VideoConfig {
320 timestamp: t,
321 ..base.clone()
322 };
323 assert_eq!(c.timestamp.to_bits(), t.to_bits(), "timestamp {t} was altered");
324 assert_eq!(c, c.clone(), "non-NaN config must be self-equal");
325 }
326 }
327
328 #[test]
333 fn config_nan_timestamp_breaks_reflexive_equality() {
334 let nan_a = VideoConfig {
335 timestamp: f32::NAN,
336 ..VideoConfig::new(VideoSource::default())
337 };
338 let nan_b = nan_a.clone();
339
340 assert!(nan_a.timestamp.is_nan());
341 assert_ne!(nan_a, nan_b, "NaN timestamp: derived PartialEq is not reflexive");
342
343 let fresh = VideoConfig::new(VideoSource::default());
345 assert!(!fresh.timestamp.is_nan());
346 assert_eq!(fresh, fresh.clone());
347 }
348
349 #[test]
352 fn config_negative_zero_timestamp_equals_default() {
353 let fresh = VideoConfig::new(VideoSource::default());
354 let neg_zero = VideoConfig {
355 timestamp: -0.0,
356 ..VideoConfig::new(VideoSource::default())
357 };
358
359 assert!(neg_zero.timestamp.is_sign_negative());
360 assert_ne!(neg_zero.timestamp.to_bits(), fresh.timestamp.to_bits());
361 assert_eq!(neg_zero, fresh, "IEEE: -0.0 == +0.0");
362 }
363
364 #[test]
368 fn frame_new_fields_match_args() {
369 let pixels: Vec<u8> = (0..(3 * 2 * 4)).map(|i| i as u8).collect();
370 let f = frame(3, 2, pixels.clone());
371
372 assert_eq!(f.width, 3);
373 assert_eq!(f.height, 2);
374 assert_eq!(f.bytes.as_ref(), pixels.as_slice());
375 assert_eq!(u128::from(f.bytes.len() as u64), declared_len(3, 2));
376 }
377
378 #[test]
381 fn frame_bytes_roundtrip_all_byte_values() {
382 let pixels: Vec<u8> = (0..=255u8).collect(); let f = frame(8, 8, pixels.clone());
384
385 assert_eq!(f.bytes.as_ref(), pixels.as_slice());
386 assert_eq!(f.bytes.len(), 256);
387 assert_eq!(u128::from(f.bytes.len() as u64), declared_len(8, 8));
388 assert_eq!(f.bytes.get(0), Some(&0));
389 assert_eq!(f.bytes.get(255), Some(&255));
390 assert_eq!(f.bytes.get(256), None);
391 assert!(f.bytes.iter().copied().eq(pixels.iter().copied()));
392 }
393
394 #[test]
396 fn frame_new_zero_dimensions_empty_bytes() {
397 let f = frame(0, 0, Vec::new());
398
399 assert_eq!(f.width, 0);
400 assert_eq!(f.height, 0);
401 assert!(f.bytes.is_empty());
402 assert_eq!(f.bytes.as_ref(), &[] as &[u8]);
403 assert_eq!(declared_len(0, 0), 0);
404 }
405
406 #[test]
408 fn frame_new_degenerate_strips() {
409 let wide = frame(1920, 0, Vec::new());
410 assert_eq!((wide.width, wide.height), (1920, 0));
411 assert!(wide.bytes.is_empty());
412
413 let tall = frame(0, 1080, Vec::new());
414 assert_eq!((tall.width, tall.height), (0, 1080));
415 assert!(tall.bytes.is_empty());
416 }
417
418 #[test]
423 fn frame_new_max_dimensions_no_panic_and_size_math_overflows() {
424 let f = VideoFrame::new(u32::MAX, u32::MAX, U8Vec::from_vec(Vec::new()));
425
426 assert_eq!(f.width, u32::MAX);
427 assert_eq!(f.height, u32::MAX);
428 assert!(f.bytes.is_empty(), "new does no allocation of its own");
429
430 assert_eq!(u32::MAX.checked_mul(u32::MAX), None);
432 assert_eq!(
433 u64::from(u32::MAX)
434 .checked_mul(u64::from(u32::MAX))
435 .and_then(|px| px.checked_mul(4)),
436 None,
437 "even u64 overflows: callers must size-check before allocating"
438 );
439 assert_eq!(declared_len(u32::MAX, u32::MAX), 4 * (u128::from(u32::MAX)).pow(2));
441 }
442
443 #[test]
446 fn frame_new_u32_pixel_size_overflow_boundary() {
447 let f = VideoFrame::new(32_768, 32_768, U8Vec::from_vec(Vec::new()));
448 assert_eq!((f.width, f.height), (32_768, 32_768));
449
450 assert_eq!(
451 32_768u32.checked_mul(32_768).and_then(|px| px.checked_mul(4)),
452 None,
453 "2^30 px * 4 == 2^32 overflows u32"
454 );
455 assert_eq!(declared_len(32_768, 32_768), 1u128 << 32);
456 assert_eq!(
458 32_767u32.checked_mul(32_768).and_then(|px| px.checked_mul(4)),
459 Some(4_294_836_224)
460 );
461 }
462
463 #[test]
468 fn frame_new_does_not_validate_buffer_length() {
469 let short = frame(1, 1, vec![0xAA, 0xBB, 0xCC]); assert_eq!(short.bytes.len(), 3);
471 assert_ne!(u128::from(short.bytes.len() as u64), declared_len(1, 1));
472
473 let long = frame(1, 1, vec![0; 64]); assert_eq!(long.bytes.len(), 64);
475
476 let lying = frame(3840, 2160, Vec::new());
478 assert!(lying.bytes.is_empty());
479 assert_eq!(declared_len(3840, 2160), 33_177_600);
480 }
481
482 #[test]
484 fn frame_new_is_const_evaluable() {
485 const F: VideoFrame = VideoFrame::new(1, 1, U8Vec::from_const_slice(&[1, 2, 3, 4]));
486
487 assert_eq!(F.width, 1);
488 assert_eq!(F.height, 1);
489 assert_eq!(F.bytes.as_ref(), &[1, 2, 3, 4]);
490 assert_eq!(u128::from(F.bytes.len() as u64), declared_len(1, 1));
491 }
492
493 #[test]
496 fn frame_equality_is_fieldwise() {
497 let base = frame(2, 1, vec![1, 2, 3, 4, 5, 6, 7, 8]);
498
499 assert_eq!(base, frame(2, 1, vec![1, 2, 3, 4, 5, 6, 7, 8]));
500 assert_ne!(base, frame(1, 2, vec![1, 2, 3, 4, 5, 6, 7, 8]), "w/h swap differs");
501 assert_ne!(base, frame(2, 1, vec![1, 2, 3, 4, 5, 6, 7, 9]), "last byte differs");
502 assert_ne!(base, frame(2, 1, vec![1, 2, 3, 4]), "truncated buffer differs");
503 }
504
505 #[test]
508 fn frame_clone_is_deep_and_survives_original_drop() {
509 let original = frame(2, 2, (0..16).collect());
510 let original_ptr = original.bytes.as_ptr();
511
512 let cloned = original.clone();
513 assert_ne!(original_ptr, cloned.bytes.as_ptr(), "clone must not alias");
514 assert_eq!(cloned, original);
515
516 drop(original);
517
518 assert_eq!(cloned.bytes.as_ref(), (0..16u8).collect::<Vec<_>>().as_slice());
519 assert_eq!(cloned.width, 2);
520 assert_eq!(cloned.height, 2);
521 }
522
523 #[test]
528 fn option_video_frame_roundtrip() {
529 let f = frame(1, 1, vec![9, 8, 7, 6]);
530
531 let some: OptionVideoFrame = Some(f.clone()).into();
532 assert!(some.is_some());
533 assert!(!some.is_none());
534 assert_eq!(some.as_ref(), Some(&f));
535 assert_eq!(Option::<VideoFrame>::from(some.clone()), Some(f.clone()));
536 assert_eq!(some.into_option(), Some(f));
537
538 let none: OptionVideoFrame = Option::<VideoFrame>::None.into();
539 assert!(none.is_none());
540 assert_eq!(none.as_ref(), None);
541 assert_eq!(Option::<VideoFrame>::from(none), None);
542 assert_eq!(OptionVideoFrame::default().into_option(), None);
543 }
544
545 #[test]
547 fn option_video_frame_replace_returns_previous() {
548 let first = frame(1, 1, vec![1, 1, 1, 1]);
549 let second = frame(1, 1, vec![2, 2, 2, 2]);
550
551 let mut slot = OptionVideoFrame::None;
552 assert_eq!(slot.replace(first.clone()).into_option(), None);
553 assert_eq!(slot.replace(second.clone()).into_option(), Some(first));
554 assert_eq!(slot.into_option(), Some(second));
555 }
556
557 #[test]
560 fn frame_vec_roundtrip_and_bounds() {
561 let frames = vec![
562 frame(1, 1, vec![0, 0, 0, 255]),
563 frame(2, 1, vec![1; 8]),
564 frame(0, 0, Vec::new()),
565 ];
566
567 let v = VideoFrameVec::from_vec(frames.clone());
568
569 assert_eq!(v.len(), 3);
570 assert!(!v.is_empty());
571 assert_eq!(v.as_ref(), frames.as_slice());
572 assert_eq!(v.get(0), Some(&frames[0]));
573 assert_eq!(v.get(2), Some(&frames[2]));
574 assert_eq!(v.get(3), None, "out of bounds must be None");
575 assert_eq!(v.get(usize::MAX), None, "usize::MAX index must be None");
576 assert!(v.iter().eq(frames.iter()), "iteration order preserved");
577
578 assert_eq!(v.c_get(1).into_option(), Some(frames[1].clone()));
580 assert!(v.c_get(3).is_none());
581
582 let cloned = v.clone();
584 assert_eq!(cloned, v);
585 assert_ne!(cloned.as_ptr(), v.as_ptr());
586 }
587
588 #[test]
590 fn frame_vec_empty_is_well_formed() {
591 let v = VideoFrameVec::from_vec(Vec::new());
592
593 assert!(v.is_empty());
594 assert_eq!(v.len(), 0);
595 assert_eq!(v.as_ref(), &[] as &[VideoFrame]);
596 assert_eq!(v.get(0), None);
597 assert_eq!(v.clone(), v);
598 assert_eq!(VideoFrameVec::new(), v);
599 }
600
601 #[test]
605 fn frame_vec_from_const_slice_no_destructor() {
606 static FRAMES: [VideoFrame; 2] = [
607 VideoFrame::new(1, 1, U8Vec::from_const_slice(&[1, 2, 3, 4])),
608 VideoFrame::new(1, 1, U8Vec::from_const_slice(&[5, 6, 7, 8])),
609 ];
610
611 let v = VideoFrameVec::from_const_slice(&FRAMES);
612 assert_eq!(v.len(), 2);
613 assert_eq!(v.as_ref(), &FRAMES[..]);
614 assert_eq!(v.get(1).map(|f| f.bytes.as_ref()), Some(&[5, 6, 7, 8][..]));
615
616 let cloned = v.clone();
618 assert_eq!(cloned, v);
619 drop(v);
620 assert_eq!(cloned.len(), 2);
621 }
622}