From 8b21bb52259cd47d28c523cc2b190b369b652220 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 27 Sep 2024 13:31:11 +0200 Subject: [PATCH 1/5] Refactor handling of `VideoFrameTexture` --- crates/viewer/re_data_ui/src/blob.rs | 39 +++++---- .../src/visualizers/videos.rs | 86 ++++++++++--------- 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/crates/viewer/re_data_ui/src/blob.rs b/crates/viewer/re_data_ui/src/blob.rs index 41e93fe068a3..bf21dafc478a 100644 --- a/crates/viewer/re_data_ui/src/blob.rs +++ b/crates/viewer/re_data_ui/src/blob.rs @@ -240,28 +240,29 @@ fn show_video_blob_info( egui::Id::new("video_miniplayer").value(), ); - if let Some(texture) = - match video.frame_at(render_ctx, decode_stream_id, timestamp_in_seconds) { - Ok(VideoFrameTexture::Ready(texture)) => Some(texture), + match video.frame_at(render_ctx, decode_stream_id, timestamp_in_seconds) { + Ok(frame) => { + let texture = match frame { + VideoFrameTexture::Ready(texture) => texture, - Ok(VideoFrameTexture::Pending(texture)) => { - ui.ctx().request_repaint(); - Some(texture) - } + VideoFrameTexture::Pending(placeholder) => { + ui.ctx().request_repaint(); + placeholder + } + }; + + crate::image::texture_preview_ui( + render_ctx, + ui, + ui_layout, + "video_preview", + re_renderer::renderer::ColormappedTexture::from_unorm_rgba(texture), + ); + } - Err(err) => { - ui.error_label_long(&err.to_string()); - None - } + Err(err) => { + ui.error_label_long(&err.to_string()); } - { - crate::image::texture_preview_ui( - render_ctx, - ui, - ui_layout, - "video_preview", - re_renderer::renderer::ColormappedTexture::from_unorm_rgba(texture), - ); } } }); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs b/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs index 867473d774d6..cf6caa3c7d56 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs @@ -191,49 +191,51 @@ impl VideoFrameReferenceVisualizer { Some(Ok(video)) => { video_resolution = glam::vec2(video.width() as _, video.height() as _); - if let Some(texture) = - match video.frame_at(render_ctx, decode_stream_id, video_timestamp.as_seconds()) - { - Ok(VideoFrameTexture::Ready(texture)) => Some(texture), - Ok(VideoFrameTexture::Pending(texture)) => { - ctx.viewer_ctx.egui_ctx.request_repaint(); - Some(texture) - } - Err(err) => { - self.show_video_error( - ctx, - spatial_ctx, - world_from_entity, - err.to_string(), - video_resolution, - entity_path, - ); - None - } + + match video.frame_at(render_ctx, decode_stream_id, video_timestamp.as_seconds()) { + Ok(frame) => { + let texture = match frame { + VideoFrameTexture::Ready(texture) => texture, + VideoFrameTexture::Pending(placeholder) => { + ctx.viewer_ctx.egui_ctx.request_repaint(); + placeholder + } + }; + + let textured_rect = TexturedRect { + top_left_corner_position: world_from_entity + .transform_point3(glam::Vec3::ZERO), + // Make sure to use the video instead of texture size here, + // since it may be a placeholder which doesn't have the full size yet. + extent_u: world_from_entity + .transform_vector3(glam::Vec3::X * video_resolution.x), + extent_v: world_from_entity + .transform_vector3(glam::Vec3::Y * video_resolution.y), + colormapped_texture: ColormappedTexture::from_unorm_rgba(texture), + options: RectangleOptions { + texture_filter_magnification: TextureFilterMag::Nearest, + texture_filter_minification: TextureFilterMin::Linear, + outline_mask: spatial_ctx.highlight.overall, + ..Default::default() + }, + }; + self.data.pickable_rects.push(PickableTexturedRect { + ent_path: entity_path.clone(), + textured_rect, + source_data: PickableRectSourceData::Video, + }); + } + + Err(err) => { + self.show_video_error( + ctx, + spatial_ctx, + world_from_entity, + err.to_string(), + video_resolution, + entity_path, + ); } - { - let textured_rect = TexturedRect { - top_left_corner_position: world_from_entity - .transform_point3(glam::Vec3::ZERO), - // Make sure to use the video instead of texture size here, - // since it may be a placeholder which doesn't have the full size yet. - extent_u: world_from_entity - .transform_vector3(glam::Vec3::X * video_resolution.x), - extent_v: world_from_entity - .transform_vector3(glam::Vec3::Y * video_resolution.y), - colormapped_texture: ColormappedTexture::from_unorm_rgba(texture), - options: RectangleOptions { - texture_filter_magnification: TextureFilterMag::Nearest, - texture_filter_minification: TextureFilterMin::Linear, - outline_mask: spatial_ctx.highlight.overall, - ..Default::default() - }, - }; - self.data.pickable_rects.push(PickableTexturedRect { - ent_path: entity_path.clone(), - textured_rect, - source_data: PickableRectSourceData::Video, - }); } } Some(Err(err)) => { From 0528a5a5a5d64af921b1cb08655111e100806325 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 27 Sep 2024 13:37:34 +0200 Subject: [PATCH 2/5] In the selection panel, show a spinner over the video when `Pending` --- crates/viewer/re_data_ui/src/blob.rs | 18 ++++++++++++++++-- crates/viewer/re_data_ui/src/image.rs | 7 ++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/viewer/re_data_ui/src/blob.rs b/crates/viewer/re_data_ui/src/blob.rs index bf21dafc478a..14a0136735dc 100644 --- a/crates/viewer/re_data_ui/src/blob.rs +++ b/crates/viewer/re_data_ui/src/blob.rs @@ -242,22 +242,36 @@ fn show_video_blob_info( match video.frame_at(render_ctx, decode_stream_id, timestamp_in_seconds) { Ok(frame) => { + let is_pending; let texture = match frame { - VideoFrameTexture::Ready(texture) => texture, + VideoFrameTexture::Ready(texture) => { + is_pending = false; + texture + } VideoFrameTexture::Pending(placeholder) => { + is_pending = true; ui.ctx().request_repaint(); placeholder } }; - crate::image::texture_preview_ui( + let response = crate::image::texture_preview_ui( render_ctx, ui, ui_layout, "video_preview", re_renderer::renderer::ColormappedTexture::from_unorm_rgba(texture), ); + + if is_pending { + // Shrink slightly: + let smaller_rect = egui::Rect::from_center_size( + response.rect.center(), + 0.75 * response.rect.size(), + ); + egui::Spinner::new().paint_at(ui, smaller_rect); + } } Err(err) => { diff --git a/crates/viewer/re_data_ui/src/image.rs b/crates/viewer/re_data_ui/src/image.rs index 2185291cf2c9..56dd270e1455 100644 --- a/crates/viewer/re_data_ui/src/image.rs +++ b/crates/viewer/re_data_ui/src/image.rs @@ -54,7 +54,7 @@ pub fn texture_preview_ui( ui_layout: UiLayout, debug_name: &str, texture: ColormappedTexture, -) { +) -> egui::Response { if ui_layout.is_single_line() { let preview_size = Vec2::splat(ui.available_height()); ui.allocate_ui_with_layout( @@ -73,7 +73,8 @@ pub fn texture_preview_ui( Err((response, err)) => response.on_hover_text(err.to_string()), } }, - ); + ) + .inner } else { let size_range = if ui_layout == UiLayout::Tooltip { egui::Rangef::new(64.0, 128.0) @@ -90,7 +91,7 @@ pub fn texture_preview_ui( re_log::warn_once!("Failed to show texture {debug_name}: {err}"); response }, - ); + ) } } From 07a1e713bf4bbcc1c89bdd1b35847e71d8c1d9fe Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 27 Sep 2024 14:32:44 +0200 Subject: [PATCH 3/5] Show spinner icon for pending videos in spatial space views --- crates/viewer/re_space_view_spatial/src/ui.rs | 17 ++++++++++ .../viewer/re_space_view_spatial/src/ui_2d.rs | 5 ++- .../viewer/re_space_view_spatial/src/ui_3d.rs | 9 +++++- .../utilities/spatial_view_visualizer.rs | 10 ++++-- .../src/visualizers/videos.rs | 32 ++++++++++++++----- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/ui.rs b/crates/viewer/re_space_view_spatial/src/ui.rs index 6ce07265f18c..5e107108f164 100644 --- a/crates/viewer/re_space_view_spatial/src/ui.rs +++ b/crates/viewer/re_space_view_spatial/src/ui.rs @@ -268,6 +268,23 @@ pub fn create_labels( (label_shapes, ui_rects) } +pub fn paint_loading_spinners( + ui: &egui::Ui, + ui_from_scene: egui::emath::RectTransform, + visualizers: &re_viewer_context::VisualizerCollection, +) { + for data in crate::visualizers::iter_spatial_visualizer_data(visualizers) { + for &rect_in_scene in &data.loading_rects { + let rect_in_ui = ui_from_scene.transform_rect(rect_in_scene); + + // Shrink slightly: + let rect = egui::Rect::from_center_size(rect_in_ui.center(), 0.75 * rect_in_ui.size()); + + egui::Spinner::new().paint_at(ui, rect); + } + } +} + pub fn outline_config(gui_ctx: &egui::Context) -> OutlineConfig { // Use the exact same colors we have in the ui! let hover_outline = gui_ctx.hover_stroke(); diff --git a/crates/viewer/re_space_view_spatial/src/ui_2d.rs b/crates/viewer/re_space_view_spatial/src/ui_2d.rs index 16fcff735d3c..c0999aa2e9bb 100644 --- a/crates/viewer/re_space_view_spatial/src/ui_2d.rs +++ b/crates/viewer/re_space_view_spatial/src/ui_2d.rs @@ -286,7 +286,10 @@ impl SpatialSpaceView2D { )); } - // Add egui driven labels on top of re_renderer content. + // Add egui-rendered spinners/loaders on top of re_renderer content: + crate::ui::paint_loading_spinners(ui, ui_from_scene, &system_output.view_systems); + + // Add egui-rendered labels on top of everything else: painter.extend(label_shapes); Ok(()) diff --git a/crates/viewer/re_space_view_spatial/src/ui_3d.rs b/crates/viewer/re_space_view_spatial/src/ui_3d.rs index cd2a2e7ede51..19ca39c75310 100644 --- a/crates/viewer/re_space_view_spatial/src/ui_3d.rs +++ b/crates/viewer/re_space_view_spatial/src/ui_3d.rs @@ -687,7 +687,14 @@ impl SpatialSpaceView3D { clear_color, )); - // Add egui driven labels on top of re_renderer content. + // Add egui-rendered spinners/loaders on top of re_renderer content: + crate::ui::paint_loading_spinners( + ui, + RectTransform::from_to(ui_rect, ui_rect), + &system_output.view_systems, + ); + + // Add egui-rendered labels on top of everything else: let painter = ui.painter().with_clip_rect(ui.max_rect()); painter.extend(label_shapes); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs index 040698533700..8111ef1d1022 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs @@ -7,6 +7,9 @@ use crate::{view_kind::SpatialSpaceViewKind, PickableTexturedRect}; /// /// Each spatial scene element is expected to fill an instance of this struct with its data. pub struct SpatialViewVisualizerData { + /// Loading icons/spinners shown using egui, in world/scene coordinates. + pub loading_rects: Vec, + /// Labels that should be shown using egui. pub ui_labels: Vec, @@ -23,9 +26,10 @@ pub struct SpatialViewVisualizerData { impl SpatialViewVisualizerData { pub fn new(preferred_view_kind: Option) -> Self { Self { - ui_labels: Vec::new(), - bounding_boxes: Vec::new(), - pickable_rects: Vec::new(), + loading_rects: Default::default(), + ui_labels: Default::default(), + bounding_boxes: Default::default(), + pickable_rects: Default::default(), preferred_view_kind, } } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs b/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs index cf6caa3c7d56..8fdf14b1711f 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/videos.rs @@ -194,23 +194,39 @@ impl VideoFrameReferenceVisualizer { match video.frame_at(render_ctx, decode_stream_id, video_timestamp.as_seconds()) { Ok(frame) => { + // Make sure to use the video instead of texture size here, + // since the texture may be a placeholder which doesn't have the full size yet. + let top_left_corner_position = + world_from_entity.transform_point3(glam::Vec3::ZERO); + let extent_u = + world_from_entity.transform_vector3(glam::Vec3::X * video_resolution.x); + let extent_v = + world_from_entity.transform_vector3(glam::Vec3::Y * video_resolution.y); + let texture = match frame { VideoFrameTexture::Ready(texture) => texture, VideoFrameTexture::Pending(placeholder) => { + // Show loading rectangle: + let min = top_left_corner_position; + let max = top_left_corner_position + extent_u + extent_v; + let center = 0.5 * (min + max); + let diameter = (max - min).truncate().abs().min_element(); + self.data.loading_rects.push(egui::Rect::from_center_size( + egui::pos2(center.x, center.y), + egui::Vec2::splat(diameter), + )); + + // Keep polling for the decoded result: ctx.viewer_ctx.egui_ctx.request_repaint(); + placeholder } }; let textured_rect = TexturedRect { - top_left_corner_position: world_from_entity - .transform_point3(glam::Vec3::ZERO), - // Make sure to use the video instead of texture size here, - // since it may be a placeholder which doesn't have the full size yet. - extent_u: world_from_entity - .transform_vector3(glam::Vec3::X * video_resolution.x), - extent_v: world_from_entity - .transform_vector3(glam::Vec3::Y * video_resolution.y), + top_left_corner_position, + extent_u, + extent_v, colormapped_texture: ColormappedTexture::from_unorm_rgba(texture), options: RectangleOptions { texture_filter_magnification: TextureFilterMag::Nearest, From 438811d369ba8816a79d4981394d3cb0c5b2f59f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 27 Sep 2024 14:40:00 +0200 Subject: [PATCH 4/5] Refactor: make it easier to iterate over specific visualizers --- crates/viewer/re_space_view_spatial/src/picking_ui.rs | 6 ++++-- .../re_space_view_spatial/src/scene_bounding_boxes.rs | 4 ++-- crates/viewer/re_space_view_spatial/src/ui.rs | 7 ++++--- .../re_space_view_spatial/src/visualizers/mod.rs | 7 ++++--- .../src/visualizers/utilities/mod.rs | 2 +- .../visualizers/utilities/spatial_view_visualizer.rs | 10 ---------- .../src/space_view/visualizer_system.rs | 11 +++++++++++ 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/picking_ui.rs b/crates/viewer/re_space_view_spatial/src/picking_ui.rs index 4d8695752b7d..8aa10dde8581 100644 --- a/crates/viewer/re_space_view_spatial/src/picking_ui.rs +++ b/crates/viewer/re_space_view_spatial/src/picking_ui.rs @@ -17,7 +17,7 @@ use crate::{ picking_ui_pixel::{textured_rect_hover_ui, PickedPixelInfo}, ui::SpatialSpaceViewState, view_kind::SpatialSpaceViewKind, - visualizers::{iter_spatial_visualizer_data, CamerasVisualizer, DepthImageVisualizer}, + visualizers::{CamerasVisualizer, DepthImageVisualizer, SpatialViewVisualizerData}, PickableRectSourceData, PickableTexturedRect, }; @@ -217,7 +217,9 @@ pub fn picking( fn iter_pickable_rects( visualizers: &VisualizerCollection, ) -> impl Iterator { - iter_spatial_visualizer_data(visualizers).flat_map(|data| data.pickable_rects.iter()) + visualizers + .iter_visualizer_data::() + .flat_map(|data| data.pickable_rects.iter()) } /// If available, finds pixel info for a picking hit. diff --git a/crates/viewer/re_space_view_spatial/src/scene_bounding_boxes.rs b/crates/viewer/re_space_view_spatial/src/scene_bounding_boxes.rs index 77af7138894f..986106e14f14 100644 --- a/crates/viewer/re_space_view_spatial/src/scene_bounding_boxes.rs +++ b/crates/viewer/re_space_view_spatial/src/scene_bounding_boxes.rs @@ -3,7 +3,7 @@ use nohash_hasher::IntMap; use re_log_types::EntityPathHash; use re_viewer_context::VisualizerCollection; -use crate::{view_kind::SpatialSpaceViewKind, visualizers::iter_spatial_visualizer_data}; +use crate::{view_kind::SpatialSpaceViewKind, visualizers::SpatialViewVisualizerData}; #[derive(Clone)] pub struct SceneBoundingBoxes { @@ -42,7 +42,7 @@ impl SceneBoundingBoxes { self.current = re_math::BoundingBox::NOTHING; self.per_entity.clear(); - for data in iter_spatial_visualizer_data(visualizers) { + for data in visualizers.iter_visualizer_data::() { // If we're in a 3D space, but the visualizer is distintivly 2D, don't count it towards the bounding box. // These visualizers show up when we're on a pinhole camera plane which itself is heuristically fed by the // bounding box, creating a feedback loop if we were to add it here. diff --git a/crates/viewer/re_space_view_spatial/src/ui.rs b/crates/viewer/re_space_view_spatial/src/ui.rs index 5e107108f164..1c34d4b76be9 100644 --- a/crates/viewer/re_space_view_spatial/src/ui.rs +++ b/crates/viewer/re_space_view_spatial/src/ui.rs @@ -19,7 +19,7 @@ use crate::{ picking::{PickableUiRect, PickingResult}, scene_bounding_boxes::SceneBoundingBoxes, view_kind::SpatialSpaceViewKind, - visualizers::{iter_spatial_visualizer_data, UiLabel, UiLabelTarget}, + visualizers::{SpatialViewVisualizerData, UiLabel, UiLabelTarget}, }; use super::{eye::Eye, ui_3d::View3DState}; @@ -84,7 +84,8 @@ impl SpatialSpaceViewState { .update(ui, &system_output.view_systems, space_kind); let view_systems = &system_output.view_systems; - self.num_non_segmentation_images_last_frame = iter_spatial_visualizer_data(view_systems) + self.num_non_segmentation_images_last_frame = view_systems + .iter_visualizer_data::() .flat_map(|data| { data.pickable_rects.iter().map(|pickable_rect| { if let PickableRectSourceData::Image { image, .. } = &pickable_rect.source_data @@ -273,7 +274,7 @@ pub fn paint_loading_spinners( ui_from_scene: egui::emath::RectTransform, visualizers: &re_viewer_context::VisualizerCollection, ) { - for data in crate::visualizers::iter_spatial_visualizer_data(visualizers) { + for data in visualizers.iter_visualizer_data::() { for &rect_in_scene in &data.loading_rects { let rect_in_ui = ui_from_scene.transform_rect(rect_in_scene); diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs index 59e4d25a7e40..93db22658852 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs @@ -24,8 +24,8 @@ pub use cameras::CamerasVisualizer; pub use depth_images::DepthImageVisualizer; pub use transform3d_arrows::{add_axis_arrows, AxisLengthDetector, Transform3DArrowsVisualizer}; pub use utilities::{ - entity_iterator, iter_spatial_visualizer_data, process_labels_3d, textured_rect_from_image, - SpatialViewVisualizerData, UiLabel, UiLabelTarget, + entity_iterator, process_labels_3d, textured_rect_from_image, SpatialViewVisualizerData, + UiLabel, UiLabelTarget, }; // --- @@ -129,7 +129,8 @@ pub fn visualizers_processing_draw_order() -> impl Iterator Vec { - iter_spatial_visualizer_data(visualizers) + visualizers + .iter_visualizer_data::() .flat_map(|data| data.ui_labels.iter().cloned()) .collect() } diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs index fcb409fa9019..127a706ccdad 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs @@ -9,5 +9,5 @@ pub use labels::{ UiLabel, UiLabelTarget, }; pub use proc_mesh_vis::{ProcMeshBatch, ProcMeshDrawableBuilder}; -pub use spatial_view_visualizer::{iter_spatial_visualizer_data, SpatialViewVisualizerData}; +pub use spatial_view_visualizer::SpatialViewVisualizerData; pub use textured_rect::textured_rect_from_image; diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs index 8111ef1d1022..96c97710b7a4 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/spatial_view_visualizer.rs @@ -62,13 +62,3 @@ impl SpatialViewVisualizerData { self } } - -pub fn iter_spatial_visualizer_data( - visualizers: &re_viewer_context::VisualizerCollection, -) -> impl Iterator { - visualizers.iter().filter_map(|visualizer| { - visualizer - .data() - .and_then(|data| data.downcast_ref::()) - }) -} diff --git a/crates/viewer/re_viewer_context/src/space_view/visualizer_system.rs b/crates/viewer/re_viewer_context/src/space_view/visualizer_system.rs index 29000a17d4fd..10eec441f940 100644 --- a/crates/viewer/re_viewer_context/src/space_view/visualizer_system.rs +++ b/crates/viewer/re_viewer_context/src/space_view/visualizer_system.rs @@ -174,4 +174,15 @@ impl VisualizerCollection { ) -> impl Iterator { self.systems.iter().map(|s| (*s.0, s.1.as_ref())) } + + /// Iterate over all visualizer data that can be downcast to the given type. + pub fn iter_visualizer_data( + &self, + ) -> impl Iterator { + self.iter().filter_map(|visualizer| { + visualizer + .data() + .and_then(|data| data.downcast_ref::()) + }) + } } From 6808fd81dc669b8a046ccc568bfe8bbe14d683b2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 27 Sep 2024 14:43:01 +0200 Subject: [PATCH 5/5] Bug fix: give the UI decoder a globally unique ID --- crates/viewer/re_data_ui/src/blob.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/re_data_ui/src/blob.rs b/crates/viewer/re_data_ui/src/blob.rs index 14a0136735dc..a6a4caf6d3e1 100644 --- a/crates/viewer/re_data_ui/src/blob.rs +++ b/crates/viewer/re_data_ui/src/blob.rs @@ -237,7 +237,7 @@ fn show_video_blob_info( }; let decode_stream_id = re_renderer::video::VideoDecodingStreamId( - egui::Id::new("video_miniplayer").value(), + ui.id().with("video_player").value(), ); match video.frame_at(render_ctx, decode_stream_id, timestamp_in_seconds) {