fix(plex): a refused finish restores no resume point at the media's own end
The rollback of a refused watched mark guarded against Plex's REPORTED duration. On a file shorter than its metadata the end sits outside the finish margin, so playing to the end and having the mark refused stored the very end as a resume point on an unwatched row: a ~99% bar that reopened in the last seconds. The media's own ended flag now decides.
This commit is contained in:
@@ -420,6 +420,24 @@ describe("leaving an item that was played through", () => {
|
|||||||
expect(qc.getQueryData<PlexItemDetail>(qk.plexItem("A", "en"))?.status).not.toBe("watched");
|
expect(qc.getQueryData<PlexItemDetail>(qk.plexItem("A", "en"))?.status).not.toBe("watched");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("restores nothing when the media ended short of the duration Plex reports", async () => {
|
||||||
|
// Plex's metadata can overrun the file. Ending at 2900 of a reported 3000 is outside the finish
|
||||||
|
// margin, so the duration-based guard alone wrote 2900 back as the resume point: an unwatched
|
||||||
|
// item with a ~99% bar that reopened in its last seconds. The media's own `ended` decides.
|
||||||
|
prefs({ upNext: false });
|
||||||
|
const { progress } = mount({
|
||||||
|
details: { A: item("A"), B: item("B") },
|
||||||
|
sessions: { A: () => Promise.resolve(direct("A")), B: () => new Promise(() => {}) },
|
||||||
|
queue: ["A", "B"],
|
||||||
|
});
|
||||||
|
vi.spyOn(api, "plexSetState").mockRejectedValue(new Error("offline"));
|
||||||
|
await playingAt("/media/A", 2900);
|
||||||
|
endMedia();
|
||||||
|
await screen.findByText("Title B");
|
||||||
|
await tick();
|
||||||
|
expect(progress.mock.calls.filter(([id, , , final]) => id === "A" && final)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("does restore the position when the refused finish came from the credits skip", async () => {
|
it("does restore the position when the refused finish came from the credits skip", async () => {
|
||||||
// The other caller of the same helper captures a MID-episode position, and there the rollback
|
// The other caller of the same helper captures a MID-episode position, and there the rollback
|
||||||
// is the point: without it the viewer loses the stretch they watched. One helper, two callers,
|
// is the point: without it the viewer loses the stretch they watched. One helper, two callers,
|
||||||
|
|||||||
@@ -215,6 +215,10 @@ export function usePlexProgress(opts: {
|
|||||||
// Watched badge the server disagreed with (round 5).
|
// Watched badge the server disagreed with (round 5).
|
||||||
const pos = Math.floor(absRef.current);
|
const pos = Math.floor(absRef.current);
|
||||||
const dur = durationRef.current;
|
const dur = durationRef.current;
|
||||||
|
// The media's own end, not Plex's reported duration: a file shorter than its metadata (a
|
||||||
|
// configuration this file documents) ends outside the margin below, and restoring THAT
|
||||||
|
// position left an unwatched item with a ~99% bar that reopened in its last seconds.
|
||||||
|
const ended = video?.ended === true;
|
||||||
void confirmed?.catch(() => {
|
void confirmed?.catch(() => {
|
||||||
finishRefused = true;
|
finishRefused = true;
|
||||||
// Restore the resume point the finish replaced — but ONLY where there is one to restore.
|
// Restore the resume point the finish replaced — but ONLY where there is one to restore.
|
||||||
@@ -224,7 +228,7 @@ export function usePlexProgress(opts: {
|
|||||||
// performed, through a second route, exactly the write that had just been refused — while
|
// performed, through a second route, exactly the write that had just been refused — while
|
||||||
// the caller's error toast was already on screen saying it had failed (round 6). The
|
// the caller's error toast was already on screen saying it had failed (round 6). The
|
||||||
// credits-skip caller captures a mid-episode position and is unaffected.
|
// credits-skip caller captures a mid-episode position and is unaffected.
|
||||||
if (pos > 0 && dur > 0 && pos <= dur - FINISH_MARGIN_S)
|
if (!ended && pos > 0 && dur > 0 && pos <= dur - FINISH_MARGIN_S)
|
||||||
api.plexProgress(id, pos, dur, true).catch(() => {});
|
api.plexProgress(id, pos, dur, true).catch(() => {});
|
||||||
// `settle` may already have patched the detail to watched / 0 on the way out. Dropping that
|
// `settle` may already have patched the detail to watched / 0 on the way out. Dropping that
|
||||||
// entry rather than invalidating it: an invalidated query with no observer keeps serving its
|
// entry rather than invalidating it: an invalidated query with no observer keeps serving its
|
||||||
|
|||||||
Reference in New Issue
Block a user