From 9b768b7d56f4e575e7a5f99f5dfdc02dadffe31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Cerveau?= Date: Mon, 31 Jan 2022 15:53:47 +0100 Subject: [PATCH] app: display position/duration and seek scale The user can now see the position and duration of the playback The slider can now seek to the given position. --- TODO.md | 5 ++-- src/app.rs | 57 +++++++++++++++++++++++++++++++++++++++++---- src/gps.ui | 25 +++++++++++++------- src/gps/pipeline.rs | 40 +++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index 9398c2b..13120f9 100644 --- a/TODO.md +++ b/TODO.md @@ -61,8 +61,9 @@ - [ ] Render a media file - [ ] Offer compatible element to a pad (autorender) - [ ] Display tags/meta/message detected -- [ ] Seek to position -- [ ] Use one listbox with name, favorites and rank (sort list) +- [x] Display position and duration +- [x] Seek to position +- [x] One listbox with elements and one listbox with favorites in the app dashboard - [x] See the link creation with a dashed line ### CI/Infra diff --git a/src/app.rs b/src/app.rs index 76ccd87..3226a0f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -52,7 +52,7 @@ pub struct GPSAppInner { pub builder: Builder, pub pipeline: RefCell, pub plugin_list_initialized: OnceCell, - pub menu_signal_handlers: RefCell>, + pub signal_handlers: RefCell>, } #[derive(Debug)] @@ -135,7 +135,7 @@ impl GPSApp { builder, pipeline: RefCell::new(pipeline), plugin_list_initialized: OnceCell::new(), - menu_signal_handlers: RefCell::new(HashMap::new()), + signal_handlers: RefCell::new(HashMap::new()), })); let app_weak = app.downgrade(); app.pipeline.borrow().set_app(app_weak); @@ -161,7 +161,52 @@ impl GPSApp { app.build_ui(&application); })); + let app_weak = app.downgrade(); + let slider: gtk::Scale = app + .builder + .object("scale-position") + .expect("Couldn't get status_bar"); + let slider_update_signal_id = slider.connect_value_changed(move |slider| { + let app = upgrade_weak!(app_weak); + let pipeline = app.pipeline.borrow(); + let value = slider.value() as u64; + GPS_TRACE!("Seeking to {} s", value); + if pipeline.set_position(value).is_err() { + GPS_ERROR!("Seeking to {} failed", value); + } + }); + let app_weak = app.downgrade(); + let timeout_id = + glib::timeout_add_local(std::time::Duration::from_millis(500), move || { + let app = upgrade_weak!(app_weak, glib::Continue(false)); + let pipeline = app.pipeline.borrow(); + + let label: gtk::Label = app + .builder + .object("label-position") + .expect("Couldn't get status_bar"); + let slider: gtk::Scale = app + .builder + .object("scale-position") + .expect("Couldn't get status_bar"); + let position = pipeline.position(); + let duration = pipeline.duration(); + slider.set_range(0.0, duration as f64 / 1000_f64); + slider.block_signal(&slider_update_signal_id); + slider.set_value(position as f64 / 1000_f64); + slider.unblock_signal(&slider_update_signal_id); + + // Query the current playing position from the underlying pipeline. + let position_desc = pipeline.position_description(); + // Display the playing position in the gui. + label.set_text(&position_desc); + // Tell the callback to continue calling this closure. + glib::Continue(true) + }); + + let timeout_id = RefCell::new(Some(timeout_id)); let app_container = RefCell::new(Some(app)); + application.connect_shutdown(move |_| { let app = app_container .borrow_mut() @@ -202,6 +247,9 @@ impl GPSApp { .object("app_pop_menu") .expect("Couldn't get app_pop_menu"); pop_menu.unparent(); + if let Some(timeout_id) = timeout_id.borrow_mut().take() { + timeout_id.remove(); + } app.drop(); }); @@ -283,8 +331,7 @@ impl GPSApp { fn disconnect_app_menu_action(&self, action_name: &str) { let action = self.app_menu_action(action_name); - if let Some(signal_handler_id) = self.menu_signal_handlers.borrow_mut().remove(action_name) - { + if let Some(signal_handler_id) = self.signal_handlers.borrow_mut().remove(action_name) { action.disconnect(signal_handler_id); } } @@ -299,7 +346,7 @@ impl GPSApp { let action = self.app_menu_action(action_name); self.disconnect_app_menu_action(action_name); let signal_handler_id = action.connect_activate(f); - self.menu_signal_handlers + self.signal_handlers .borrow_mut() .insert(String::from(action_name), signal_handler_id); } diff --git a/src/gps.ui b/src/gps.ui index 902dfa3..b92b7d4 100644 --- a/src/gps.ui +++ b/src/gps.ui @@ -145,7 +145,7 @@ vertical 1 - + 0 @@ -177,18 +177,25 @@ edit-clear + + + xx:xx:xx + 1 + + + + + + + True + True + scale_adjustment + 1 - - - True - True - scale_adjustment - 1 - - + diff --git a/src/gps/pipeline.rs b/src/gps/pipeline.rs index 9b6c526..e8a462e 100644 --- a/src/gps/pipeline.rs +++ b/src/gps/pipeline.rs @@ -180,6 +180,46 @@ impl Pipeline { self.current_state.get() } + pub fn set_position(&self, position: u64) -> anyhow::Result<()> { + if let Some(pipeline) = self.pipeline.borrow().to_owned() { + pipeline.seek_simple( + gst::SeekFlags::FLUSH | gst::SeekFlags::KEY_UNIT, + position * gst::ClockTime::SECOND, + )?; + } + Ok(()) + } + + pub fn position(&self) -> u64 { + let mut position = gst::ClockTime::NONE; + if let Some(pipeline) = self.pipeline.borrow().to_owned() { + position = pipeline.query_position::(); + } + position.unwrap_or_default().mseconds() + } + + pub fn duration(&self) -> u64 { + let mut duration = gst::ClockTime::NONE; + if let Some(pipeline) = self.pipeline.borrow().to_owned() { + duration = pipeline.query_duration::(); + } + duration.unwrap_or_default().mseconds() + } + + pub fn position_description(&self) -> String { + let mut position = gst::ClockTime::NONE; + let mut duration = gst::ClockTime::NONE; + if let Some(pipeline) = self.pipeline.borrow().to_owned() { + position = pipeline.query_position::(); + duration = pipeline.query_duration::(); + } + format!( + "{:.0}/{:.0}", + position.unwrap_or_default().display(), + duration.unwrap_or_default().display(), + ) + } + fn state_to_app_state(state: PipelineState) -> AppState { match state { PipelineState::Playing => AppState::Playing,