Rename repeat options to Disabled, Track, and Playlist
Change track and playlist to repeat current track indefinitely.
This commit is contained in:
parent
ff97fa2f62
commit
b559de8fc5
4 changed files with 33 additions and 52 deletions
|
|
@ -36,5 +36,5 @@ quit = Quit
|
||||||
# Controls
|
# Controls
|
||||||
|
|
||||||
repeat-disabled = Repeat disabled
|
repeat-disabled = Repeat disabled
|
||||||
repeat-always = Repeat always
|
repeat-playlist = Repeat playlist
|
||||||
repeat-once = Repeat once
|
repeat-track = Repeat track
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,12 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
pub enum RepeatState {
|
pub enum RepeatState {
|
||||||
|
#[default]
|
||||||
Disabled,
|
Disabled,
|
||||||
Once,
|
Track,
|
||||||
Always,
|
Playlist,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
|
|
||||||
55
src/main.rs
55
src/main.rs
|
|
@ -232,7 +232,7 @@ pub struct MprisState {
|
||||||
position_micros: i64,
|
position_micros: i64,
|
||||||
paused: bool,
|
paused: bool,
|
||||||
volume: f64,
|
volume: f64,
|
||||||
will_repeat: bool,
|
repeat_state: RepeatState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -322,7 +322,6 @@ 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 {
|
||||||
|
|
@ -347,7 +346,6 @@ 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();
|
||||||
|
|
@ -379,7 +377,6 @@ 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);
|
||||||
|
|
||||||
|
|
@ -743,15 +740,12 @@ impl App {
|
||||||
position_micros: (self.position * 1_000_000.0) as i64,
|
position_micros: (self.position * 1_000_000.0) as i64,
|
||||||
paused: true,
|
paused: true,
|
||||||
volume: 0.0,
|
volume: 0.0,
|
||||||
will_repeat: false,
|
repeat_state: RepeatState::Disabled,
|
||||||
};
|
};
|
||||||
if let Some(video) = &self.video_opt {
|
if let Some(video) = &self.video_opt {
|
||||||
new.paused = video.paused();
|
new.paused = video.paused();
|
||||||
new.volume = video.volume();
|
new.volume = video.volume();
|
||||||
|
new.repeat_state = self.flags.config_state.player_state.repeat;
|
||||||
let repeat_state = &self.flags.config_state.player_state.repeat;
|
|
||||||
new.will_repeat = *repeat_state == RepeatState::Always
|
|
||||||
|| (*repeat_state == RepeatState::Once && !self.has_media_repeated);
|
|
||||||
}
|
}
|
||||||
if new != *old {
|
if new != *old {
|
||||||
*old = new.clone();
|
*old = new.clone();
|
||||||
|
|
@ -885,7 +879,6 @@ 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
|
||||||
|
|
@ -1321,27 +1314,17 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
Message::EndOfStream => {
|
Message::EndOfStream => {
|
||||||
let repeat_state = &self.flags.config_state.player_state.repeat;
|
let repeat_state = &self.flags.config_state.player_state.repeat;
|
||||||
println!(
|
println!("end of stream, repeat={:?}", repeat_state);
|
||||||
"end of stream, repeat={:?}, has_media_repeated={:?}",
|
|
||||||
repeat_state, self.has_media_repeated
|
|
||||||
);
|
|
||||||
|
|
||||||
match repeat_state {
|
if matches!(repeat_state, RepeatState::Playlist | RepeatState::Track) {
|
||||||
RepeatState::Always | RepeatState::Once
|
if let Some(video) = &mut self.video_opt {
|
||||||
if (*repeat_state == RepeatState::Always || !self.has_media_repeated) =>
|
// Workaround: Explicitly seeking to the start before `restart_stream`.
|
||||||
{
|
// This prevents its internal `pause(false)` from triggering a second EndOfStream message
|
||||||
if let Some(video) = &mut self.video_opt {
|
// that breaks RepeatState::Once. This results in a double seek but avoids single repeat
|
||||||
self.has_media_repeated = true;
|
// not working at all. `restart_stream` is still required to set internal `is_eos` value.
|
||||||
|
video.seek(0, false).expect("seek");
|
||||||
// Workaround: Explicitly seeking to the start before `restart_stream`.
|
video.restart_stream().expect("restart_stream");
|
||||||
// This prevents its internal `pause(false)` from triggering a second EndOfStream message
|
|
||||||
// that breaks RepeatState::Once. This results in a double seek but avoids single repeat
|
|
||||||
// not working at all. `restart_stream` is still required to set internal `is_eos` value.
|
|
||||||
video.seek(0, false).expect("seek");
|
|
||||||
video.restart_stream().expect("restart_stream");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::MissingPlugin(element) => {
|
Message::MissingPlugin(element) => {
|
||||||
|
|
@ -1675,22 +1658,22 @@ impl Application for App {
|
||||||
widget::button::icon(
|
widget::button::icon(
|
||||||
widget::icon::from_name(match self.flags.config_state.player_state.repeat {
|
widget::icon::from_name(match self.flags.config_state.player_state.repeat {
|
||||||
RepeatState::Disabled => "media-playlist-no-repeat-symbolic",
|
RepeatState::Disabled => "media-playlist-no-repeat-symbolic",
|
||||||
RepeatState::Always => "media-playlist-repeat-symbolic",
|
RepeatState::Playlist => "media-playlist-repeat-symbolic",
|
||||||
RepeatState::Once => "media-playlist-repeat-song-symbolic",
|
RepeatState::Track => "media-playlist-repeat-song-symbolic",
|
||||||
})
|
})
|
||||||
.size(16),
|
.size(16),
|
||||||
)
|
)
|
||||||
.on_press(Message::RepeatToggled(
|
.on_press(Message::RepeatToggled(
|
||||||
match self.flags.config_state.player_state.repeat {
|
match self.flags.config_state.player_state.repeat {
|
||||||
RepeatState::Disabled => RepeatState::Always,
|
RepeatState::Disabled => RepeatState::Playlist,
|
||||||
RepeatState::Always => RepeatState::Once,
|
RepeatState::Playlist => RepeatState::Track,
|
||||||
RepeatState::Once => RepeatState::Disabled,
|
RepeatState::Track => RepeatState::Disabled,
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
match self.flags.config_state.player_state.repeat {
|
match self.flags.config_state.player_state.repeat {
|
||||||
RepeatState::Disabled => fl!("repeat-disabled"),
|
RepeatState::Disabled => fl!("repeat-disabled"),
|
||||||
RepeatState::Always => fl!("repeat-always"),
|
RepeatState::Playlist => fl!("repeat-playlist"),
|
||||||
RepeatState::Once => fl!("repeat-once"),
|
RepeatState::Track => fl!("repeat-track"),
|
||||||
},
|
},
|
||||||
widget::tooltip::Position::Top,
|
widget::tooltip::Position::Top,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
19
src/mpris.rs
19
src/mpris.rs
|
|
@ -60,12 +60,10 @@ impl MprisState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loop_status(&self) -> LoopStatus {
|
fn loop_status(&self) -> LoopStatus {
|
||||||
if self.will_repeat {
|
match self.repeat_state {
|
||||||
// TODO: Our choice is between Track and Playlist. Track is the best match for current repeat behavior,
|
RepeatState::Disabled => LoopStatus::None,
|
||||||
// but this may change when we implement mpris playlists.
|
RepeatState::Playlist => LoopStatus::Playlist,
|
||||||
LoopStatus::Track
|
RepeatState::Track => LoopStatus::Track,
|
||||||
} else {
|
|
||||||
LoopStatus::None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -210,11 +208,10 @@ impl PlayerInterface for Player {
|
||||||
|
|
||||||
async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<()> {
|
async fn set_loop_status(&self, loop_status: LoopStatus) -> Result<()> {
|
||||||
log::info!("SetLoopStatus({})", loop_status);
|
log::info!("SetLoopStatus({})", loop_status);
|
||||||
let repeat_state = if loop_status == LoopStatus::None {
|
let repeat_state = match loop_status {
|
||||||
RepeatState::Disabled
|
LoopStatus::None => RepeatState::Disabled,
|
||||||
} else {
|
LoopStatus::Playlist => RepeatState::Playlist,
|
||||||
// TODO: This may change when we implement mpris playlists.
|
LoopStatus::Track => RepeatState::Track,
|
||||||
RepeatState::Always
|
|
||||||
};
|
};
|
||||||
self.message(Message::RepeatToggled(repeat_state)).await?;
|
self.message(Message::RepeatToggled(repeat_state)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue