mxl_player_components/
player.rs

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", &gtk_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                    // Do not change from Error state to Stopped, because we want to stay in the Error state until the user takes action:
272                    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                    // Force state change from Error to Stopped, because the player implicitly was stopped by the previous error:
338                    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                    // .set_audio_track_id() <-- !!! TODO(mw): must be used!
369                    .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}