Files
bookhoard/web/src/audio-rewind.ts
T
John O'Keefe 7b7ce39826 feat(audio-player): apply the resume-rewind setting on open and unpause
The chapters payload now carries resume_rewind_seconds (server-side
per-user setting); the player applies it at two events, each rewinding
once from where playback currently sits — never stacked:

- open-resume: restorePosition starts the rewind early from the saved
  spot (percentage and chapter+offset branches, clamped at the book
  start). Deep links (?loc=) stay exempt — an explicit jump is not a
  resume.
- unpause: a genuine pause→play steps back again (the re-orientation
  gap). Programmatic plays — post-restore autoplay and track-switch
  autoplays — set a suppress flag: their play event is the session
  start the open-rewind already positioned, not an unpause.

Math lives in audio-rewind.ts (pure, unit-tested) after the
audio-scrubber pattern; payload values gate through the 0/5/10
allow-list so stale or foreign servers degrade to exact resume.
2026-11-01 17:21:35 -05:00

22 lines
1.1 KiB
TypeScript

// Resume rewind (per-user server setting): how many seconds before the
// saved position the audiobook/podcast player starts. Applied on
// open-resume AND again on every genuine unpause — each event rewinds
// once from where playback currently sits, never stacked. Pure math;
// the Alpine component owns when to call it (the play-event suppress
// flag keeps restore-autoplay and track switches from double-firing).
// applyResumeRewind maps a position back by the rewind amount, clamped
// to the start of the book.
export function applyResumeRewind(positionSeconds: number, rewindSeconds: number): number {
if (!Number.isFinite(positionSeconds)) return 0;
if (!Number.isFinite(rewindSeconds) || rewindSeconds <= 0) return Math.max(0, positionSeconds);
return Math.max(0, positionSeconds - rewindSeconds);
}
// normalizeResumeRewind gates the server payload value to the setting's
// allow-list (0/5/10) — anything else means off.
export function normalizeResumeRewind(value: unknown): number {
const n = Number(value);
return n === 5 || n === 10 ? n : 0;
}