mod chunk_decoder;
use std::collections::hash_map::Entry;
use ahash::HashMap;
use re_log::ResultExt as _;
use re_mutex::Mutex;
use re_video::player::{DecoderDelayState, VideoPlayerError, VideoPlayerStreamId};
use re_video::{DecodeSettings, VideoDataDescription};
use crate::RenderContext;
use crate::resource_managers::{GpuTexture2D, SourceImageDataFormat};
pub type VideoPlayer = re_video::player::VideoPlayer<VideoTexture>;
impl From<crate::resource_managers::ImageDataToTextureError> for VideoPlayerError {
fn from(err: crate::resource_managers::ImageDataToTextureError) -> Self {
Self::TextureUploadError(err.to_string())
}
}
pub struct FrameDecodingOutput {
pub output: Option<VideoFrameTexture>,
pub error: Option<VideoPlayerError>,
}
impl FrameDecodingOutput {
fn error(error: impl Into<VideoPlayerError>) -> Self {
Self {
output: None,
error: Some(error.into()),
}
}
}
#[derive(Clone)]
pub struct VideoTexture {
pub texture: Option<GpuTexture2D>,
pub source_pixel_format: SourceImageDataFormat,
}
impl Default for VideoTexture {
fn default() -> Self {
Self {
texture: None,
source_pixel_format: SourceImageDataFormat::WgpuCompatible(
wgpu::TextureFormat::Rgba8Unorm,
),
}
}
}
pub struct VideoFrameTexture {
pub texture: Option<GpuTexture2D>,
pub decoder_delay_state: DecoderDelayState,
pub show_loading_indicator: bool,
pub source_pixel_format: SourceImageDataFormat,
pub frame_info: Option<re_video::FrameInfo>,
}
struct PlayerEntry {
player: VideoPlayer,
used_last_frame: bool,
}
impl re_byte_size::SizeBytes for PlayerEntry {
fn heap_size_bytes(&self) -> u64 {
let Self {
player,
used_last_frame: _,
} = self;
player.heap_size_bytes()
}
}
pub struct Video {
debug_name: String,
video_description: re_video::VideoDataDescription,
players: Mutex<HashMap<VideoPlayerStreamId, PlayerEntry>>,
decode_settings: DecodeSettings,
}
impl re_byte_size::SizeBytes for Video {
fn heap_size_bytes(&self) -> u64 {
let Self {
debug_name,
video_description,
players,
decode_settings: _,
} = self;
debug_name.heap_size_bytes()
+ video_description.heap_size_bytes()
+ players.lock().heap_size_bytes()
}
}
impl Drop for Video {
fn drop(&mut self) {
re_log::trace!("Dropping Video {:?}", self.debug_name);
}
}
impl Video {
pub fn load(
debug_name: String,
video_description: VideoDataDescription,
decode_settings: DecodeSettings,
) -> Self {
let players = Mutex::new(HashMap::default());
Self {
debug_name,
video_description,
players,
decode_settings,
}
}
pub fn debug_name(&self) -> &str {
&self.debug_name
}
#[inline]
pub fn data_descr(&self) -> &re_video::VideoDataDescription {
&self.video_description
}
#[inline]
pub fn data_descr_mut(&mut self) -> &mut re_video::VideoDataDescription {
&mut self.video_description
}
pub fn reset_all_decoders(&self) {
let mut players = self.players.lock();
for player in players.values_mut() {
player
.player
.reset(&self.video_description)
.ok_or_log_error_once();
}
}
#[inline]
pub fn dimensions(&self) -> Option<[u16; 2]> {
self.video_description
.encoding_details
.as_ref()
.map(|details| details.coded_dimensions)
}
pub fn frame_at<'a>(
&self,
render_context: &RenderContext,
player_stream_id: VideoPlayerStreamId,
video_time: re_video::Time,
get_video_buffer: &dyn Fn(re_tuid::Tuid) -> &'a [u8],
) -> FrameDecodingOutput {
re_tracing::profile_function!();
let mut players = self.players.lock();
let decoder_entry = match players.entry(player_stream_id) {
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
Entry::Vacant(vacant_entry) => {
let new_player = match VideoPlayer::new(
&self.debug_name,
&self.video_description,
&self.decode_settings,
) {
Ok(player) => player,
Err(err) => return FrameDecodingOutput::error(err),
};
vacant_entry.insert(PlayerEntry {
player: new_player,
used_last_frame: true,
})
}
};
decoder_entry.used_last_frame = true;
let status = decoder_entry.player.frame_at(
video_time,
&self.video_description,
&mut |texture, frame| {
chunk_decoder::update_video_texture_with_frame(render_context, texture, frame)
},
get_video_buffer,
);
let output = decoder_entry.player.output();
FrameDecodingOutput {
output: Some(VideoFrameTexture {
texture: output.and_then(|o| o.texture.clone()),
decoder_delay_state: status
.as_ref()
.map(|s| s.decoder_delay_state)
.unwrap_or(DecoderDelayState::UpToDate),
show_loading_indicator: status.as_ref().is_ok_and(|s| s.show_loading_indicator),
source_pixel_format: output.map_or(
SourceImageDataFormat::WgpuCompatible(wgpu::TextureFormat::Rgba8Unorm),
|o| o.source_pixel_format,
),
frame_info: status.as_ref().ok().and_then(|s| s.frame_info.clone()),
}),
error: status.err(),
}
}
pub fn handle_sample_insertion(
&self,
change_start: re_video::SampleIndex,
change_end: re_video::SampleIndex,
size_delta: isize,
) {
let mut players = self.players.lock();
for entry in players.values_mut() {
let player = &mut entry.player;
let needs_reset = {
let indices = player
.last_enqueued()
.into_iter()
.chain(player.last_requested());
indices
.map(|idx| {
let keyframe_idx = self.video_description.sample_keyframe_idx(idx)?;
self.video_description
.gop_sample_range_for_keyframe(keyframe_idx)
})
.reduce(|a, b| {
a.zip(b)
.map(|(a, b)| a.start.min(b.start)..a.end.max(b.end))
})
.flatten()
.is_some_and(|gop| {
gop.start <= change_end && change_start < gop.end
})
};
if needs_reset {
player.reset(&self.video_description).ok_or_log_error_once();
} else {
player.shift_indices(change_end, size_delta);
}
}
}
pub fn begin_frame(&self) {
re_tracing::profile_function!();
let mut players = self.players.lock();
players.retain(|_id, entry| entry.used_last_frame);
for entry in players.values_mut() {
entry.used_last_frame = false;
}
}
}