diff --git a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.spec.ts b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.spec.ts index f115c4b90..aa7fc98f3 100644 --- a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.spec.ts +++ b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.spec.ts @@ -203,17 +203,21 @@ describe('openEpisodeExternally', () => { () => new Promise((resolve) => (settleLaunch = resolve)) ); const launch = openEpisodeExternally(t.host, PLAYBACK, 'mpv'); - const start = jest.fn(); - queueEpisodeChoice('xtream-1:103', 'episode-1', start); - queueEpisodeChoice('xtream-1:103', 'episode-2', start); + // The first page queues, the viewer reopens the series and queues + // again: the reopened page's start runs, with its episode. + const startOld = jest.fn(); + const startNew = jest.fn(); + queueEpisodeChoice('xtream-1:103', 'episode-1', startOld); + queueEpisodeChoice('xtream-1:103', 'episode-2', startNew); await flush(); - expect(start).not.toHaveBeenCalled(); + expect(startOld).not.toHaveBeenCalled(); settleLaunch(); await launch; await flush(); - expect(start).toHaveBeenCalledTimes(1); - expect(start).toHaveBeenCalledWith('episode-2'); + expect(startOld).not.toHaveBeenCalled(); + expect(startNew).toHaveBeenCalledTimes(1); + expect(startNew).toHaveBeenCalledWith('episode-2'); }); it('keeps the running player when closing it fails', async () => { diff --git a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.ts b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.ts index ef7bd3730..900abcab1 100644 --- a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.ts +++ b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-external-launch.ts @@ -52,26 +52,38 @@ export function whenEpisodeLaunchesSettle(owner: string | null): Promise { return tail ? tail.then(() => undefined) : Promise.resolve(); } +interface QueuedEpisodeChoice { + readonly episode: unknown; + readonly start: (episode: never) => void; +} + /** Per owner, the latest Play/episode choice made while its forced launch settled. */ -const queuedChoices = new Map(); +const queuedChoices = new Map(); /** * Keeps the latest choice made while the owner's forced launch settles and - * hands it to `start` once that launch settled; the caller decides whether - * the page still shows the owner by then. + * hands it to the `start` that came with it once that launch settled; the + * caller decides whether its page still shows the owner by then. One settle + * handler per owner: a page reopened meanwhile queues with its own `start`, + * and that one runs, not the handler of the page the viewer left. */ export function queueEpisodeChoice( owner: string, episode: TEpisode, start: (episode: TEpisode) => void ): void { - queuedChoices.set(owner, episode); + const handlerRegistered = queuedChoices.has(owner); + queuedChoices.set(owner, { + episode, + start: start as (episode: never) => void, + }); + if (handlerRegistered) { + return; + } void whenEpisodeLaunchesSettle(owner).then(() => { - const queued = queuedChoices.get(owner) as TEpisode | undefined; - if (queued !== undefined) { - queuedChoices.delete(owner); - start(queued); - } + const queued = queuedChoices.get(owner); + queuedChoices.delete(owner); + queued?.start(queued.episode as never); }); }