mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
fix(portals): a reopened series can relaunch an episode an earlier visit still settles
The duplicate guard of forced episode launches is keyed by the page visit, not the owner, so a launch left behind by an earlier visit of the same series no longer swallows the reopened page's click; that click queues behind the stale launch on the owner's chain instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
023c3444af
commit
3cdf1bd3ea
2 files changed
+31
-2
No files matched your search
+24
@@ -81,10 +81,12 @@ describe('openEpisodeExternally', () => {
|
||||
const openExternalPlayback = jest.fn().mockResolvedValue(undefined);
|
||||
const closeSession = jest.fn().mockResolvedValue(undefined);
|
||||
const launchOwner = jest.fn().mockReturnValue('xtream-1:103');
|
||||
const pageToken = jest.fn().mockReturnValue('xtream-1:103#1');
|
||||
return {
|
||||
openExternalPlayback,
|
||||
closeSession,
|
||||
launchOwner,
|
||||
pageToken,
|
||||
host: {
|
||||
portalPlayer: { openExternalPlayback },
|
||||
externalPlayback: {
|
||||
@@ -92,6 +94,7 @@ describe('openEpisodeExternally', () => {
|
||||
closeSession,
|
||||
},
|
||||
launchOwner,
|
||||
pageToken,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -255,6 +258,27 @@ describe('openEpisodeExternally', () => {
|
||||
expect(startNew).toHaveBeenCalledWith('episode-2');
|
||||
});
|
||||
|
||||
it('lets a reopened series launch the episode an earlier visit still settles', async () => {
|
||||
const first = host(session());
|
||||
let settleClose: () => void = () => undefined;
|
||||
first.closeSession.mockImplementation(
|
||||
() => new Promise<void>((resolve) => (settleClose = resolve))
|
||||
);
|
||||
const stale = openEpisodeExternally(first.host, PLAYBACK, 'mpv');
|
||||
// The viewer left; this visit's page is gone.
|
||||
first.launchOwner.mockReturnValue(null);
|
||||
|
||||
const reopened = host(null);
|
||||
reopened.pageToken.mockReturnValue('xtream-1:103#2');
|
||||
const fresh = openEpisodeExternally(reopened.host, PLAYBACK, 'mpv');
|
||||
settleClose();
|
||||
await stale;
|
||||
await fresh;
|
||||
|
||||
expect(first.openExternalPlayback).not.toHaveBeenCalled();
|
||||
expect(reopened.openExternalPlayback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('keeps the running player when closing it fails', async () => {
|
||||
const t = host(session());
|
||||
t.closeSession.mockRejectedValue(new Error('still busy'));
|
||||
|
||||
@@ -57,9 +57,11 @@ export interface SeriesExternalLaunchHost {
|
||||
>;
|
||||
/** `playlist:series` on screen, null once the page is gone. */
|
||||
launchOwner(): string | null;
|
||||
/** Owner plus visit: a reopened series is a new page for the duplicate guard. */
|
||||
pageToken(): string;
|
||||
}
|
||||
|
||||
/** `owner:episode` keys of launches that have not settled yet. */
|
||||
/** `page:episode` keys of launches that have not settled yet. */
|
||||
const launchesInFlight = new Set<string>();
|
||||
/** Per owner, the tail of its launch chain: a second episode waits its turn. */
|
||||
const launchChains = new Map<string, Promise<unknown>>();
|
||||
@@ -144,7 +146,10 @@ export async function openEpisodeExternally(
|
||||
if (!owner) {
|
||||
return;
|
||||
}
|
||||
const key = `${owner}:${playback.contentInfo?.contentXtreamId ?? playback.streamUrl}`;
|
||||
// Keyed by the visit, not the owner: a launch left behind by an earlier
|
||||
// visit of the same series must not swallow the reopened page's click,
|
||||
// which instead queues behind it on the owner's chain.
|
||||
const key = `${host.pageToken()}:${playback.contentInfo?.contentXtreamId ?? playback.streamUrl}`;
|
||||
if (launchesInFlight.has(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user