feat: add skip forward and backward playback controls

This commit is contained in:
Rodrigo Rivero 2026-05-07 18:53:08 -04:00 committed by GitHub
parent 468b5f1459
commit f3d6469bed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 119 additions and 13 deletions

View file

@ -47,6 +47,12 @@ const GST_PLAY_FLAG_VIDEO: i32 = 1 << 0;
const GST_PLAY_FLAG_AUDIO: i32 = 1 << 1;
const GST_PLAY_FLAG_TEXT: i32 = 1 << 2;
const JUMP_BACKWARD_ICON: &[u8] =
include_bytes!("../res/icons/hicolor/16x16/apps/jump-backward-10-symbolic.svg");
const JUMP_FORWARD_ICON: &[u8] =
include_bytes!("../res/icons/hicolor/16x16/apps/jump-forward-10-symbolic.svg");
use std::error::Error;
fn language_name(code: &str) -> Option<String> {
@ -170,6 +176,8 @@ pub enum Action {
FolderOpenRecent(usize),
Fullscreen,
PlayPause,
PlayPrev,
PlayNext,
SeekBackward,
SeekForward,
NextFrame,
@ -193,6 +201,8 @@ impl MenuAction for Action {
Self::FolderOpenRecent(index) => Message::FolderOpenRecent(*index),
Self::Fullscreen => Message::Fullscreen,
Self::PlayPause => Message::PlayPause,
Self::PlayPrev => Message::PlayPrev,
Self::PlayNext => Message::PlayNext,
Self::SeekBackward => Message::SeekRelative(-10.0),
Self::SeekForward => Message::SeekRelative(10.0),
Self::NextFrame => Message::NextFrame,
@ -291,6 +301,7 @@ pub enum Message {
Seek(f64),
SeekRelative(f64),
SeekRelease,
PlayPrev,
PlayNext,
NextFrame,
PreviousFrame,
@ -1338,6 +1349,55 @@ impl Application for App {
}
}
Message::PlayPrev => {
if self.flags.config_state.player_state.repeat == RepeatState::Track {
return Task::none();
}
//first we get info about current media id & position in nav_bar
let curr_id = self.nav_model.active();
let curr_position = match self.nav_model.position(curr_id) {
Some(pos) => pos,
None => {
log::warn!("Failed to get position of current media: {:?}", curr_id);
return self.update(Message::EndOfStream);
}
};
if let Some(video) = &mut self.video_opt {
self.position = video.position().as_secs_f64();
if self.position > 3.0 {
video.seek(0, false).expect("seek");
} else {
if self.nav_model.activate_position(curr_position - 1) {
let curr_id = self.nav_model.active();
match self.nav_model.data::<ProjectNode>(curr_id) {
//The prev one is a media file, we play it.
Some(ProjectNode::File { .. }) => {
return self.on_nav_select(curr_id);
}
//The prev one is a folder. We expand it and recall PlayPrev.
Some(ProjectNode::Folder { .. }) => {
let _ = self.on_nav_select(curr_id);
return self.update(Message::PlayPrev);
}
//Unknown type. We do nothing.
_ => log::warn!(
"unknown type: {:?}",
self.nav_model.data::<ProjectNode>(curr_id)
),
}
} else {
// first file
video.seek(0, false).expect("seek");
}
}
}
}
Message::PlayNext => {
// TODO: known limitations:
// 1) if the user collapses the folder entry while a song is playing,
@ -1779,19 +1839,55 @@ impl Application for App {
);
}
if self.controls {
let mut row = widget::row::with_capacity(7)
let mut row = widget::row::with_capacity(9)
.align_y(Alignment::Center)
.spacing(space_xxs)
.push(
widget::button::icon(
if self.video_opt.as_ref().is_none_or(|video| video.paused()) {
widget::icon::from_name("media-playback-start-symbolic").size(16)
} else {
widget::icon::from_name("media-playback-pause-symbolic").size(16)
},
)
.on_press(Message::PlayPause),
if self
.video_opt
.as_ref()
.map_or(true, |video| video.has_video())
{
widget::button::icon(
widget::icon::from_svg_bytes(JUMP_BACKWARD_ICON).symbolic(true),
)
.on_press(Message::SeekRelative(-10.0))
} else {
widget::button::icon(
widget::icon::from_name("media-skip-backward-symbolic").size(16),
)
.on_press(Message::PlayPrev)
},
);
row = row.push(
widget::button::icon(
if self.video_opt.as_ref().map_or(true, |video| video.paused()) {
widget::icon::from_name("media-playback-start-symbolic").size(16)
} else {
widget::icon::from_name("media-playback-pause-symbolic").size(16)
},
)
.on_press(Message::PlayPause),
);
row = row.push(
if self
.video_opt
.as_ref()
.map_or(true, |video| video.has_video())
{
widget::button::icon(
widget::icon::from_svg_bytes(JUMP_FORWARD_ICON).symbolic(true),
)
.on_press(Message::SeekRelative(10.0))
} else {
widget::button::icon(
widget::icon::from_name("media-skip-forward-symbolic").size(16),
)
.on_press(Message::PlayNext)
},
);
row = row.push(widget::tooltip(
widget::button::icon(
widget::icon::from_name(match self.flags.config_state.player_state.repeat {

View file

@ -150,12 +150,12 @@ impl RootInterface for Player {
impl PlayerInterface for Player {
async fn next(&self) -> fdo::Result<()> {
log::info!("Next");
Ok(())
self.message(Message::PlayNext).await
}
async fn previous(&self) -> fdo::Result<()> {
log::info!("Previous");
Ok(())
self.message(Message::PlayPrev).await
}
async fn pause(&self) -> fdo::Result<()> {
@ -271,12 +271,12 @@ impl PlayerInterface for Player {
async fn can_go_next(&self) -> fdo::Result<bool> {
log::info!("CanGoNext");
Ok(false)
Ok(true)
}
async fn can_go_previous(&self) -> fdo::Result<bool> {
log::info!("CanGoPrevious");
Ok(false)
Ok(true)
}
async fn can_play(&self) -> fdo::Result<bool> {