feat(player): skipping an episode marks it watched, not paused

Skipping to the next episode left a mid-episode resume point behind, so
the skipped episode reappeared in Continue Watching with a partial
progress bar. Skipping means "done with this one", not "stopped here".

- reportSkippedEpisode marks the outgoing episode played instead of
  reporting a stop position, and arms a one-shot suppression consumed by
  the player's stop handler, so VideoPlayer's post-navigation unmount
  stop report can't overwrite the 100% progress with the partial one.
- Continue Watching drops resume entries superseded by Next Up: an
  in-progress episode whose series has a next-up entry strictly later in
  series order (season, then episode) is hidden from the Home and TV
  rows. Movies, series without a next-up entry, and items with unknown
  or mixed ordering are always kept.

Adds UR-059, DR-088, DR-089.

TRACES: UR-059 | DR-088, DR-089
This commit is contained in:
2026-07-25 09:20:56 +02:00
parent c3ead64748
commit eb76c96e94
8 changed files with 383 additions and 5 deletions
+14 -1
View File
@@ -20,6 +20,7 @@
reportPlaybackProgress,
reportPlaybackStopped,
} from "$lib/services/playbackReporting";
import { reportSkippedEpisode, shouldSuppressStopReport } from "$lib/services/skipReporting";
import { cleanup as cleanupNextEpisode } from "$lib/services/nextEpisodeService";
import * as html5Adapter from "$lib/player/html5Adapter";
@@ -536,7 +537,11 @@
function handleReportStop(positionSeconds: number, reportId?: string) {
const id = reportId ?? itemId;
if (id) {
// A skipped episode was already recorded as fully watched. Its unmount stop
// report arrives after the skip navigation carrying the mid-episode
// position; letting it through would undo that and restore the partial
// progress bar.
if (id && !shouldSuppressStopReport(id)) {
reportPlaybackStopped(id, positionSeconds);
}
// Intentionally do NOT emit a "stopped" player state here. This runs on both
@@ -592,6 +597,14 @@
function handleSkipToNextEpisode() {
if (nextEpisode) {
// Skipping means "I'm done with this one" — record the outgoing episode as
// fully watched rather than leaving a mid-episode resume point behind. This
// also arms suppression of the VideoPlayer's unmount stop report, which
// would otherwise fire after navigation and overwrite the 100% progress
// with the partial position (see skipReporting.ts).
const skippedId = currentMedia?.id ?? itemId ?? null;
void reportSkippedEpisode(skippedId);
// Use replaceState so "close/back" returns to the library, not the previous episode.
// restart=true so advancing to the next episode always starts from the beginning,
// even if it was previously started or watched.