diff --git a/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.spec.ts b/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.spec.ts index ee503898f..4bb5a5456 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.spec.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.spec.ts @@ -156,6 +156,30 @@ describe('createStalkerVodDetailActions openExternal', () => { expect(t.openExternalPlayback).toHaveBeenCalledTimes(1); }); + it('still rejects a repeat of the first movie after another one was started', async () => { + let selected = 42; + const t = setup(() => selected); + const other = { + ...MOVIE, + data: { id: '7' }, + } as unknown as VodDetailsItem; + const launch = (item: VodDetailsItem) => + t.actions.openExternal({ + item, + player: 'mpv', + positionSeconds: null, + }); + void launch(MOVIE); + selected = 7; + void launch(other); + selected = 42; + await launch(MOVIE); + + // A, B, A again: the second A is a repeat of a pending launch. + expect(t.beginPendingStart).toHaveBeenCalledTimes(2); + expect(t.openExternalPlayback).not.toHaveBeenCalled(); + }); + it('drops the stream once another movie was selected meanwhile', async () => { let selected = 42; const t = setup(() => selected); diff --git a/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.ts b/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.ts index 74bce669b..8e63f40b6 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-vod-detail-actions.ts @@ -71,10 +71,10 @@ export function createStalkerVodDetailActions( deps.snackBar.open(deps.translate.instant(key), undefined, { duration: 3000, }); - // A second "Open in external player" for the SAME movie during a slow - // `create_link` would resolve beside the first and start a second - // player; another movie's launch is not held back by it. - let externalLaunchInFlight: string | null = null; + // A second "Open in external player" for a movie whose `create_link` is + // still resolving would resolve beside the first and start a second + // player; other movies' launches are not held back by it. + const externalLaunchesInFlight = new Set(); return { /** The inline player copied the stream URL. */ @@ -106,10 +106,10 @@ export function createStalkerVodDetailActions( const playlistId = deps.playlistId(); const vodId = Number(event.item.data.id); const launchKey = `${playlistId}:${vodId}`; - if (externalLaunchInFlight === launchKey) { + if (externalLaunchesInFlight.has(launchKey)) { return; } - externalLaunchInFlight = launchKey; + externalLaunchesInFlight.add(launchKey); const stillSelected = () => deps.selectedVodId() === vodId && deps.playlistId() === playlistId; @@ -146,9 +146,7 @@ export function createStalkerVodDetailActions( notify('PORTALS.PLAYBACK_ERROR'); } } finally { - if (externalLaunchInFlight === launchKey) { - externalLaunchInFlight = null; - } + externalLaunchesInFlight.delete(launchKey); settlePendingStart?.(); } }, diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-menu.service.ts b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-menu.service.ts index f3ad99c59..2ffc9151b 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-menu.service.ts +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-menu.service.ts @@ -192,6 +192,15 @@ export class VodDetailsMenuService { this.logger.error('Resetting the playback position failed', error); return; } + // The clear was async: the route may show another movie by now + // (the Similar rail reuses it), whose own state must stay. + if ( + this.bindings()?.vodId() !== vodId || + this.xtreamStore.currentPlaylist()?.id !== playlistId + ) { + void this.xtreamStore.loadAllPositions(playlistId); + return; + } // A read still in flight started from the pre-write row; letting it // land would bring the position back. this.playback.discardPendingPositionLoads();