Add three-state repeat button
Add a new button to the controls that defines whether the media repeats at the end of the stream. The states are: - Disabled: Media does not repeat. This is existing behavior and the default state. - Always: Media always repeats. - Once: Media repeats once. Flag is reset when loading new media. Tested all three states with both audio and video. Added a `rustfmt.toml` to avoid reordering imports. They keep moving around in previous pull requests, best to just define what we want in the repository. Fixes: #39
This commit is contained in:
parent
7c9ec8b423
commit
de70009ed2
3 changed files with 60 additions and 3 deletions
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
reorder_imports = false
|
||||||
|
|
@ -40,10 +40,23 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
pub enum RepeatState {
|
||||||
|
Disabled,
|
||||||
|
Once,
|
||||||
|
Always,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct PlayerState {
|
||||||
|
pub repeat: RepeatState,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
pub struct ConfigState {
|
pub struct ConfigState {
|
||||||
pub recent_files: VecDeque<url::Url>,
|
pub recent_files: VecDeque<url::Url>,
|
||||||
pub recent_projects: VecDeque<PathBuf>,
|
pub recent_projects: VecDeque<PathBuf>,
|
||||||
|
pub player_state: PlayerState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ConfigState {
|
impl Default for ConfigState {
|
||||||
|
|
@ -51,6 +64,9 @@ impl Default for ConfigState {
|
||||||
Self {
|
Self {
|
||||||
recent_files: VecDeque::new(),
|
recent_files: VecDeque::new(),
|
||||||
recent_projects: VecDeque::new(),
|
recent_projects: VecDeque::new(),
|
||||||
|
player_state: PlayerState {
|
||||||
|
repeat: RepeatState::Disabled,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
src/main.rs
46
src/main.rs
|
|
@ -34,7 +34,7 @@ use std::{
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{CONFIG_VERSION, Config, ConfigState},
|
config::{CONFIG_VERSION, Config, ConfigState, RepeatState},
|
||||||
key_bind::{KeyBind, key_binds},
|
key_bind::{KeyBind, key_binds},
|
||||||
project::ProjectNode,
|
project::ProjectNode,
|
||||||
};
|
};
|
||||||
|
|
@ -280,6 +280,7 @@ pub enum Message {
|
||||||
Pause,
|
Pause,
|
||||||
Play,
|
Play,
|
||||||
PlayPause,
|
PlayPause,
|
||||||
|
RepeatToggled(RepeatState),
|
||||||
Scrolled(ScrollDelta),
|
Scrolled(ScrollDelta),
|
||||||
Seek(f64),
|
Seek(f64),
|
||||||
SeekRelative(f64),
|
SeekRelative(f64),
|
||||||
|
|
@ -320,6 +321,7 @@ pub struct App {
|
||||||
current_text: Option<i32>,
|
current_text: Option<i32>,
|
||||||
#[cfg(feature = "xdg-portal")]
|
#[cfg(feature = "xdg-portal")]
|
||||||
inhibit: tokio::sync::watch::Sender<bool>,
|
inhibit: tokio::sync::watch::Sender<bool>,
|
||||||
|
has_media_repeated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -344,6 +346,7 @@ impl App {
|
||||||
self.current_audio = -1;
|
self.current_audio = -1;
|
||||||
self.text_codes.clear();
|
self.text_codes.clear();
|
||||||
self.current_text = None;
|
self.current_text = None;
|
||||||
|
self.has_media_repeated = false;
|
||||||
self.update_mpris_meta();
|
self.update_mpris_meta();
|
||||||
self.update_nav_bar_active();
|
self.update_nav_bar_active();
|
||||||
self.allow_idle();
|
self.allow_idle();
|
||||||
|
|
@ -375,6 +378,7 @@ impl App {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.duration = video.duration().as_secs_f64();
|
self.duration = video.duration().as_secs_f64();
|
||||||
|
self.has_media_repeated = false;
|
||||||
let pipeline = video.pipeline();
|
let pipeline = video.pipeline();
|
||||||
self.video_opt = Some(video);
|
self.video_opt = Some(video);
|
||||||
|
|
||||||
|
|
@ -875,6 +879,7 @@ impl Application for App {
|
||||||
current_text: None,
|
current_text: None,
|
||||||
#[cfg(feature = "xdg-portal")]
|
#[cfg(feature = "xdg-portal")]
|
||||||
inhibit,
|
inhibit,
|
||||||
|
has_media_repeated: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Do not show nav bar by default. Will be opened by open_project if needed
|
// Do not show nav bar by default. Will be opened by open_project if needed
|
||||||
|
|
@ -1222,6 +1227,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::RepeatToggled(state) => {
|
||||||
|
self.flags.config_state.player_state.repeat = state;
|
||||||
|
self.update_controls(true);
|
||||||
|
self.save_config_state();
|
||||||
|
}
|
||||||
Message::Scrolled(delta) => {
|
Message::Scrolled(delta) => {
|
||||||
let nav_bar_toggled = self.core.nav_bar_active();
|
let nav_bar_toggled = self.core.nav_bar_active();
|
||||||
if let Some(video) = &mut self.video_opt {
|
if let Some(video) = &mut self.video_opt {
|
||||||
|
|
@ -1304,7 +1314,20 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::EndOfStream => {
|
Message::EndOfStream => {
|
||||||
println!("end of stream");
|
println!(
|
||||||
|
"end of stream, repeat={:?}, has_media_repeated={:?}",
|
||||||
|
self.flags.config_state.player_state.repeat, self.has_media_repeated
|
||||||
|
);
|
||||||
|
|
||||||
|
match self.flags.config_state.player_state.repeat {
|
||||||
|
RepeatState::Always | RepeatState::Once if !self.has_media_repeated => {
|
||||||
|
if let Some(video) = &mut self.video_opt {
|
||||||
|
self.has_media_repeated = true;
|
||||||
|
video.restart_stream().expect("restart_stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Message::MissingPlugin(element) => {
|
Message::MissingPlugin(element) => {
|
||||||
if let Some(video) = &mut self.video_opt {
|
if let Some(video) = &mut self.video_opt {
|
||||||
|
|
@ -1620,7 +1643,7 @@ impl Application for App {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.controls {
|
if self.controls {
|
||||||
let mut row = widget::row::with_capacity(7)
|
let mut row = widget::row::with_capacity(8)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(space_xxs)
|
.spacing(space_xxs)
|
||||||
.push(
|
.push(
|
||||||
|
|
@ -1633,6 +1656,23 @@ impl Application for App {
|
||||||
)
|
)
|
||||||
.on_press(Message::PlayPause),
|
.on_press(Message::PlayPause),
|
||||||
);
|
);
|
||||||
|
row = row.push(
|
||||||
|
widget::button::icon(
|
||||||
|
widget::icon::from_name(match self.flags.config_state.player_state.repeat {
|
||||||
|
RepeatState::Disabled => "media-playlist-no-repeat-symbolic",
|
||||||
|
RepeatState::Always => "media-playlist-repeat-symbolic",
|
||||||
|
RepeatState::Once => "media-playlist-repeat-song-symbolic",
|
||||||
|
})
|
||||||
|
.size(16),
|
||||||
|
)
|
||||||
|
.on_press(Message::RepeatToggled(
|
||||||
|
match self.flags.config_state.player_state.repeat {
|
||||||
|
RepeatState::Disabled => RepeatState::Always,
|
||||||
|
RepeatState::Always => RepeatState::Once,
|
||||||
|
RepeatState::Once => RepeatState::Disabled,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
);
|
||||||
if self.core.is_condensed() {
|
if self.core.is_condensed() {
|
||||||
row = row.push(widget::horizontal_space(Length::Fill));
|
row = row.push(widget::horizontal_space(Length::Fill));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue