1use std::collections::HashMap;
11
12use thiserror::Error;
13
14#[derive(Debug, Error)]
16pub enum VideoTextureError {
17 #[error("Invalid video frame data: expected {expected} bytes, got {actual}")]
19 InvalidFrameData {
20 expected: usize,
22 actual: usize,
24 },
25
26 #[error("Video stream not found: {0}")]
28 StreamNotFound(String),
29
30 #[error("Failed to create video texture: {0}")]
32 TextureCreation(String),
33
34 #[error("Frame dimensions {width}x{height} would overflow")]
36 DimensionOverflow {
37 width: u32,
39 height: u32,
41 },
42}
43
44pub type VideoTextureResult<T> = Result<T, VideoTextureError>;
46
47#[derive(Debug, Clone)]
60pub struct VideoFrameData {
61 pub width: u32,
63 pub height: u32,
65 pub data: Vec<u8>,
67}
68
69impl VideoFrameData {
70 fn checked_frame_size(width: u32, height: u32) -> VideoTextureResult<usize> {
76 (width as usize)
77 .checked_mul(height as usize)
78 .and_then(|pixels| pixels.checked_mul(4))
79 .ok_or(VideoTextureError::DimensionOverflow { width, height })
80 }
81
82 pub fn new(width: u32, height: u32, data: Vec<u8>) -> VideoTextureResult<Self> {
90 let expected = Self::checked_frame_size(width, height)?;
91 if data.len() != expected {
92 return Err(VideoTextureError::InvalidFrameData {
93 expected,
94 actual: data.len(),
95 });
96 }
97
98 Ok(Self {
99 width,
100 height,
101 data,
102 })
103 }
104
105 pub fn placeholder(width: u32, height: u32) -> VideoTextureResult<Self> {
113 let byte_size = Self::checked_frame_size(width, height)?;
114 let pixel_count = byte_size / 4;
115 let mut data = Vec::with_capacity(byte_size);
116
117 for _ in 0..pixel_count {
119 data.extend_from_slice(&[32, 32, 32, 255]); }
121
122 Ok(Self {
123 width,
124 height,
125 data,
126 })
127 }
128
129 #[must_use]
131 pub fn is_valid(&self) -> bool {
132 self.width > 0 && self.height > 0 && !self.data.is_empty()
133 }
134
135 #[must_use]
137 pub fn width(&self) -> u32 {
138 self.width
139 }
140
141 #[must_use]
143 pub fn height(&self) -> u32 {
144 self.height
145 }
146
147 #[must_use]
149 pub fn data(&self) -> &[u8] {
150 &self.data
151 }
152}
153
154#[derive(Debug)]
159pub struct VideoTextureEntry {
160 pub width: u32,
162 pub height: u32,
164 pub last_updated: u64,
166}
167
168impl VideoTextureEntry {
169 #[must_use]
171 pub fn width(&self) -> u32 {
172 self.width
173 }
174
175 #[must_use]
177 pub fn height(&self) -> u32 {
178 self.height
179 }
180
181 #[must_use]
183 pub fn last_updated(&self) -> u64 {
184 self.last_updated
185 }
186}
187
188#[derive(Debug, Default)]
211pub struct VideoTextureManager {
212 entries: HashMap<String, VideoTextureEntry>,
214 frame_counter: u64,
216}
217
218impl VideoTextureManager {
219 #[must_use]
221 pub fn new() -> Self {
222 Self {
223 entries: HashMap::new(),
224 frame_counter: 0,
225 }
226 }
227
228 pub fn update_texture(&mut self, stream_id: &str, frame: &VideoFrameData) {
233 self.frame_counter += 1;
234
235 self.entries.insert(
236 stream_id.to_string(),
237 VideoTextureEntry {
238 width: frame.width,
239 height: frame.height,
240 last_updated: self.frame_counter,
241 },
242 );
243 }
244
245 #[must_use]
247 pub fn get_texture(&self, stream_id: &str) -> Option<&VideoTextureEntry> {
248 self.entries.get(stream_id)
249 }
250
251 #[must_use]
253 pub fn has_texture(&self, stream_id: &str) -> bool {
254 self.entries.contains_key(stream_id)
255 }
256
257 pub fn remove_texture(&mut self, stream_id: &str) -> bool {
261 self.entries.remove(stream_id).is_some()
262 }
263
264 pub fn clear(&mut self) {
266 self.entries.clear();
267 }
268
269 #[must_use]
271 pub fn texture_count(&self) -> usize {
272 self.entries.len()
273 }
274
275 pub fn stream_ids(&self) -> impl Iterator<Item = &str> {
277 self.entries.keys().map(String::as_str)
278 }
279
280 #[must_use]
282 pub fn frame_counter(&self) -> u64 {
283 self.frame_counter
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_video_frame_data_new() {
293 let data = vec![0u8; 4 * 4 * 4]; let frame = VideoFrameData::new(4, 4, data);
296 assert!(frame.is_ok());
297
298 let frame = frame.expect("Frame should be valid");
299 assert_eq!(frame.width, 4);
300 assert_eq!(frame.height, 4);
301 assert!(frame.is_valid());
302 }
303
304 #[test]
305 fn test_video_frame_data_invalid() {
306 let data = vec![0u8; 10]; let frame = VideoFrameData::new(4, 4, data);
309 assert!(frame.is_err());
310
311 match frame {
312 Err(VideoTextureError::InvalidFrameData { expected, actual }) => {
313 assert_eq!(expected, 64);
314 assert_eq!(actual, 10);
315 }
316 _ => panic!("Expected InvalidFrameData error"),
317 }
318 }
319
320 #[test]
321 fn test_video_frame_placeholder() {
322 let frame = VideoFrameData::placeholder(640, 480).expect("Should create placeholder");
323 assert_eq!(frame.width, 640);
324 assert_eq!(frame.height, 480);
325 assert_eq!(frame.data.len(), 640 * 480 * 4);
326 assert!(frame.is_valid());
327
328 assert_eq!(&frame.data[0..4], &[32, 32, 32, 255]);
330 }
331
332 #[test]
333 fn test_video_frame_dimension_overflow() {
334 let result = VideoFrameData::new(u32::MAX, u32::MAX, vec![]);
336 assert!(result.is_err());
337
338 match result {
339 Err(VideoTextureError::DimensionOverflow { width, height }) => {
340 assert_eq!(width, u32::MAX);
341 assert_eq!(height, u32::MAX);
342 }
343 _ => panic!("Expected DimensionOverflow error"),
344 }
345
346 let result = VideoFrameData::placeholder(u32::MAX, u32::MAX);
348 assert!(matches!(
349 result,
350 Err(VideoTextureError::DimensionOverflow { .. })
351 ));
352 }
353
354 #[test]
355 fn test_video_texture_manager() {
356 let mut manager = VideoTextureManager::new();
357
358 assert_eq!(manager.texture_count(), 0);
360 assert!(!manager.has_texture("stream-1"));
361
362 let frame = VideoFrameData::placeholder(320, 240).expect("Should create placeholder");
364 manager.update_texture("stream-1", &frame);
365
366 assert_eq!(manager.texture_count(), 1);
367 assert!(manager.has_texture("stream-1"));
368
369 let entry = manager.get_texture("stream-1");
371 assert!(entry.is_some());
372 let entry = entry.expect("Entry should exist");
373 assert_eq!(entry.width, 320);
374 assert_eq!(entry.height, 240);
375 assert_eq!(entry.last_updated, 1);
376
377 let frame2 = VideoFrameData::placeholder(640, 480).expect("Should create placeholder");
379 manager.update_texture("stream-1", &frame2);
380
381 let entry = manager.get_texture("stream-1").expect("Entry should exist");
382 assert_eq!(entry.width, 640);
383 assert_eq!(entry.height, 480);
384 assert_eq!(entry.last_updated, 2);
385
386 manager.update_texture("stream-2", &frame);
388 assert_eq!(manager.texture_count(), 2);
389
390 assert!(manager.remove_texture("stream-1"));
392 assert_eq!(manager.texture_count(), 1);
393 assert!(!manager.has_texture("stream-1"));
394 assert!(manager.has_texture("stream-2"));
395
396 assert!(!manager.remove_texture("stream-1"));
398
399 manager.clear();
401 assert_eq!(manager.texture_count(), 0);
402 }
403
404 #[test]
405 fn test_video_texture_manager_stream_ids() {
406 let mut manager = VideoTextureManager::new();
407
408 let frame = VideoFrameData::placeholder(100, 100).expect("Should create placeholder");
409 manager.update_texture("a", &frame);
410 manager.update_texture("b", &frame);
411 manager.update_texture("c", &frame);
412
413 let mut ids: Vec<_> = manager.stream_ids().collect();
414 ids.sort_unstable();
415 assert_eq!(ids, vec!["a", "b", "c"]);
416 }
417
418 #[test]
419 fn test_video_frame_data_getters() {
420 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
421 let frame = VideoFrameData::new(2, 2, data.clone()).expect("Should create frame");
422
423 assert_eq!(frame.width(), 2);
424 assert_eq!(frame.height(), 2);
425 assert_eq!(frame.data(), data.as_slice());
426 }
427
428 #[test]
429 fn test_video_texture_entry_getters() {
430 let mut manager = VideoTextureManager::new();
431 let frame = VideoFrameData::placeholder(800, 600).expect("Should create placeholder");
432 manager.update_texture("test-stream", &frame);
433
434 let entry = manager
435 .get_texture("test-stream")
436 .expect("Entry should exist");
437 assert_eq!(entry.width(), 800);
438 assert_eq!(entry.height(), 600);
439 assert_eq!(entry.last_updated(), 1);
440 }
441
442 #[test]
443 fn test_video_frame_zero_dimensions() {
444 let result = VideoFrameData::new(0, 100, vec![]);
446 assert!(result.is_ok()); let frame = result.expect("Should create frame");
449 assert!(!frame.is_valid()); let frame2 = VideoFrameData::new(100, 0, vec![]).expect("Should create frame");
453 assert!(!frame2.is_valid());
454 }
455
456 #[test]
457 fn test_video_texture_manager_frame_counter() {
458 let mut manager = VideoTextureManager::new();
459 assert_eq!(manager.frame_counter(), 0);
460
461 let frame = VideoFrameData::placeholder(100, 100).expect("Should create placeholder");
462
463 manager.update_texture("stream-1", &frame);
464 assert_eq!(manager.frame_counter(), 1);
465
466 manager.update_texture("stream-1", &frame);
467 assert_eq!(manager.frame_counter(), 2);
468
469 manager.update_texture("stream-2", &frame);
470 assert_eq!(manager.frame_counter(), 3);
471 }
472
473 #[test]
474 fn test_video_frame_missing_stream_behavior() {
475 let manager = VideoTextureManager::new();
476
477 assert!(manager.get_texture("nonexistent").is_none());
479 assert!(!manager.has_texture("nonexistent"));
480 }
481}