diff options
| author | Malte Voos <git@mal.tc> | 2025-11-21 23:25:41 +0100 |
|---|---|---|
| committer | Malte Voos <git@mal.tc> | 2025-11-21 23:25:41 +0100 |
| commit | 90a1d5729c32910c249460cfe56ad682fd3fd608 (patch) | |
| tree | 4afeee7738986aebeece84a6944b43b32d0122d2 /src/app.rs | |
| parent | 016b76acba13e86df59f818581aa61f7bbaffff8 (diff) | |
| download | lleap-90a1d5729c32910c249460cfe56ad682fd3fd608.tar.gz lleap-90a1d5729c32910c249460cfe56ad682fd3fd608.zip | |
overhaul autopausing
Diffstat (limited to 'src/app.rs')
| -rw-r--r-- | src/app.rs | 157 |
1 files changed, 91 insertions, 66 deletions
diff --git a/src/app.rs b/src/app.rs index 7aa5abd..066980c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,7 +12,7 @@ use crate::{ subtitle_view::{SubtitleView, SubtitleViewMsg, SubtitleViewOutput}, tracks::{SUBTITLE_TRACKS, StreamIndex, SubtitleCue}, transcript::{Transcript, TranscriptMsg, TranscriptOutput}, - util::OptionTracker, + util::{OptionTracker, Tracker}, }; pub struct App { @@ -26,13 +26,14 @@ pub struct App { subtitle_selection_dialog: Controller<SubtitleSelectionDialog>, primary_stream_ix: Option<StreamIndex>, - primary_last_cue_ix: OptionTracker<usize>, + primary_cue: Tracker<Option<String>>, + primary_last_cue_ix: Tracker<Option<usize>>, + secondary_cue: Tracker<Option<String>>, secondary_stream_ix: Option<StreamIndex>, - secondary_last_cue_ix: OptionTracker<usize>, + secondary_last_cue_ix: Tracker<Option<usize>>, // for auto-pausing autopaused: bool, - primary_cue_active: bool, hovering_primary_cue: bool, } @@ -162,12 +163,13 @@ impl SimpleComponent for App { subtitle_selection_dialog, primary_stream_ix: None, - primary_last_cue_ix: OptionTracker::new(None), + primary_cue: Tracker::new(None), + primary_last_cue_ix: Tracker::new(None), secondary_stream_ix: None, - secondary_last_cue_ix: OptionTracker::new(None), + secondary_cue: Tracker::new(None), + secondary_last_cue_ix: Tracker::new(None), autopaused: false, - primary_cue_active: false, hovering_primary_cue: false, }; @@ -177,9 +179,6 @@ impl SimpleComponent for App { } fn update(&mut self, message: Self::Input, _sender: ComponentSender<Self>) { - self.primary_last_cue_ix.reset(); - self.secondary_last_cue_ix.reset(); - match message { AppMsg::NewCue(stream_index, cue) => { self.transcript @@ -203,20 +202,41 @@ impl SimpleComponent for App { } AppMsg::PositionUpdate(pos) => { if let Some(stream_ix) = self.primary_stream_ix { - let cue = - Self::get_cue_and_update_ix(stream_ix, pos, &mut self.primary_last_cue_ix); - let cue_is_some = cue.is_some(); - - // beginning of new subtitle - if self.primary_last_cue_ix.is_dirty() - || (!self.primary_cue_active && cue_is_some) - { + // sometimes we get a few position update messages after + // auto-pausing; this prevents us from immediately un-autopausing + // again + if self.autopaused { + return; + } + + let cue_was_some = self.primary_cue.get().is_some(); + + Self::update_cue( + stream_ix, + pos, + &mut self.primary_cue, + &mut self.primary_last_cue_ix, + ); + + if self.primary_cue.is_dirty() { + // last cue just ended -> auto-pause + if cue_was_some && self.hovering_primary_cue { + self.player.sender().send(PlayerMsg::Pause).unwrap(); + self.autopaused = true; + return; + } + self.subtitle_view .sender() - .send(SubtitleViewMsg::SetPrimaryCue(cue)) + .send(SubtitleViewMsg::SetPrimaryCue( + self.primary_cue.get().clone(), + )) .unwrap(); - self.primary_cue_active = cue_is_some; + self.primary_cue.reset(); + } + + if self.primary_last_cue_ix.is_dirty() { if let Some(ix) = self.primary_last_cue_ix.get() { self.transcript .sender() @@ -226,33 +246,24 @@ impl SimpleComponent for App { self.primary_last_cue_ix.reset(); } - - // end of current subtitle - if self.primary_cue_active && !cue_is_some && !self.autopaused { - if self.hovering_primary_cue { - self.player.sender().send(PlayerMsg::Pause).unwrap(); - self.autopaused = true; - } else { - self.subtitle_view - .sender() - .send(SubtitleViewMsg::SetPrimaryCue(None)) - .unwrap(); - self.primary_cue_active = false; - } - } } if let Some(stream_ix) = self.secondary_stream_ix { - if !self.autopaused { + Self::update_cue( + stream_ix, + pos, + &mut self.secondary_cue, + &mut self.secondary_last_cue_ix, + ); + + if !self.autopaused && self.secondary_cue.is_dirty() { self.subtitle_view .sender() .send(SubtitleViewMsg::SetSecondaryCue( - Self::get_cue_and_update_ix( - stream_ix, - pos, - &mut self.secondary_last_cue_ix, - ), + self.secondary_cue.get().clone(), )) .unwrap(); + + self.secondary_cue.reset(); } } } @@ -302,50 +313,64 @@ impl SimpleComponent for App { } impl App { - fn get_cue_and_update_ix( + fn update_cue( stream_ix: StreamIndex, position: gst::ClockTime, - last_cue_ix: &mut OptionTracker<usize>, - ) -> Option<String> { + cue: &mut Tracker<Option<String>>, + last_cue_ix: &mut Tracker<Option<usize>>, + ) { let lock = SUBTITLE_TRACKS.read(); - let track = lock.get(&stream_ix)?; + let track = lock.get(&stream_ix).unwrap(); // try to find current cue quickly (should usually succeed during playback) if let Some(ix) = last_cue_ix.get() { - let last_cue = track.cues.get(*ix)?; + let last_cue = track.cues.get(*ix).unwrap(); if last_cue.start <= position && position <= last_cue.end { - return Some(last_cue.text.clone()); - } - let next_cue = track.cues.get(ix + 1)?; - if last_cue.end < position && position < next_cue.start { - return None; - } - if next_cue.start <= position && position <= next_cue.end { - last_cue_ix.set(Some(ix + 1)); - return Some(next_cue.text.clone()); + // still at current cue + return; + } else if let Some(next_cue) = track.cues.get(ix + 1) { + if last_cue.end < position && position < next_cue.start { + // strictly between cues + cue.set(None); + return; + } + if next_cue.start <= position && position <= next_cue.end { + // already in next cue (this happens when one cue immediately + // follows the previous one) + cue.set(Some(next_cue.text.clone())); + last_cue_ix.set(Some(ix + 1)); + return; + } } } // if we are before the first subtitle, no need to look further - if position < track.cues.first()?.start { + if track.cues.is_empty() || position < track.cues.first().unwrap().start { + cue.set(None); last_cue_ix.set(None); - return None; + return; } // otherwise, search the whole track (e.g. after seeking) - let (ix, cue) = track + match track .cues .iter() .enumerate() .rev() - .find(|(_ix, cue)| cue.start <= position)?; - - last_cue_ix.set(Some(ix)); - - if position <= cue.end { - Some(cue.text.clone()) - } else { - None - } + .find(|(_ix, cue)| cue.start <= position) + { + Some((ix, new_cue)) => { + last_cue_ix.set(Some(ix)); + if position <= new_cue.end { + cue.set(Some(new_cue.text.clone())); + } else { + cue.set(None); + } + } + None => { + cue.set(None); + last_cue_ix.set(None); + } + }; } } |