mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-04 16:57:00 +01:00
feat(app,android,ios): end an episode without chapters as well as one with them
The up-next countdown only ran over a credits chapter, so a file with no chapters - most that are not anime - cut straight into the next episode with no warning and no way to stay. Such a file now shows the same card over its last ten seconds, counted on the file's own clock so it reaches zero at the end of file, and its second button is Cancel rather than Watch till the end. On the phones, an episode that ends with nothing after it - the last one, autoplay off, or cancelled - left a black screen. It now says so, with Play next or Watch again, and Close. Android also: - saves the resume position from PlaybackService rather than the player screen. With background audio on the service outlives the activity, and a position only the screen kept stopped being saved when it went. A swipe from Recents saves and stops instead of playing on with nothing to return to. - shows "Opening <episode>..." while the stream opens, where there was a black screen for as long as the share session and block list took.
This commit is contained in:
1 parent
c5490ab3da
commit
c6b0457c4d
7 files changed
+313
-38
No files matched your search
@@ -262,6 +262,10 @@ pub struct UpNext {
|
||||
/// The viewer said to play this one out, or the countdown has already
|
||||
/// fired. Either way, do not ask again about this file.
|
||||
dismissed: bool,
|
||||
/// The card is counting down the last seconds of a file with no credits
|
||||
/// chapter, rather than the credits. Dismissing it then also means "do not
|
||||
/// start the next one", since there are no credits to watch till the end.
|
||||
tail: bool,
|
||||
}
|
||||
|
||||
/// Which way to step through a title's files.
|
||||
@@ -1110,9 +1114,20 @@ impl App {
|
||||
};
|
||||
}
|
||||
|
||||
// A file with no chapters has no credits to count over, so the card
|
||||
// takes its last seconds instead: without it the next episode cut in
|
||||
// with no warning and no way to stay. Not on a clip shorter than a
|
||||
// few countdowns, where the card would cover most of it.
|
||||
let tail = !playback.knows_credits()
|
||||
&& playback
|
||||
.duration
|
||||
.is_some_and(|duration| duration > UP_NEXT_SECONDS * 6.0)
|
||||
&& playback
|
||||
.remaining()
|
||||
.is_some_and(|left| left <= UP_NEXT_SECONDS);
|
||||
let counting = playback.loaded
|
||||
&& !playback.paused
|
||||
&& playback.in_credits()
|
||||
&& (playback.in_credits() || tail)
|
||||
&& has_next
|
||||
&& autoplay
|
||||
&& !self.up_next.dismissed;
|
||||
@@ -1120,10 +1135,20 @@ impl App {
|
||||
self.up_next.started = None;
|
||||
return None;
|
||||
}
|
||||
self.up_next.tail = tail;
|
||||
|
||||
let now = ctx.input(|input| input.time);
|
||||
let started = *self.up_next.started.get_or_insert(now);
|
||||
let left = UP_NEXT_SECONDS - (now - started);
|
||||
let left = if tail {
|
||||
// Counted on the file's own clock, so it reaches zero at the end
|
||||
// of file, which is what starts the next one.
|
||||
playback.remaining().unwrap_or_default()
|
||||
} else {
|
||||
UP_NEXT_SECONDS - (now - started)
|
||||
};
|
||||
if tail && left <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
if left <= 0.0 {
|
||||
// Latched, not cleared: the next file takes a moment to open, and
|
||||
// without this the countdown would fire again on every frame of
|
||||
@@ -1157,6 +1182,7 @@ impl App {
|
||||
left: (left / UP_NEXT_SECONDS) as f32,
|
||||
caption: next.map(|target| target.caption()).unwrap_or_default(),
|
||||
still,
|
||||
tail,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1228,6 +1254,16 @@ impl App {
|
||||
if !self.engine.playback_prefs().autoplay_next {
|
||||
return;
|
||||
}
|
||||
// Cancelled from the end-of-file card: the viewer chose to stay.
|
||||
let cancelled = self.up_next.tail
|
||||
&& self.up_next.dismissed
|
||||
&& self
|
||||
.playback
|
||||
.as_ref()
|
||||
.is_some_and(|playback| playback.id == self.up_next.playback_id);
|
||||
if cancelled {
|
||||
return;
|
||||
}
|
||||
let Some(target) = self.adjacent(Adjacent::Next) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -338,6 +338,18 @@ impl Playback {
|
||||
(end > self.position + 0.5).then_some((label, end))
|
||||
}
|
||||
|
||||
/// Whether the chapters say where the credits start. Most files that are
|
||||
/// not anime have no chapters at all.
|
||||
pub fn knows_credits(&self) -> bool {
|
||||
self.credits_at.is_some()
|
||||
}
|
||||
|
||||
/// Seconds of the file left to play, once its length is known.
|
||||
pub fn remaining(&self) -> Option<f64> {
|
||||
self.duration
|
||||
.map(|duration| (duration - self.position).max(0.0))
|
||||
}
|
||||
|
||||
/// Whether the playhead is inside the run of credits and preview that ends
|
||||
/// the file — what "the episode is over" means when a file states its
|
||||
/// chapters.
|
||||
|
||||
@@ -139,6 +139,9 @@ pub struct UpNextCard {
|
||||
pub caption: String,
|
||||
/// Its still, once there is one.
|
||||
pub still: Option<egui::TextureHandle>,
|
||||
/// Counting down the end of a file with no credits chapter, where the
|
||||
/// second button cancels the next episode rather than deferring it.
|
||||
pub tail: bool,
|
||||
}
|
||||
|
||||
impl Overlay {
|
||||
@@ -566,11 +569,15 @@ fn up_next_card(
|
||||
actions.push(Action::PlayAdjacent(Adjacent::Next));
|
||||
}
|
||||
ui.add_space(theme::space::S);
|
||||
if ui
|
||||
.button("Watch till the end")
|
||||
.on_hover_text("Play this one out — nothing will be skipped")
|
||||
.clicked()
|
||||
{
|
||||
let (label, hint) = if card.tail {
|
||||
("Cancel", "Stay here — the next episode will not start")
|
||||
} else {
|
||||
(
|
||||
"Watch till the end",
|
||||
"Play this one out — nothing will be skipped",
|
||||
)
|
||||
};
|
||||
if ui.button(label).on_hover_text(hint).clicked() {
|
||||
actions.push(Action::WatchToEnd);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user