fix(playback): show the Up next card near the real end of an episode (#1768)

* fix(playback): show the Up next card near the real end of an episode

The card appeared a fixed 8 minutes before the end: most of a short
episode, and far into the story of a long one, with no way to hide it.

- Adaptive lead: 4% of the episode, clamped to 40 s … 3 min.
- A closing-credits chapter in the last third, when timeline segments
  carry one, brings the card forward to its start (capped at 5 min).
- Close button and Escape dismiss the card for the current next episode.
- After 10 s (not while hovered) the card collapses into a one-line pill.
- Seconds countdown in the last minute.
- New playback setting "Up next card" (default on) turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(playback): address Up next card review feedback

- Offer the Up next card setting for Embedded MPV only under the
  frame-copy engine; native view never mounts the shared controls.
- Hovering pauses the collapse delay instead of restarting it.
- Document that the card stays visible when the controls auto-hide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(playback): restart the Up next collapse delay for a new episode

- A card that stays mounted while its next episode changes gives the new
  item the full delay instead of the previous item's leftover.
- The setting description no longer promises credit-based timing: no
  current series path supplies chapters yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 authored and GitHub committed 2026-09-30 21:56:48 +02:00
1 parent cb317bad4c
commit 525ca7bc44
44 files changed
+782 -106

No files matched your search

+14 -2
View File
@@ -143,8 +143,8 @@ test.describe('@xtream inline series fullscreen', () => {
)
.toBeGreaterThanOrEqual(1);
// The 30 s fixture clip is inside the card's threshold from its
// first frame, so the card is up as soon as the duration is known.
// The 30 s fixture clip is shorter than the card's 40 s minimum
// lead, so the card is up as soon as the duration is known.
const card = playerView.locator(
'[data-test-id="player-controls-up-next"]'
);
@@ -169,6 +169,18 @@ test.describe('@xtream inline series fullscreen', () => {
await expect(card).toHaveAttribute('aria-label', /S01E03/, {
timeout: 15_000,
});
// The corner button hides the card for this episode; bringing the
// controls back does not bring it back.
await playerView
.locator('[data-test-id="player-controls-up-next-close"]')
.click();
await expect(card).toHaveCount(0);
await playerView.hover({ position: { x: 40, y: 40 } });
await expect(
playerView.locator('[data-test-id="player-controls-next-episode"]')
).toBeVisible();
await expect(card).toHaveCount(0);
});
test('switches episodes from the fullscreen episode panel without leaving fullscreen', async ({