use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum VideoTextureError {
#[error("Invalid video frame data: expected {expected} bytes, got {actual}")]
InvalidFrameData {
expected: usize,
actual: usize,
},
#[error("Video stream not found: {0}")]
StreamNotFound(String),
#[error("Failed to create video texture: {0}")]
TextureCreation(String),
#[error("Frame dimensions {width}x{height} would overflow")]
DimensionOverflow {
width: u32,
height: u32,
},
}
pub type VideoTextureResult<T> = Result<T, VideoTextureError>;
#[derive(Debug, Clone)]
pub struct VideoFrameData {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
impl VideoFrameData {
fn checked_frame_size(width: u32, height: u32) -> VideoTextureResult<usize> {
(width as usize)
.checked_mul(height as usize)
.and_then(|pixels| pixels.checked_mul(4))
.ok_or(VideoTextureError::DimensionOverflow { width, height })
}
pub fn new(width: u32, height: u32, data: Vec<u8>) -> VideoTextureResult<Self> {
let expected = Self::checked_frame_size(width, height)?;
if data.len() != expected {
return Err(VideoTextureError::InvalidFrameData {
expected,
actual: data.len(),
});
}
Ok(Self {
width,
height,
data,
})
}
pub fn placeholder(width: u32, height: u32) -> VideoTextureResult<Self> {
let byte_size = Self::checked_frame_size(width, height)?;
let pixel_count = byte_size / 4;
let mut data = Vec::with_capacity(byte_size);
for _ in 0..pixel_count {
data.extend_from_slice(&[32, 32, 32, 255]); }
Ok(Self {
width,
height,
data,
})
}
#[must_use]
pub fn is_valid(&self) -> bool {
self.width > 0 && self.height > 0 && !self.data.is_empty()
}
#[must_use]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Debug)]
pub struct VideoTextureEntry {
pub width: u32,
pub height: u32,
pub last_updated: u64,
}
impl VideoTextureEntry {
#[must_use]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
pub fn last_updated(&self) -> u64 {
self.last_updated
}
}
#[derive(Debug, Default)]
pub struct VideoTextureManager {
entries: HashMap<String, VideoTextureEntry>,
frame_counter: u64,
}
impl VideoTextureManager {
#[must_use]
pub fn new() -> Self {
Self {
entries: HashMap::new(),
frame_counter: 0,
}
}
pub fn update_texture(&mut self, stream_id: &str, frame: &VideoFrameData) {
self.frame_counter += 1;
self.entries.insert(
stream_id.to_string(),
VideoTextureEntry {
width: frame.width,
height: frame.height,
last_updated: self.frame_counter,
},
);
}
#[must_use]
pub fn get_texture(&self, stream_id: &str) -> Option<&VideoTextureEntry> {
self.entries.get(stream_id)
}
#[must_use]
pub fn has_texture(&self, stream_id: &str) -> bool {
self.entries.contains_key(stream_id)
}
pub fn remove_texture(&mut self, stream_id: &str) -> bool {
self.entries.remove(stream_id).is_some()
}
pub fn clear(&mut self) {
self.entries.clear();
}
#[must_use]
pub fn texture_count(&self) -> usize {
self.entries.len()
}
pub fn stream_ids(&self) -> impl Iterator<Item = &str> {
self.entries.keys().map(String::as_str)
}
#[must_use]
pub fn frame_counter(&self) -> u64 {
self.frame_counter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_video_frame_data_new() {
let data = vec![0u8; 4 * 4 * 4]; let frame = VideoFrameData::new(4, 4, data);
assert!(frame.is_ok());
let frame = frame.expect("Frame should be valid");
assert_eq!(frame.width, 4);
assert_eq!(frame.height, 4);
assert!(frame.is_valid());
}
#[test]
fn test_video_frame_data_invalid() {
let data = vec![0u8; 10]; let frame = VideoFrameData::new(4, 4, data);
assert!(frame.is_err());
match frame {
Err(VideoTextureError::InvalidFrameData { expected, actual }) => {
assert_eq!(expected, 64);
assert_eq!(actual, 10);
}
_ => panic!("Expected InvalidFrameData error"),
}
}
#[test]
fn test_video_frame_placeholder() {
let frame = VideoFrameData::placeholder(640, 480).expect("Should create placeholder");
assert_eq!(frame.width, 640);
assert_eq!(frame.height, 480);
assert_eq!(frame.data.len(), 640 * 480 * 4);
assert!(frame.is_valid());
assert_eq!(&frame.data[0..4], &[32, 32, 32, 255]);
}
#[test]
fn test_video_frame_dimension_overflow() {
let result = VideoFrameData::new(u32::MAX, u32::MAX, vec![]);
assert!(result.is_err());
match result {
Err(VideoTextureError::DimensionOverflow { width, height }) => {
assert_eq!(width, u32::MAX);
assert_eq!(height, u32::MAX);
}
_ => panic!("Expected DimensionOverflow error"),
}
let result = VideoFrameData::placeholder(u32::MAX, u32::MAX);
assert!(matches!(
result,
Err(VideoTextureError::DimensionOverflow { .. })
));
}
#[test]
fn test_video_texture_manager() {
let mut manager = VideoTextureManager::new();
assert_eq!(manager.texture_count(), 0);
assert!(!manager.has_texture("stream-1"));
let frame = VideoFrameData::placeholder(320, 240).expect("Should create placeholder");
manager.update_texture("stream-1", &frame);
assert_eq!(manager.texture_count(), 1);
assert!(manager.has_texture("stream-1"));
let entry = manager.get_texture("stream-1");
assert!(entry.is_some());
let entry = entry.expect("Entry should exist");
assert_eq!(entry.width, 320);
assert_eq!(entry.height, 240);
assert_eq!(entry.last_updated, 1);
let frame2 = VideoFrameData::placeholder(640, 480).expect("Should create placeholder");
manager.update_texture("stream-1", &frame2);
let entry = manager.get_texture("stream-1").expect("Entry should exist");
assert_eq!(entry.width, 640);
assert_eq!(entry.height, 480);
assert_eq!(entry.last_updated, 2);
manager.update_texture("stream-2", &frame);
assert_eq!(manager.texture_count(), 2);
assert!(manager.remove_texture("stream-1"));
assert_eq!(manager.texture_count(), 1);
assert!(!manager.has_texture("stream-1"));
assert!(manager.has_texture("stream-2"));
assert!(!manager.remove_texture("stream-1"));
manager.clear();
assert_eq!(manager.texture_count(), 0);
}
#[test]
fn test_video_texture_manager_stream_ids() {
let mut manager = VideoTextureManager::new();
let frame = VideoFrameData::placeholder(100, 100).expect("Should create placeholder");
manager.update_texture("a", &frame);
manager.update_texture("b", &frame);
manager.update_texture("c", &frame);
let mut ids: Vec<_> = manager.stream_ids().collect();
ids.sort_unstable();
assert_eq!(ids, vec!["a", "b", "c"]);
}
#[test]
fn test_video_frame_data_getters() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let frame = VideoFrameData::new(2, 2, data.clone()).expect("Should create frame");
assert_eq!(frame.width(), 2);
assert_eq!(frame.height(), 2);
assert_eq!(frame.data(), data.as_slice());
}
#[test]
fn test_video_texture_entry_getters() {
let mut manager = VideoTextureManager::new();
let frame = VideoFrameData::placeholder(800, 600).expect("Should create placeholder");
manager.update_texture("test-stream", &frame);
let entry = manager
.get_texture("test-stream")
.expect("Entry should exist");
assert_eq!(entry.width(), 800);
assert_eq!(entry.height(), 600);
assert_eq!(entry.last_updated(), 1);
}
#[test]
fn test_video_frame_zero_dimensions() {
let result = VideoFrameData::new(0, 100, vec![]);
assert!(result.is_ok());
let frame = result.expect("Should create frame");
assert!(!frame.is_valid());
let frame2 = VideoFrameData::new(100, 0, vec![]).expect("Should create frame");
assert!(!frame2.is_valid());
}
#[test]
fn test_video_texture_manager_frame_counter() {
let mut manager = VideoTextureManager::new();
assert_eq!(manager.frame_counter(), 0);
let frame = VideoFrameData::placeholder(100, 100).expect("Should create placeholder");
manager.update_texture("stream-1", &frame);
assert_eq!(manager.frame_counter(), 1);
manager.update_texture("stream-1", &frame);
assert_eq!(manager.frame_counter(), 2);
manager.update_texture("stream-2", &frame);
assert_eq!(manager.frame_counter(), 3);
}
#[test]
fn test_video_frame_missing_stream_behavior() {
let manager = VideoTextureManager::new();
assert!(manager.get_texture("nonexistent").is_none());
assert!(!manager.has_texture("nonexistent"));
}
}