mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
fix(portals): a queued episode choice stays with its series
The choice made while a forced launch settles is kept per playlist:series owner by the launch helper and only starts if the page still shows that series, so a Similar-rail navigation cannot play it under another title. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
5b2fbe296b
commit
39ec848110
3 files changed
+50
-16
No files matched your search
+21
@@ -6,6 +6,7 @@ import type {
|
||||
import {
|
||||
isEpisodeLaunchPending,
|
||||
openEpisodeExternally,
|
||||
queueEpisodeChoice,
|
||||
} from './serial-details-external-launch';
|
||||
|
||||
const PLAYBACK: ResolvedPortalPlayback = {
|
||||
@@ -195,6 +196,26 @@ describe('openEpisodeExternally', () => {
|
||||
expect(t.openExternalPlayback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('starts only the latest queued choice once the launch settled', async () => {
|
||||
const t = host(null);
|
||||
let settleLaunch: () => void = () => undefined;
|
||||
t.openExternalPlayback.mockImplementation(
|
||||
() => new Promise<void>((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);
|
||||
await flush();
|
||||
expect(start).not.toHaveBeenCalled();
|
||||
|
||||
settleLaunch();
|
||||
await launch;
|
||||
await flush();
|
||||
expect(start).toHaveBeenCalledTimes(1);
|
||||
expect(start).toHaveBeenCalledWith('episode-2');
|
||||
});
|
||||
|
||||
it('keeps the running player when closing it fails', async () => {
|
||||
const t = host(session());
|
||||
t.closeSession.mockRejectedValue(new Error('still busy'));
|
||||
|
||||
@@ -52,6 +52,29 @@ export function whenEpisodeLaunchesSettle(owner: string | null): Promise<void> {
|
||||
return tail ? tail.then(() => undefined) : Promise.resolve();
|
||||
}
|
||||
|
||||
/** Per owner, the latest Play/episode choice made while its forced launch settled. */
|
||||
const queuedChoices = new Map<string, unknown>();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function queueEpisodeChoice<TEpisode>(
|
||||
owner: string,
|
||||
episode: TEpisode,
|
||||
start: (episode: TEpisode) => void
|
||||
): void {
|
||||
queuedChoices.set(owner, episode);
|
||||
void whenEpisodeLaunchesSettle(owner).then(() => {
|
||||
const queued = queuedChoices.get(owner) as TEpisode | undefined;
|
||||
if (queued !== undefined) {
|
||||
queuedChoices.delete(owner);
|
||||
start(queued);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The "…" menu's MPV/VLC launch of an episode. An episode of this series
|
||||
* still running externally is closed first: with instance reuse off a
|
||||
|
||||
+6
-16
@@ -44,7 +44,7 @@ import { XTREAM_SERIES_RESUME_TARGET } from './serial-details-resume-target.toke
|
||||
import {
|
||||
isEpisodeLaunchPending,
|
||||
openEpisodeExternally,
|
||||
whenEpisodeLaunchesSettle,
|
||||
queueEpisodeChoice,
|
||||
} from './serial-details-external-launch';
|
||||
import { SerialDetailsPlaybackPositionState } from './serial-details-playback-position-state';
|
||||
import {
|
||||
@@ -228,11 +228,11 @@ export class SerialDetailsPlaybackService {
|
||||
): void {
|
||||
// A forced launch still settling owns the next start: the latest
|
||||
// choice made meanwhile starts once it settled, never beside it.
|
||||
if (!player && this.forcedLaunchPending()) {
|
||||
this.queuedEpisode = episode;
|
||||
void whenEpisodeLaunchesSettle(this.launchOwner()).then(() =>
|
||||
this.playQueuedEpisode()
|
||||
);
|
||||
const owner = this.launchOwner();
|
||||
if (!player && owner && this.forcedLaunchPending()) {
|
||||
queueEpisodeChoice(owner, episode, (queued) => {
|
||||
if (this.launchOwner() === owner) this.playEpisode(queued);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const playlist = this.xtreamStore.currentPlaylist();
|
||||
@@ -425,16 +425,6 @@ export class SerialDetailsPlaybackService {
|
||||
readonly forcedLaunchPending = computed(() =>
|
||||
isEpisodeLaunchPending(this.launchOwner())
|
||||
);
|
||||
/** The choice made while a forced launch settled; only the latest one starts. */
|
||||
private queuedEpisode: XtreamSerieEpisode | null = null;
|
||||
|
||||
private playQueuedEpisode(): void {
|
||||
const episode = this.queuedEpisode;
|
||||
this.queuedEpisode = null;
|
||||
if (episode && !this.forcedLaunchPending()) {
|
||||
this.playEpisode(episode);
|
||||
}
|
||||
}
|
||||
|
||||
/** `playlist:series` of the page, null once it is gone or shows another series. */
|
||||
launchOwner(): string | null {
|
||||
|
||||
Reference in new issue
Block a user