1use anyhow::{Context, Result};
2use gst::{event::Step, format::Buffers, glib, prelude::*};
3use gst_play::PlayMessage;
4use log::*;
5use mxl_relm4_components::relm4::{self, Sender, gtk::gdk};
6use std::sync::{Arc, Mutex};
7
8use glib::clone;
9
10use crate::ui::player::messages::{PlaybackState, PlayerComponentCommand, Track};
11
12const GLSINKBIN_NAME: &str = "glsinkbin";
13
14#[derive(Debug, Default)]
15pub enum MaxLateness {
16 Unlimited,
17 #[default]
18 Default,
19 Custom(i64),
20}
21
22#[derive(Debug)]
23pub struct PlayerBuilder {
24 seek_accurate: bool,
25 compositor: Option<gst::Element>,
26 audio_offset: i64,
27 subtitle_offset: i64,
28 qos: bool,
29 max_lateness: MaxLateness,
30}
31
32impl Default for PlayerBuilder {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl PlayerBuilder {
39 pub fn new() -> Self {
40 Self {
41 seek_accurate: false,
42 compositor: None,
43 audio_offset: 0,
44 subtitle_offset: 0,
45 qos: true,
46 max_lateness: MaxLateness::Default,
47 }
48 }
49
50 pub fn seek_accurate(&mut self, seek_accurate: bool) -> &mut Self {
51 self.seek_accurate = seek_accurate;
52 self
53 }
54
55 pub fn compositor(&mut self, compositor: Option<gst::Element>) -> &mut Self {
56 self.compositor = compositor;
57 self
58 }
59
60 pub fn audio_offset(&mut self, offset: i64) -> &mut Self {
61 self.audio_offset = offset;
62 self
63 }
64
65 pub fn subtitle_offset(&mut self, offset: i64) -> &mut Self {
66 self.subtitle_offset = offset;
67 self
68 }
69
70 pub fn qos(&mut self, qos: bool) -> &mut Self {
71 self.qos = qos;
72 self
73 }
74
75 pub fn max_lateness(&mut self, max_lateness: MaxLateness) -> &mut Self {
76 self.max_lateness = max_lateness;
77 self
78 }
79
80 pub fn build(&self, sender: relm4::Sender<PlayerComponentCommand>) -> Result<Player> {
81 let gtk_sink = gst::ElementFactory::make("gtk4paintablesink").build()?;
82
83 let paintable = gtk_sink.property::<gdk::Paintable>("paintable");
84 paintable.set_property("force-aspect-ratio", true);
85 paintable.set_property("use-scaling-filter", true);
86
87 let video_sink = if paintable.property::<Option<gdk::GLContext>>("gl-context").is_some()
88 && gst::ElementFactory::find(GLSINKBIN_NAME).is_some()
89 {
90 debug!("Use GL rendering for playback view");
91 gst::ElementFactory::make(GLSINKBIN_NAME)
92 .property("sink", >k_sink)
93 .build()
94 .with_context(|| "Failed to create player with element to process GL textures")?
95 } else {
96 warn!("Use software rendering for playback view");
97 gtk_sink.clone()
98 };
99
100 let renderer = gst_play::PlayVideoOverlayVideoRenderer::with_sink(&video_sink);
101
102 let gst_play = gst_play::Play::new(Some(renderer.clone().upcast::<gst_play::PlayVideoRenderer>()));
103
104 let pipeline = gst_play.pipeline();
105 pipeline.set_property_from_str(
106 "flags",
107 "soft-colorbalance+deinterlace+buffering+soft-volume+text+audio+video+vis",
108 );
109 if let Some(compositor) = &self.compositor {
110 pipeline.set_property("video-stream-combiner", compositor);
111 }
112
113 let mut config = gst_play.config();
114 config.set_seek_accurate(self.seek_accurate);
115 config.set_position_update_interval(250);
116 gst_play
117 .set_config(config)
118 .with_context(|| "Failed to set player configuration")?;
119
120 let player_data = Arc::new(Mutex::new(PlayerData {
121 sender,
122 current_state: None,
123 }));
124
125 let _bus_watch = gst_play
126 .message_bus()
127 .add_watch_local(clone!(
128 #[weak]
129 gst_play,
130 #[weak]
131 player_data,
132 #[upgrade_or]
133 glib::ControlFlow::Break,
134 move |_, message| {
135 match PlayMessage::parse(message) {
136 Ok(PlayMessage::EndOfStream(_msg)) => {
137 if let Some(uri) = gst_play.uri() {
138 let player_data = player_data.lock().unwrap();
139 player_data.send(PlayerComponentCommand::EndOfStream(uri.into()));
140 }
141 }
142 Ok(PlayMessage::MediaInfoUpdated(msg)) => {
143 let mut player_data_guard = player_data.as_ref().lock();
144 let player_data = player_data_guard.as_mut().unwrap();
145 player_data.send(PlayerComponentCommand::MediaInfoUpdated(msg.media_info().to_owned()));
146 }
147 Ok(PlayMessage::DurationChanged(msg)) => {
148 let player_data = player_data.lock().unwrap();
149 if let Some(duration) = msg.duration() {
150 player_data.send(PlayerComponentCommand::DurationChanged(
151 duration.mseconds() as f64 / 1000_f64,
152 ));
153 }
154 }
155 Ok(PlayMessage::PositionUpdated(msg)) => {
156 let player_data = player_data.lock().unwrap();
157 if let Some(position) = msg.position() {
158 player_data.send(PlayerComponentCommand::PositionUpdated(
159 position.mseconds() as f64 / 1000_f64,
160 ));
161 }
162 }
163 Ok(PlayMessage::VideoDimensionsChanged(msg)) => {
164 let player_data = player_data.lock().unwrap();
165 player_data.send(PlayerComponentCommand::VideoDimensionsChanged(
166 msg.width() as i32,
167 msg.height() as i32,
168 ));
169 }
170 Ok(PlayMessage::StateChanged(msg)) => {
171 let state = match msg.state() {
172 gst_play::PlayState::Playing => Some(PlaybackState::Playing),
173 gst_play::PlayState::Paused => Some(PlaybackState::Paused),
174 gst_play::PlayState::Stopped => Some(PlaybackState::Stopped),
175 gst_play::PlayState::Buffering => Some(PlaybackState::Buffering),
176 _ => None,
177 };
178 if let Some(s) = state {
179 let mut player_data = player_data.lock().unwrap();
180 player_data.change_state(s);
181 }
182 }
183 Ok(PlayMessage::VolumeChanged(msg)) => {
184 let player_data = player_data.lock().unwrap();
185 player_data.send(PlayerComponentCommand::VolumeChanged(msg.volume()));
186 }
187 Ok(PlayMessage::Error(msg)) => {
188 let mut player_data = player_data.lock().unwrap();
189 player_data.change_state(PlaybackState::Error);
190 player_data.send(PlayerComponentCommand::Error(anyhow::Error::from(
191 msg.error().to_owned(),
192 )));
193 }
194 Ok(PlayMessage::SeekDone(_msg)) => {
195 let player_data = player_data.lock().unwrap();
196 player_data.send(PlayerComponentCommand::SeekDone);
197 }
198 Ok(PlayMessage::Warning(msg)) => {
199 let player_data = player_data.lock().unwrap();
200 player_data.send(PlayerComponentCommand::Warning(anyhow::Error::from(
201 msg.error().to_owned(),
202 )));
203 }
204 _ => (),
205 }
206
207 glib::ControlFlow::Continue
208 }
209 ))
210 .with_context(|| "Cannot add watcher to player bus")?;
211
212 gst_play.connect_audio_video_offset_notify(clone!(
213 #[weak]
214 player_data,
215 move |play| {
216 let player_data = player_data.lock().unwrap();
217 player_data.send(PlayerComponentCommand::AudioVideoOffsetChanged(
218 play.audio_video_offset(),
219 ));
220 }
221 ));
222
223 gst_play.connect_subtitle_video_offset_notify(clone!(
224 #[weak]
225 player_data,
226 move |play| {
227 let player_data = player_data.lock().unwrap();
228 player_data.send(PlayerComponentCommand::SubtitleVideoOffsetChanged(
229 play.subtitle_video_offset(),
230 ));
231 }
232 ));
233
234 let player = Player {
235 player: gst_play,
236 renderer,
237 gtk_sink,
238 _bus_watch,
239 data: player_data,
240 };
241
242 player.set_audio_video_offset(self.audio_offset);
243 player.set_subtitle_video_offset(self.subtitle_offset);
244 player.set_qos(self.qos);
245 player.set_max_lateness(&self.max_lateness);
246
247 Ok(player)
248 }
249}
250
251#[derive(Debug)]
252pub struct Player {
253 player: gst_play::Play,
254 renderer: gst_play::PlayVideoOverlayVideoRenderer,
255 gtk_sink: gst::Element,
256 _bus_watch: gst::bus::BusWatchGuard,
257 data: Arc<Mutex<PlayerData>>,
258}
259
260#[derive(Debug)]
261struct PlayerData {
262 sender: Sender<PlayerComponentCommand>,
263 current_state: Option<PlaybackState>,
264}
265
266impl PlayerData {
267 fn change_state(&mut self, new_state: PlaybackState) {
268 let target_state = if let Some(current_state) = self.current_state {
269 if current_state != new_state {
270 if current_state == PlaybackState::Error && new_state == PlaybackState::Stopped {
271 trace!(
273 "Ignore player state change from {:?} to {:?}",
274 PlaybackState::Error,
275 PlaybackState::Stopped,
276 );
277 None
278 } else {
279 Some(new_state)
280 }
281 } else {
282 None
283 }
284 } else {
285 Some(new_state)
286 };
287 if let Some(target_state) = target_state {
288 self.set_state(target_state);
289 }
290 }
291
292 fn set_state(&mut self, new_state: PlaybackState) {
293 let old_state = self.current_state;
294 self.current_state = Some(new_state);
295 trace!("player state changed from {old_state:?} to {new_state:?}");
296 self.send(PlayerComponentCommand::StateChanged(old_state, new_state));
297 }
298
299 fn send(&self, cmd: PlayerComponentCommand) {
300 self.sender.send(cmd).unwrap_or_default();
301 }
302}
303
304impl Player {
305 pub fn paintable(&self) -> gdk::Paintable {
306 self.gtk_sink.property::<gdk::Paintable>("paintable")
307 }
308
309 pub fn update_render_rectangle(&self, src_rect: &gst_video::VideoRectangle, new_rect: gst_video::VideoRectangle) {
310 let rect = gst_video::center_video_rectangle(src_rect, &new_rect, true);
311 self.renderer.set_render_rectangle(rect.x, rect.y, rect.w, rect.h);
312 self.renderer.expose();
313 }
314
315 pub fn set_uri(&self, uri: &str) {
316 debug!("player set uri {uri}");
317 self.player.set_uri(Some(uri));
318 }
319
320 pub fn play(&self) {
321 self.player.play();
322 }
323
324 pub fn pause(&self) {
325 self.player.pause();
326 }
327
328 pub fn stop(&self) {
329 let mut player_data = self.data.lock().unwrap();
330 if let Some(current_state) = player_data.current_state {
331 match current_state {
332 PlaybackState::Stopped => player_data.set_state(PlaybackState::Stopped),
333 PlaybackState::Playing => (),
334 PlaybackState::Paused => (),
335 PlaybackState::Buffering => (),
336 PlaybackState::Error => {
337 trace!(
339 "Explicitly change player state from {:?} to {:?}",
340 PlaybackState::Error,
341 PlaybackState::Stopped,
342 );
343 player_data.set_state(PlaybackState::Stopped)
344 }
345 }
346 }
347 drop(player_data);
348 self.player.stop();
349 }
350
351 pub fn seek(&self, to: &f64) {
352 let to = gst::ClockTime::from_mseconds((to * 1000_f64) as u64);
353 self.player.seek(to);
354 }
355
356 pub fn set_volume(&self, vol: f64) {
357 self.player.set_volume(vol);
358 }
359
360 pub fn set_audio_track(&self, track: Track) -> Result<()> {
361 match track {
362 Track::Enable => self.player.set_audio_track_enabled(true),
363 Track::Disable => self.player.set_audio_track_enabled(false),
364 Track::Stream(index) => {
365 self.player.set_audio_track_enabled(true);
366 self.player
367 .set_audio_track(index)
368 .with_context(|| "Cannot set audio stream")?
370 }
371 }
372 Ok(())
373 }
374
375 pub fn speed(&self) -> f64 {
376 self.player.rate()
377 }
378
379 pub fn set_speed(&self, speed: f64) {
380 self.player.set_rate(speed);
381 }
382
383 pub fn next_frame(&self) {
384 trace!("step to next frame");
385 self.player
386 .pipeline()
387 .send_event(Step::new(Buffers::from_u64(1), 1., true, false));
388 }
389
390 pub fn set_audio_video_offset(&self, offset: i64) {
391 self.player.set_audio_video_offset(offset);
392 }
393
394 pub fn set_subtitle_video_offset(&self, offset: i64) {
395 self.player.set_subtitle_video_offset(offset);
396 }
397
398 pub fn dump_pipeline(&self, label: &str) {
399 let element = self.player.pipeline();
400 if let Ok(pipeline) = element.downcast::<gst::Pipeline>() {
401 pipeline.debug_to_dot_file_with_ts(gst::DebugGraphDetails::all(), label);
402 }
403 }
404
405 pub fn set_qos(&self, qos: bool) {
406 debug!("Set qos to {qos}");
407 self.gtk_sink.set_property("qos", qos);
408 }
409
410 pub fn set_max_lateness(&self, max_lateness: &MaxLateness) {
411 let property_name = "max-lateness";
412 let default_value = 5000000_i64;
413 let value = match max_lateness {
414 MaxLateness::Unlimited => -1_i64,
415 MaxLateness::Default => {
416 if let Some(spec) = self.gtk_sink.find_property(property_name) {
417 spec.default_value().get().unwrap_or(default_value)
418 } else {
419 default_value
420 }
421 }
422 MaxLateness::Custom(custom) => *custom,
423 };
424 debug!("Set max-lateness to {value}");
425 self.gtk_sink.set_property(property_name, value);
426 }
427}