From 023c3444afc7fdc03c5f8056b1fbabd344c9e8f1 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 3 Oct 2026 15:08:12 +0200 Subject: [PATCH] fix(portals): a failed episode launch is reported to its own visit only The menu compares the playback service's series+visit page token, so a launch started on an earlier visit of the same series does not surface its failure after the viewer came back. Co-Authored-By: Claude Fable 5.1 --- .../serial-details/serial-details-menu.service.ts | 15 +++++++-------- .../serial-details-playback.service.ts | 2 +- .../serial-details/serial-details.component.ts | 1 + 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-menu.service.ts b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-menu.service.ts index b8d68d0f6..6ec898c1f 100644 --- a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-menu.service.ts +++ b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-menu.service.ts @@ -40,6 +40,8 @@ interface SerialDetailsMenuBindings { /** A forced launch has not published its session yet: bulk watched actions would include it. */ readonly startPending: Signal; readonly resetProgress: () => Promise; + /** Series and visit on screen; a launch's failure is reported to that page only. */ + readonly pageToken: () => string; /** The regular episode start forced to MPV/VLC, so history and the launch position are recorded. */ readonly openEpisodeExternally: ( episode: XtreamSerieEpisode, @@ -242,8 +244,7 @@ export class SerialDetailsMenuService { if (!bindings || !episode) { return; } - const seriesId = this.seriesId(); - const playlistId = this.xtreamStore.currentPlaylist()?.id; + const token = bindings.pageToken(); try { await bindings.openEpisodeExternally( episode, @@ -251,12 +252,10 @@ export class SerialDetailsMenuService { ); } catch (error) { this.logger.warn('External episode launch failed', error); - // The page is reused across series: a failure that lands after - // the viewer moved on is not the new title's problem. - if ( - this.seriesId() === seriesId && - this.xtreamStore.currentPlaylist()?.id === playlistId - ) { + // The page is reused across series and visits: a failure that + // lands after the viewer moved on (or came back) is not the + // current visit's problem. + if (bindings.pageToken() === token) { this.notify('PORTALS.PLAYBACK_ERROR'); } } diff --git a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-playback.service.ts b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-playback.service.ts index 22bcc076f..db5989a1d 100644 --- a/libs/portal/xtream/feature/src/lib/serial-details/serial-details-playback.service.ts +++ b/libs/portal/xtream/feature/src/lib/serial-details/serial-details-playback.service.ts @@ -413,7 +413,7 @@ export class SerialDetailsPlaybackService { * only while the page still shows what it showed when the launch * started, not after leaving and coming back. */ - private pageToken(): string { + pageToken(): string { return `${this.launchOwner()}#${this.pageGeneration}`; } diff --git a/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.ts b/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.ts index 193452cdc..9c40d2b12 100644 --- a/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.ts +++ b/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.ts @@ -229,6 +229,7 @@ export class SerialDetailsComponent implements OnDestroy { this.playback.activeEpisodeId() !== null ), startPending: this.playback.forcedLaunchPending, + pageToken: () => this.playback.pageToken(), resetProgress: () => this.resetProgress(), openEpisodeExternally: (episode, player) => this.playback.playEpisode(episode, player),