overhaul to iced widget w/ custom render pipeline

This commit is contained in:
jazzfool 2024-02-19 02:59:54 +11:00
parent 51794fc0b1
commit e347a9b324
11 changed files with 972 additions and 499 deletions

178
src/video_player.rs Normal file
View file

@ -0,0 +1,178 @@
use crate::{pipeline::VideoPrimitive, video::Video};
use gstreamer as gst;
use iced::{
advanced::{self, graphics::core::event::Status, layout, widget, Widget},
Element,
};
use iced_wgpu::primitive::pipeline::Renderer as PrimitiveRenderer;
use std::{marker::PhantomData, sync::atomic::Ordering};
use std::{sync::Arc, time::Duration};
/// Video player which displays the current frame of a [`Video`](crate::Video).
pub struct VideoPlayer<'a, Message, Theme = iced::Theme, Renderer = iced::Renderer>
where
Renderer: PrimitiveRenderer,
{
video: &'a Video,
on_end_of_stream: Option<Message>,
on_new_frame: Option<Message>,
_phantom: PhantomData<(Theme, Renderer)>,
}
impl<'a, Message, Theme, Renderer> VideoPlayer<'a, Message, Theme, Renderer>
where
Renderer: PrimitiveRenderer,
{
pub fn new(video: &'a Video) -> Self {
VideoPlayer {
video,
on_end_of_stream: None,
on_new_frame: None,
_phantom: Default::default(),
}
}
pub fn on_end_of_stream(self, on_end_of_stream: Message) -> Self {
VideoPlayer {
on_end_of_stream: Some(on_end_of_stream),
..self
}
}
pub fn on_new_frame(self, on_new_frame: Message) -> Self {
VideoPlayer {
on_new_frame: Some(on_new_frame),
..self
}
}
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for VideoPlayer<'a, Message, Theme, Renderer>
where
Message: Clone,
Renderer: PrimitiveRenderer,
{
fn size(&self) -> iced::Size<iced::Length> {
iced::Size {
width: iced::Length::Shrink,
height: iced::Length::Shrink,
}
}
fn layout(
&self,
_tree: &mut widget::Tree,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let (width, height) = self.video.size();
let size = limits.resolve(
iced::Length::Shrink,
iced::Length::Shrink,
iced::Size::new(width as _, height as _),
);
layout::Node::new(size)
}
fn draw(
&self,
_tree: &widget::Tree,
renderer: &mut Renderer,
_theme: &Theme,
_style: &advanced::renderer::Style,
layout: advanced::Layout<'_>,
_cursor: advanced::mouse::Cursor,
_viewport: &iced::Rectangle,
) {
let inner = self.video.0.borrow();
renderer.draw_pipeline_primitive(
layout.bounds(),
VideoPrimitive::new(
inner.id,
Arc::clone(&inner.frame),
(inner.width as _, inner.height as _),
inner.upload_frame.load(Ordering::SeqCst),
),
);
}
fn on_event(
&mut self,
_state: &mut widget::Tree,
event: iced::Event,
_layout: advanced::Layout<'_>,
_cursor: advanced::mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn advanced::Clipboard,
shell: &mut advanced::Shell<'_, Message>,
_viewport: &iced::Rectangle,
) -> Status {
let mut inner = self.video.0.borrow_mut();
if let iced::Event::Window(_, iced::window::Event::RedrawRequested(now)) = event {
if inner.restart_stream || (!inner.is_eos && !inner.paused) {
let mut restart_stream = false;
if inner.restart_stream {
restart_stream = true;
// Set flag to false to avoid potentially multiple seeks
inner.restart_stream = false;
}
let mut eos_pause = false;
for msg in inner.bus.iter() {
match msg.view() {
gst::MessageView::Error(err) => panic!("{:#?}", err),
gst::MessageView::Eos(_eos) => {
if let Some(on_end_of_stream) = self.on_end_of_stream.clone() {
shell.publish(on_end_of_stream);
}
if inner.looping {
restart_stream = true;
} else {
eos_pause = true;
}
}
_ => {}
}
}
// Don't run eos_pause if restart_stream is true; fixes "pausing" after restarting a stream
if restart_stream {
if let Err(err) = inner.restart_stream() {
eprintln!("cannot restart stream (can't seek): {:#?}", err);
}
} else if eos_pause {
inner.is_eos = true;
inner.set_paused(true);
}
let redraw_interval = 1.0 / inner.framerate;
let until_redraw =
redraw_interval - (now - inner.next_redraw).as_secs_f64() % redraw_interval;
inner.next_redraw = now + Duration::from_secs_f64(until_redraw);
shell.request_redraw(iced::window::RedrawRequest::At(inner.next_redraw));
if let Some(on_new_frame) = self.on_new_frame.clone() {
shell.publish(on_new_frame);
}
}
Status::Captured
} else {
Status::Ignored
}
}
}
impl<'a, Message, Theme, Renderer> From<VideoPlayer<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a + Clone,
Theme: 'a,
Renderer: 'a + PrimitiveRenderer,
{
fn from(video_player: VideoPlayer<'a, Message, Theme, Renderer>) -> Self {
Self::new(video_player)
}
}