// Skip-to-next-episode reporting. // // Skipping an episode is a "done with it" signal, not a "stopped here" one: // the user is moving on because they've already seen it. So a manual skip // records the outgoing episode as fully played rather than saving the // mid-episode position as a resume point. // // The suppression handshake exists because VideoPlayer.onDestroy fires its // final reportStop *after* the skip navigation, with the mid-episode time. If // that landed, it would overwrite the just-written 100% progress and the // episode would look partially watched again. markSkipped() arms a one-shot // suppression that the stop handler consumes. // // TRACES: UR-059, UR-025 | DR-088 import { markAsPlayed, reportPlaybackStopped } from "./playbackReporting"; /** Item id whose next stop report should be dropped, if any. */ let suppressedItemId: string | null = null; /** * Arm suppression of the next stop report for `itemId`. * * Exported separately from `reportSkippedEpisode` so callers that already * handled their own reporting can still silence the unmount stop. */ export function markSkipped(itemId: string | null): void { if (!itemId) return; suppressedItemId = itemId; } /** * Should the pending stop report for `itemId` be dropped? * * One-shot: consumes the armed suppression, so a later genuine stop on the * same episode still reports its position normally. */ export function shouldSuppressStopReport(itemId: string | null): boolean { if (!itemId) return false; if (suppressedItemId !== itemId) return false; suppressedItemId = null; return true; } /** * Record a manually skipped episode as fully watched. * * Deliberately does NOT call `reportPlaybackStopped` — that would write the * partial position we are trying to avoid. */ export async function reportSkippedEpisode(itemId: string | null): Promise { if (!itemId) return; markSkipped(itemId); await markAsPlayed(itemId); } /** Test hook: clear armed suppression between cases. */ export function resetSkipState(): void { suppressedItemId = null; } // Re-exported so the module owns the full skip story; callers that need the // normal stop path keep importing it from playbackReporting directly. export { reportPlaybackStopped };