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.
This commit is contained in:
Stéphane Cerveau 2022-01-31 15:53:47 +01:00
parent 8f72b9ac79
commit 9b768b7d56
4 changed files with 111 additions and 16 deletions

View file

@ -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

View file

@ -52,7 +52,7 @@ pub struct GPSAppInner {
pub builder: Builder,
pub pipeline: RefCell<GPS::Pipeline>,
pub plugin_list_initialized: OnceCell<bool>,
pub menu_signal_handlers: RefCell<HashMap<String, SignalHandlerId>>,
pub signal_handlers: RefCell<HashMap<String, SignalHandlerId>>,
}
#[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);
}

View file

@ -145,7 +145,7 @@
<property name="orientation">vertical</property>
<property name="spacing">1</property>
<child>
<object class="GtkBox">
<object class="GtkPaned">
<property name="vexpand">0</property>
<child>
<object class="GtkBox">
@ -177,18 +177,25 @@
<property name="icon-name">edit-clear</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label-position">
<property name="label" translatable="yes">xx:xx:xx</property>
<property name="hexpand">1</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScale" id="scale-position">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="adjustment">scale_adjustment</property>
<property name="round-digits">1</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScale" id="scale-position">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="adjustment">scale_adjustment</property>
<property name="round-digits">1</property>
</object>
</child>
<!--Graph and DashBoard paned-->
<child>
<object class="GtkPaned" id="graph_dashboard-paned">

View file

@ -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::<gst::ClockTime>();
}
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::<gst::ClockTime>();
}
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::<gst::ClockTime>();
duration = pipeline.query_duration::<gst::ClockTime>();
}
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,