feat(player): restore the last player session at boot
Reload the episode that was loaded in the player when the previous run ended (persisted on play/load and synchronously at exit) into the Player tab paused at its saved position — never autostarted. Episodes at or above 98% completion are skipped, as are empty-player and unsubscribed episodes. - useAudio gains load(episode) (sets currentEpisode/position/Now Playing without starting the backend) and restoreLastSession(), triggered once at boot and serialized through a chain so a late-finishing boot restore can't clobber later state. - togglePlayback branches on a startedPlayback flag: a restored episode starts the backend from the saved position; a paused one resumes. - stop() clears the marker; the exit teardown writes it synchronously (process.exit bypasses async writes). - feed/progress stores expose whenReady() so restore waits for the async boot loads; feed store gains findEpisode(). - app-persistence serializes last-player marker writes and exposes waitForLastPlayerWrite() for deterministic tests. - tests/restore-session.test.ts: real modules + local RSS feed server; the real useAudio is imported via a ?restore-test query suffix to bypass the suite's mock.module leak across shared bun workers.
This commit is contained in:
@@ -219,9 +219,14 @@ export async function loadLastPlayerFromFile(): Promise<LastPlayerState | null>
|
||||
}
|
||||
}
|
||||
|
||||
/** Save the last-loaded-player marker (overwrite, fire-and-forget) */
|
||||
/** Serialized marker-write chain: concurrent writes land in submission
|
||||
* order, and callers can await the last one (tests read the file back
|
||||
* deterministically). Mirrors updateConfig's write serialization. */
|
||||
let lastPlayerWriteChain: Promise<void> = Promise.resolve();
|
||||
|
||||
/** Save the last-loaded-player marker (fire-and-forget) */
|
||||
export function saveLastPlayerToFile(state: LastPlayerState): void {
|
||||
(async () => {
|
||||
lastPlayerWriteChain = lastPlayerWriteChain.then(async () => {
|
||||
try {
|
||||
await ensureConfigDir();
|
||||
await Bun.write(
|
||||
@@ -231,7 +236,12 @@ export function saveLastPlayerToFile(state: LastPlayerState): void {
|
||||
} catch {
|
||||
// Silently ignore write errors
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolves once every marker write submitted so far has landed on disk. */
|
||||
export function waitForLastPlayerWrite(): Promise<void> {
|
||||
return lastPlayerWriteChain;
|
||||
}
|
||||
|
||||
/** Synchronous variant for the process-exit teardown. `q` quits through
|
||||
|
||||
Reference in New Issue
Block a user