Skip to main content

gstreamer_iced/
video_player.rs

1use std::marker::PhantomData;
2use std::sync::atomic::Ordering;
3
4use crate::pipeline::VideoPrimitive;
5use crate::GVideo;
6use crate::StreamType;
7use gst::State;
8use gstreamer as gst;
9use gstreamer::glib;
10use gstreamer::prelude::*;
11use iced_core::{layout, Widget};
12use iced_wgpu::primitive::Renderer as PrimitiveRenderer;
13use std::time::Duration;
14
15/// VideoPlayer, whose backend is gstreamer
16pub struct VideoPlayer<'a, Message, Theme = iced_core::Theme, Renderer = iced_renderer::Renderer> {
17    video: &'a GVideo,
18    content_fit: iced_core::ContentFit,
19    width: iced_core::Length,
20    height: iced_core::Length,
21    on_end_of_stream: Option<Message>,
22    #[allow(clippy::type_complexity)]
23    on_error: Option<Box<dyn Fn(&glib::Error) -> Message + 'a>>,
24    on_duration_changed: Option<Box<dyn Fn(Duration) -> Message + 'a>>,
25    on_position_changed: Option<Box<dyn Fn(Duration) -> Message + 'a>>,
26    on_state_changed: Option<Box<dyn Fn(State) -> Message + 'a>>,
27    _theme: PhantomData<Theme>,
28    _message: PhantomData<Message>,
29    _renderer: PhantomData<Renderer>,
30}
31
32impl<'a, Message, Theme, Renderer> VideoPlayer<'a, Message, Theme, Renderer>
33where
34    Renderer: PrimitiveRenderer,
35{
36    /// create a new video player
37    pub fn new(video: &'a GVideo) -> Self {
38        Self {
39            video,
40            content_fit: iced_core::ContentFit::default(),
41            width: iced_core::Length::Shrink,
42            height: iced_core::Length::Shrink,
43            on_error: None,
44            on_end_of_stream: None,
45            on_duration_changed: None,
46            on_position_changed: None,
47            on_state_changed: None,
48            _theme: PhantomData,
49            _message: PhantomData,
50            _renderer: PhantomData,
51        }
52    }
53
54    /// set the width of the [VideoPlayer]
55    pub fn width(self, width: impl Into<iced_core::Length>) -> Self {
56        Self {
57            width: width.into(),
58            ..self
59        }
60    }
61
62    /// set the height of the [VideoPlayer]
63    pub fn height(self, height: impl Into<iced_core::Length>) -> Self {
64        Self {
65            height: height.into(),
66            ..self
67        }
68    }
69
70    ///  When gstreamer report an error
71    pub fn on_error<F>(self, on_error: F) -> Self
72    where
73        F: 'a + Fn(&glib::Error) -> Message,
74    {
75        VideoPlayer {
76            on_error: Some(Box::new(on_error)),
77            ..self
78        }
79    }
80
81    /// Message to send when the video reaches the end of stream (i.e., the video ends).
82    pub fn on_end_of_stream(self, on_end_of_stream: Message) -> Self {
83        VideoPlayer {
84            on_end_of_stream: Some(on_end_of_stream),
85            ..self
86        }
87    }
88
89    /// The duration changed during playing
90    pub fn on_duration_changed<F>(self, on_duration_changed: F) -> Self
91    where
92        F: 'a + Fn(Duration) -> Message,
93    {
94        VideoPlayer {
95            on_duration_changed: Some(Box::new(on_duration_changed)),
96            ..self
97        }
98    }
99
100    /// The play state changed during playing
101    pub fn on_state_changed<F>(self, on_state_changed: F) -> Self
102    where
103        F: 'a + Fn(State) -> Message,
104    {
105        VideoPlayer {
106            on_state_changed: Some(Box::new(on_state_changed)),
107            ..self
108        }
109    }
110
111    /// The position changed during playing
112    pub fn on_position_changed<F>(self, on_position_changed: F) -> Self
113    where
114        F: 'a + Fn(Duration) -> Message,
115    {
116        VideoPlayer {
117            on_position_changed: Some(Box::new(on_position_changed)),
118            ..self
119        }
120    }
121}
122impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
123    for VideoPlayer<'_, Message, Theme, Renderer>
124where
125    Message: Clone,
126    Renderer: PrimitiveRenderer,
127{
128    fn size(&self) -> iced_core::Size<iced_core::Length> {
129        iced_core::Size {
130            width: self.width,
131            height: self.height,
132        }
133    }
134    fn layout(
135        &mut self,
136        _tree: &mut iced_core::widget::Tree,
137        _renderer: &Renderer,
138        limits: &iced_core::layout::Limits,
139    ) -> iced_core::layout::Node {
140        let image_size = self
141            .video
142            .frame_data()
143            .map(|data| iced_core::Size {
144                width: data.width as f32,
145                height: data.height as f32,
146            })
147            .unwrap_or(limits.min());
148        let raw_size = limits.resolve(self.width, self.height, image_size);
149        let full_size = self.content_fit.fit(image_size, raw_size);
150        let final_size = iced_core::Size {
151            width: match self.width {
152                iced_core::Length::Shrink => f32::min(raw_size.width, full_size.width),
153                _ => raw_size.width,
154            },
155            height: match self.height {
156                iced_core::Length::Shrink => f32::min(raw_size.height, full_size.height),
157                _ => raw_size.height,
158            },
159        };
160
161        layout::Node::new(final_size)
162    }
163    fn draw(
164        &self,
165        _tree: &iced_core::widget::Tree,
166        renderer: &mut Renderer,
167        _theme: &Theme,
168        _style: &iced_core::renderer::Style,
169        layout: iced_core::Layout<'_>,
170        _cursor: iced_core::mouse::Cursor,
171        _viewport: &iced_core::Rectangle,
172    ) {
173        let Some(data) = self.video.frame_data() else {
174            return;
175        };
176        let (width, height) = data.size();
177        let image_size = iced_core::Size::new(width as f32, height as f32);
178        let bounds = layout.bounds();
179        let adjusted_fit = self.content_fit.fit(image_size, bounds.size());
180        let scale = iced_core::Vector::new(
181            adjusted_fit.width / image_size.width,
182            adjusted_fit.height / image_size.height,
183        );
184        let final_size = image_size * scale;
185
186        let position = match self.content_fit {
187            iced_core::ContentFit::None => iced_core::Point::new(
188                bounds.x + (image_size.width - adjusted_fit.width) / 2.0,
189                bounds.y + (image_size.height - adjusted_fit.height) / 2.0,
190            ),
191            _ => iced_core::Point::new(
192                bounds.center_x() - final_size.width / 2.0,
193                bounds.center_y() - final_size.height / 2.0,
194            ),
195        };
196
197        let drawing_bounds = iced_core::Rectangle::new(position, final_size);
198
199        let upload_frame = self
200            .video
201            .upload_frame()
202            .unwrap()
203            .swap(false, Ordering::SeqCst);
204
205        let render = |renderer: &mut Renderer| {
206            renderer.draw_primitive(
207                drawing_bounds,
208                VideoPrimitive::new(
209                    *self.video.id().unwrap(),
210                    self.video.alive().unwrap().clone(),
211                    self.video.frame().unwrap(),
212                    upload_frame,
213                ),
214            );
215        };
216
217        if adjusted_fit.width > bounds.width || adjusted_fit.height > bounds.height {
218            renderer.with_layer(bounds, render);
219        } else {
220            render(renderer);
221        }
222    }
223    fn update(
224        &mut self,
225        _tree: &mut iced_core::widget::Tree,
226        event: &iced_core::Event,
227        _layout: iced_core::Layout<'_>,
228        _cursor: iced_core::mouse::Cursor,
229        _renderer: &Renderer,
230        _clipboard: &mut dyn iced_core::Clipboard,
231        shell: &mut iced_core::Shell<'_, Message>,
232        _viewport: &iced_core::Rectangle,
233    ) {
234        let iced_core::Event::Window(iced_core::window::Event::RedrawRequested(_instant)) = event
235        else {
236            return;
237        };
238        if self.video.is_none() {
239            return;
240        }
241        let state_o = self.video.state().unwrap();
242        let mut state = state_o.write().unwrap();
243        if self.video.stream_type() == StreamType::UrlPlayer {
244            if state.get_duration_attempt && self.video.play_state() == gst::State::Playing {
245                loop {
246                    self.video
247                        .source()
248                        .unwrap()
249                        .state(gst::ClockTime::from_seconds(1))
250                        .0
251                        .unwrap();
252
253                    if let Some(time) = self
254                        .video
255                        .source()
256                        .unwrap()
257                        .query_duration::<gst::ClockTime>()
258                    {
259                        state.duration = std::time::Duration::from_nanos(time.nseconds());
260                        if let Some(on_duration_changed) = &self.on_duration_changed {
261                            shell.publish(on_duration_changed(state.duration));
262                        }
263                        break;
264                    }
265                }
266                state.get_duration_attempt = false;
267            }
268            if state.duration.as_nanos() != 0 {
269                loop {
270                    if let Some(time) = self
271                        .video
272                        .source()
273                        .unwrap()
274                        .query_position::<gst::ClockTime>()
275                    {
276                        state.position = std::time::Duration::from_nanos(time.nseconds());
277                        if let Some(on_position_changed) = &self.on_position_changed {
278                            shell.publish(on_position_changed(state.position));
279                        }
280                        break;
281                    }
282                    self.video
283                        .source()
284                        .unwrap()
285                        .state(gst::ClockTime::from_seconds(5))
286                        .0
287                        .unwrap();
288                }
289            }
290            state.volume = self.video.source().unwrap().property("volume");
291        }
292        if self.video.play_state() == gst::State::Playing {
293            shell.request_redraw();
294        }
295        while let Some(msg) = self.video.bus().unwrap().pop_filtered(&[
296            gst::MessageType::Error,
297            gst::MessageType::Eos,
298            gst::MessageType::StateChanged,
299        ]) {
300            match msg.view() {
301                gst::MessageView::Error(err) => {
302                    log::error!("bus returned an error: {err}");
303                    if let Some(ref on_error) = self.on_error {
304                        shell.publish(on_error(&err.error()))
305                    };
306                }
307                gst::MessageView::Eos(_eos) => {
308                    self.video
309                        .source()
310                        .unwrap()
311                        .set_state(gst::State::Null)
312                        .unwrap();
313                    if let Some(on_state_changed) = &self.on_state_changed {
314                        shell.publish(on_state_changed(gst::State::Null));
315                    }
316                    if let Some(on_end_of_stream) = self.on_end_of_stream.clone() {
317                        shell.publish(on_end_of_stream);
318                        self.video.alive().unwrap().swap(false, Ordering::SeqCst);
319                    }
320                }
321                gstreamer::MessageView::StateChanged(change) => {
322                    if let Some(on_state_changed) = &self.on_state_changed {
323                        shell.publish(on_state_changed(change.current()));
324                    }
325                }
326                _ => {}
327            }
328        }
329    }
330}
331
332impl<'a, Message, Theme, Renderer> From<VideoPlayer<'a, Message, Theme, Renderer>>
333    for iced_core::Element<'a, Message, Theme, Renderer>
334where
335    Message: 'a + Clone,
336    Theme: 'a,
337    Renderer: 'a + PrimitiveRenderer,
338{
339    fn from(video_player: VideoPlayer<'a, Message, Theme, Renderer>) -> Self {
340        Self::new(video_player)
341    }
342}