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 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-10-03 13:33:27 +02:00
1 parent 4ad5f471e4
commit 95197baf98
5 files changed
+20 -7

No files matched your search

@@ -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()"
@@ -284,10 +284,16 @@ export class StalkerSeriesViewComponent implements OnDestroy {
readonly openingEpisodeId = signal<number | null>(null);
readonly activeEpisodeId = signal<number | null>(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<readonly string[]>([]);
/** `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(
@@ -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()"
@@ -241,7 +241,7 @@
[backdropUrl]="getBackdropUrl(info)"
[progress]="hero.progress()"
[trailerBackdropUrl]="trailerBackdropUrl()"
[contentKey]="'xtream-vod:' + selectedVodId()"
[contentKey]="contentKey()"
[playbackActive]="!!inlinePlayback()"
(backClicked)="goBack()"
(closePlayerRequested)="closeInlinePlayer()"
@@ -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