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
15pub 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 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 pub fn width(self, width: impl Into<iced_core::Length>) -> Self {
56 Self {
57 width: width.into(),
58 ..self
59 }
60 }
61
62 pub fn height(self, height: impl Into<iced_core::Length>) -> Self {
64 Self {
65 height: height.into(),
66 ..self
67 }
68 }
69
70 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 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 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 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 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}