use super::MediaPacket;
use bytes::Bytes;
use parking_lot::RwLock;
use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ThumbnailConfig {
pub interval: Duration,
pub width: u32,
pub height: u32,
pub quality: u8,
pub max_thumbnails: usize,
}
impl Default for ThumbnailConfig {
fn default() -> Self {
Self {
interval: Duration::from_secs(10),
width: 160,
height: 90,
quality: 80,
max_thumbnails: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct Thumbnail {
pub timestamp: u64,
pub data: Bytes,
pub width: u32,
pub height: u32,
}
impl Thumbnail {
#[must_use]
pub fn new(timestamp: u64, data: Bytes, width: u32, height: u32) -> Self {
Self {
timestamp,
data,
width,
height,
}
}
#[must_use]
pub fn size(&self) -> usize {
self.data.len()
}
}
pub struct ThumbnailGenerator {
config: ThumbnailConfig,
thumbnails: RwLock<VecDeque<Arc<Thumbnail>>>,
last_generation_ts: RwLock<u64>,
}
impl ThumbnailGenerator {
#[must_use]
pub fn new(interval: Duration) -> Self {
Self {
config: ThumbnailConfig {
interval,
..Default::default()
},
thumbnails: RwLock::new(VecDeque::new()),
last_generation_ts: RwLock::new(0),
}
}
#[must_use]
pub fn with_config(config: ThumbnailConfig) -> Self {
Self {
config,
thumbnails: RwLock::new(VecDeque::new()),
last_generation_ts: RwLock::new(0),
}
}
pub fn generate_from_packet(&self, packet: &MediaPacket) {
let last_ts = *self.last_generation_ts.read();
let interval_ms = self.config.interval.as_millis() as u64;
if packet.timestamp.saturating_sub(last_ts) < interval_ms {
return;
}
let thumbnail_data = Self::extract_thumbnail(
&packet.data,
self.config.width,
self.config.height,
self.config.quality,
);
let thumbnail = Arc::new(Thumbnail::new(
packet.timestamp,
thumbnail_data,
self.config.width,
self.config.height,
));
{
let mut thumbnails = self.thumbnails.write();
thumbnails.push_back(thumbnail);
if thumbnails.len() > self.config.max_thumbnails {
thumbnails.pop_front();
}
}
*self.last_generation_ts.write() = packet.timestamp;
}
fn extract_thumbnail(data: &[u8], _width: u32, _height: u32, _quality: u8) -> Bytes {
Bytes::copy_from_slice(&data[..data.len().min(1024)])
}
#[must_use]
pub fn get_all(&self) -> Vec<Arc<Thumbnail>> {
let thumbnails = self.thumbnails.read();
thumbnails.iter().cloned().collect()
}
#[must_use]
pub fn get_range(&self, start_ts: u64, end_ts: u64) -> Vec<Arc<Thumbnail>> {
let thumbnails = self.thumbnails.read();
thumbnails
.iter()
.filter(|t| t.timestamp >= start_ts && t.timestamp <= end_ts)
.cloned()
.collect()
}
#[must_use]
pub fn get_closest(&self, timestamp: u64) -> Option<Arc<Thumbnail>> {
let thumbnails = self.thumbnails.read();
thumbnails
.iter()
.min_by_key(|t| {
if t.timestamp > timestamp {
t.timestamp - timestamp
} else {
timestamp - t.timestamp
}
})
.cloned()
}
#[must_use]
pub fn get_latest(&self) -> Option<Arc<Thumbnail>> {
let thumbnails = self.thumbnails.read();
thumbnails.back().cloned()
}
#[must_use]
pub fn count(&self) -> usize {
let thumbnails = self.thumbnails.read();
thumbnails.len()
}
pub fn clear(&self) {
let mut thumbnails = self.thumbnails.write();
thumbnails.clear();
*self.last_generation_ts.write() = 0;
}
#[must_use]
pub fn generate_strip(&self, count: usize) -> Option<ThumbnailStrip> {
let thumbnails = self.thumbnails.read();
if thumbnails.is_empty() {
return None;
}
let step = thumbnails.len() / count.min(thumbnails.len());
let selected: Vec<_> = thumbnails
.iter()
.step_by(step.max(1))
.take(count)
.cloned()
.collect();
Some(ThumbnailStrip {
thumbnails: selected,
width: self.config.width,
height: self.config.height,
})
}
}
#[derive(Debug, Clone)]
pub struct ThumbnailStrip {
pub thumbnails: Vec<Arc<Thumbnail>>,
pub width: u32,
pub height: u32,
}
impl ThumbnailStrip {
#[must_use]
pub fn generate_sprite(&self) -> Bytes {
Bytes::new()
}
#[must_use]
pub fn sprite_width(&self) -> u32 {
self.width * self.thumbnails.len() as u32
}
#[must_use]
pub const fn sprite_height(&self) -> u32 {
self.height
}
#[must_use]
pub fn thumbnail_coords(&self, index: usize) -> Option<(u32, u32)> {
if index < self.thumbnails.len() {
Some((self.width * index as u32, 0))
} else {
None
}
}
}