From 95197baf987098ca543337897e61296b242b74e9 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 3 Oct 2026 13:33:27 +0200 Subject: [PATCH] fix(portals): playlist-scoped hero and pending-start keys for series and movies Provider ids are playlist-scoped: the Stalker series hero key and its pending-start key, and the Xtream series and movie hero keys, include the playlist so a cross-playlist navigation to the same number gets its own layout decision and does not inherit a pending start. Co-Authored-By: Claude Fable 5.1 --- .../stalker-series-view.component.html | 2 +- .../stalker-series-view.component.ts | 14 ++++++++++---- .../serial-details/serial-details.component.html | 4 +++- .../vod-details/vod-details-route.component.html | 2 +- .../lib/vod-details/vod-details-route.component.ts | 5 +++++ 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.html b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.html index 8c02ea4ce..a6ed172f9 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.html +++ b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.html @@ -7,7 +7,7 @@ [posterUrl]="serial.info.movie_image" [backdropUrl]="serial.info.tmdb_backdrop" [trailerBackdropUrl]="heroPresenter.trailerBackdropUrl()" - [contentKey]="'stalker-series:' + serial.id" + [contentKey]="'stalker-series:' + currentSeriesKey()" [playbackActive]="!!inlinePlayback()" (backClicked)="goBack()" (closePlayerRequested)="closeInlinePlayer()" diff --git a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts index d0c9219ec..5186b2f13 100644 --- a/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts +++ b/libs/portal/stalker/feature/src/lib/stalker-series-view/stalker-series-view.component.ts @@ -284,10 +284,16 @@ export class StalkerSeriesViewComponent implements OnDestroy { readonly openingEpisodeId = signal(null); readonly activeEpisodeId = signal(null); /** - * Series ids of starts still resolving their stream (`create_link` - * round trips): only the series on screen counts as starting. + * `playlist:series` keys of starts still resolving their stream + * (`create_link` round trips): only the series on screen counts as + * starting. Provider series ids are playlist-scoped. */ private readonly pendingStartSeriesIds = signal([]); + /** `playlist:series` of the series on screen; provider ids collide across playlists. */ + readonly currentSeriesKey = computed( + () => + `${this.stalkerStore.currentPlaylist()?._id ?? ''}:${this.displayItem()?.id ?? ''}` + ); readonly seasonWatchBatchRunning = signal(false); /** @@ -405,7 +411,7 @@ export class StalkerSeriesViewComponent implements OnDestroy { playbackActive: computed( () => this.pendingStartSeriesIds().includes( - String(this.displayItem()?.id ?? '') + this.currentSeriesKey() ) || this.inlinePlayback() !== null || this.openingEpisodeId() !== null || @@ -1286,7 +1292,7 @@ export class StalkerSeriesViewComponent implements OnDestroy { }; if (request.usesEmbeddedPlayer && !request.identity) return; - const pendingSeriesId = String(this.displayItem()?.id ?? ''); + const pendingSeriesId = this.currentSeriesKey(); this.pendingStartSeriesIds.update((ids) => [...ids, pendingSeriesId]); try { const playback = await this.stalkerStore.resolveVodPlayback( diff --git a/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.html b/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.html index 6300c3a4c..ccd18aa9c 100644 --- a/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.html +++ b/libs/portal/xtream/feature/src/lib/serial-details/serial-details.component.html @@ -9,7 +9,9 @@ [posterUrl]="info.cover" [backdropUrl]="getBackdropUrl(info)" [trailerBackdropUrl]="heroPresenter.trailerBackdropUrl()" - [contentKey]="'xtream-series:' + item.series_id" + [contentKey]=" + 'xtream-series:' + currentPlaylistId() + ':' + item.series_id + " [playbackActive]="!!inlinePlayback()" (backClicked)="goBack()" (closePlayerRequested)="closeInlinePlayer()" diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html index b2a432df1..77b004f57 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.html @@ -241,7 +241,7 @@ [backdropUrl]="getBackdropUrl(info)" [progress]="hero.progress()" [trailerBackdropUrl]="trailerBackdropUrl()" - [contentKey]="'xtream-vod:' + selectedVodId()" + [contentKey]="contentKey()" [playbackActive]="!!inlinePlayback()" (backClicked)="goBack()" (closePlayerRequested)="closeInlinePlayer()" diff --git a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.ts b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.ts index 244131fc4..4fbdb3128 100644 --- a/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.ts +++ b/libs/portal/xtream/feature/src/lib/vod-details/vod-details-route.component.ts @@ -381,6 +381,11 @@ export class VodDetailsRouteComponent implements OnInit, OnDestroy { readonly trailerEmbedUrl = computed(() => youtubeEmbedUrl(this.selectedVodInfo()?.youtube_trailer) ); + /** `playlist:vod`: the hero keys its one-time layout decision on it. */ + readonly contentKey = computed( + () => + `xtream-vod:${this.xtreamStore.currentPlaylist()?.id ?? ''}:${this.selectedVodId()}` + ); /** Settings → Playback → Play trailers in details background. */ readonly trailerBackdropUrl = computed(() => this.settingsStore.detailTrailerBackdrop?.() === true