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 fd016a499..e87d4dc4e 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 @@ -149,6 +149,7 @@ [playingEpisodeId]=" inlinePlayback()?.contentInfo?.contentXtreamId ?? null " + [seasonDescriptions]="seasonDescriptions()" [isLoading]=" isVodSeriesSeasonsLoading() || isCurrentSeasonLoading(seasonContainer?.selectedSeason()) 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 27bba57b7..000ecfc38 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 @@ -135,8 +135,28 @@ export class StalkerSeriesViewComponent implements OnDestroy { private readonly tmdbSeasonEpisodes = signal< ReadonlyMap >(new Map()); + /** TMDB season overviews, keyed like tmdbSeasonEpisodes. */ + private readonly tmdbSeasonOverviews = signal< + ReadonlyMap + >(new Map()); private readonly tmdbEnrichment = inject(TmdbEnrichmentService); + /** Season descriptions for the season tabs (TMDB overview per season). */ + readonly seasonDescriptions = computed>(() => { + const tmdbId = this.displayItem()?.info?.tmdb_id; + if (!tmdbId) { + return {}; + } + const prefix = `${tmdbId}|`; + const descriptions: Record = {}; + for (const [mapKey, overview] of this.tmdbSeasonOverviews()) { + if (mapKey.startsWith(prefix)) { + descriptions[mapKey.slice(prefix.length)] = overview; + } + } + return descriptions; + }); + /** * Track VOD series seasons with their loaded episodes */ @@ -380,16 +400,26 @@ export class StalkerSeriesViewComponent implements OnDestroy { return; } - const tmdbEpisodes = await this.tmdbEnrichment.getSeasonEpisodes( + const season = await this.tmdbEnrichment.getSeason( tmdbId, seasonNumber ); - if (!tmdbEpisodes?.length) { + if (!season) { + return; + } + + if (season.overview) { + const overviews = new Map(this.tmdbSeasonOverviews()); + overviews.set(mapKey, season.overview); + this.tmdbSeasonOverviews.set(overviews); + } + + if (!season.episodes?.length) { return; } const next = new Map(this.tmdbSeasonEpisodes()); - next.set(mapKey, tmdbEpisodes); + next.set(mapKey, season.episodes); this.tmdbSeasonEpisodes.set(next); } 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 b048e586e..45177a3b1 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 @@ -157,6 +157,7 @@ [playingEpisodeId]=" inlinePlayback()?.contentInfo?.contentXtreamId ?? null " + [seasonDescriptions]="seasonDescriptions()" (episodeClicked)="playEpisode($event)" (playbackToggleRequested)="handlePlaybackToggleRequested($event)" (seasonSelected)="onSeasonSelected($event)" 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 c39931fea..16c9c7081 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 @@ -109,6 +109,17 @@ export class SerialDetailsComponent implements OnInit, OnDestroy { readonly inlineEpisodeMetadata = this.playback.inlineEpisodeMetadata; readonly inlineSeriesNavigation = this.playback.inlineSeriesNavigation; + /** Season overviews from get_series_info, keyed by season key. */ + readonly seasonDescriptions = computed>(() => { + const descriptions: Record = {}; + for (const season of this.selectedItem()?.seasons ?? []) { + if (season?.overview && season.season_number !== undefined) { + descriptions[String(season.season_number)] = season.overview; + } + } + return descriptions; + }); + /** TMDB recommendations matched against the loaded series catalog */ readonly similarItems = computed(() => { const item = this.selectedItem(); diff --git a/libs/services/src/lib/tmdb/tmdb-enrichment.service.ts b/libs/services/src/lib/tmdb/tmdb-enrichment.service.ts index a2b4ecd49..c94797bb3 100644 --- a/libs/services/src/lib/tmdb/tmdb-enrichment.service.ts +++ b/libs/services/src/lib/tmdb/tmdb-enrichment.service.ts @@ -65,6 +65,19 @@ export class TmdbEnrichmentService { tmdbId: number, seasonNumber: number ): Promise { + const season = await this.getSeason(tmdbId, seasonNumber); + return season ? (season.episodes ?? []) : null; + } + + /** + * Full season payload (overview + episodes) in the app language, using + * the same cache rows as `getSeasonEpisodes`. Returns `null` when + * enrichment is off or the request fails. + */ + async getSeason( + tmdbId: number, + seasonNumber: number + ): Promise { if (!this.isEnabled()) { return null; } @@ -79,10 +92,7 @@ export class TmdbEnrichmentService { cached?.payload ) { try { - const season = JSON.parse( - cached.payload - ) as TmdbSeasonDetails; - return season.episodes ?? []; + return JSON.parse(cached.payload) as TmdbSeasonDetails; } catch { // Corrupt cache row — fall through to a fresh fetch } @@ -103,7 +113,7 @@ export class TmdbEnrichmentService { payload: JSON.stringify(season), }); - return season.episodes ?? []; + return season; } catch (error) { console.warn('TMDB season enrichment failed:', error); return null; diff --git a/libs/services/src/lib/tmdb/tmdb.types.ts b/libs/services/src/lib/tmdb/tmdb.types.ts index 3f116d211..ead6973ec 100644 --- a/libs/services/src/lib/tmdb/tmdb.types.ts +++ b/libs/services/src/lib/tmdb/tmdb.types.ts @@ -99,6 +99,7 @@ export interface TmdbEpisode { export interface TmdbSeasonDetails { season_number?: number; + overview?: string; episodes?: TmdbEpisode[]; }