fix(player): the YT progress checkpoint reaches the cache, so a reopen cannot rewind it
The player checkpointed every 5s and the server stored it, but nothing wrote the new position back into react-query: the close path only marked qk.feed() stale with refetchType "none", and nothing refetches the feed until it remounts (staleTime 30s, refetchOnWindowFocus off). So the resume read the position the cache had been FETCHED with, and the next checkpoint then persisted that older value over the newer one. Measured on localdev: played to 830s, closed (server had 830), reopened the same card without a reload -> the row was back to 751s. 79 seconds of progress destroyed by a reopen, and the card's progress bar never moved either. The Plex player already solved this for its detail query, with a comment describing the identical failure; this is the feed-side twin that was missed in that sweep. patchCachedProgress writes the position the SERVER stored (the endpoint clears trivially-early and near-finished checkpoints to 0, so mirroring our own reading would disagree with the row) into every cached feed page and playlist detail, keeping array identity where nothing changed. A session map covers the second half: the launch queue is frozen at mount, so stepping next->prev back to an item played earlier in the same session resumed from that snapshot. Verified in the browser: 811 -> close 816 -> reopen 825, and the card now shows Continue/Restart.
This commit is contained in:
@@ -40,6 +40,12 @@ export interface VideoDetail {
|
||||
duration_seconds: number | null;
|
||||
}
|
||||
|
||||
/** The stored answer of `POST /api/videos/{id}/progress` — the row as it now stands. */
|
||||
export interface StoredProgress {
|
||||
video_id: string;
|
||||
position_seconds: number;
|
||||
}
|
||||
|
||||
export interface FeedResponse {
|
||||
items: Video[];
|
||||
has_more: boolean;
|
||||
@@ -179,8 +185,16 @@ export const feedApi = {
|
||||
),
|
||||
clearState: (id: string) =>
|
||||
req(`/api/videos/${id}/state`, { method: "DELETE" }, { idempotent: true }),
|
||||
saveProgress: (id: string, positionSeconds: number, durationSeconds: number) =>
|
||||
req(
|
||||
// Returns the position the server actually STORED — which is not always what we sent: a
|
||||
// trivially-early or near-finished checkpoint is cleared to 0 (see set_video_progress). Callers
|
||||
// that mirror the position into a cache must write this answer, never their own reading, so the
|
||||
// client can never disagree with the row.
|
||||
saveProgress: (
|
||||
id: string,
|
||||
positionSeconds: number,
|
||||
durationSeconds: number,
|
||||
): Promise<StoredProgress> =>
|
||||
req<StoredProgress>(
|
||||
`/api/videos/${id}/progress`,
|
||||
{
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user