fix(stalker): preserve series episode metadata

This commit is contained in:
4gray committed 2026-07-20 21:49:36 +02:00
1 parent cfd692a7f3
commit ab8468be22
4 files changed
+117 -4

No files matched your search

@@ -15,6 +15,7 @@ import type {
export interface StalkerQuickStartButton {
labelKey: string;
labelParams?: Record<string, number>;
episodeLabel: string | null;
icon: string;
disabled: boolean;
@@ -72,6 +73,7 @@ export function getStalkerSeriesQuickStartButton(
return {
labelKey: action.labelKey,
labelParams: action.labelParams,
episodeLabel: action.episodeLabel,
icon: action.icon,
disabled: action.disabled,
@@ -104,7 +104,10 @@
</span>
<span class="play-btn__copy">
<span class="play-btn__label">
{{ action.labelKey | translate }}
{{
action.labelKey
| translate: action.labelParams
}}
</span>
@if (action.episodeLabel) {
<span class="play-btn__meta">
@@ -241,7 +241,17 @@ describe('StalkerSeriesViewComponent', () => {
StubSeasonContainerComponent,
MockPipe(
TranslatePipe,
(value: string | null | undefined) => value ?? ''
(
value: string | null | undefined,
params?: Record<string, number>
) => {
if (value === 'XTREAM.PLAY_EPISODE') {
return `Play episode ${
params?.['episode'] ?? '{{episode}}'
}`;
}
return value ?? '';
}
),
],
},
@@ -290,6 +300,65 @@ describe('StalkerSeriesViewComponent', () => {
);
});
it('interpolates the episode number for a recently started VOD is_series episode', async () => {
selectedContentType.set('vod');
selectedItem.set({
id: '50001',
is_series: true,
info: {
name: 'VOD Flagged Series',
description: 'Lazy seasons',
movie_image: 'vod-series.jpg',
},
});
serialSeasonsResource.set([]);
vodSeriesSeasonsResource.set([]);
fixture.detectChanges();
await fixture.whenStable();
fixture.componentInstance.vodSeriesSeasons.set([
{
id: 'season-1',
video_id: '50001',
season_number: '1',
name: 'Season 1',
episodes: [
{
id: 'episode-1',
series_number: 1,
name: 'Pilot',
},
],
isLoading: false,
isExpanded: false,
},
]);
const episode = fixture.componentInstance.mappedSeasons()['1'][0];
fixture.componentInstance.episodePlaybackPositions.set(
new Map([
[
Number(episode.id),
{
contentXtreamId: Number(episode.id),
contentType: 'episode',
seriesXtreamId: 50001,
positionSeconds: 5,
durationSeconds: 100,
},
],
])
);
fixture.detectChanges();
const button: HTMLButtonElement | null =
fixture.nativeElement.querySelector(
'[data-testid="series-quick-start"]'
);
expect(button?.textContent).toContain('Play episode 1');
expect(button?.textContent).not.toContain('{{episode}}');
});
it('loads the first VOD-series season and starts its first episode from quick start', async () => {
selectedContentType.set('vod');
selectedItem.set({
@@ -350,6 +419,15 @@ describe('StalkerSeriesViewComponent', () => {
expect.any(Number),
undefined
);
expect(openResolvedPlayback).toHaveBeenCalledWith(
expect.objectContaining({
contentInfo: expect.objectContaining({
seasonNumber: 1,
episodeNumber: 1,
}),
}),
true
);
});
it('loads an earlier unloaded VOD-series season before showing completed', async () => {
@@ -541,6 +619,14 @@ describe('StalkerSeriesViewComponent', () => {
By.directive(StubPortalInlinePlayerComponent)
).componentInstance as StubPortalInlinePlayerComponent;
expect(inlinePlayer.playback()).toEqual(
expect.objectContaining({
contentInfo: expect.objectContaining({
seasonNumber: 1,
episodeNumber: 1,
}),
})
);
expect(inlinePlayer.episodeMetadata()).toEqual({
label: 'S01E01',
title: 'Pilot',
@@ -687,15 +687,37 @@ export class StalkerSeriesViewComponent implements OnDestroy {
episodeId,
startTime
);
const episodeState =
episodeId === undefined
? null
: resolveSeriesPlaybackEpisodeState({
episodesBySeason: this.mappedSeasons(),
currentEpisodeId: episodeId,
fallbackEpisodeNumber: episodeNum,
});
const resolvedPlayback =
episodeState && playback.contentInfo?.contentType === 'episode'
? {
...playback,
contentInfo: {
...playback.contentInfo,
seasonNumber: episodeState.seasonNumber,
episodeNumber: episodeState.episodeNumber,
},
}
: playback;
this.lastSaveTime = 0;
if (this.portalPlayer.isEmbeddedPlayer()) {
this.inlinePlayback.set(playback);
this.inlinePlayback.set(resolvedPlayback);
return;
}
this.closeInlinePlayer();
void this.portalPlayer.openResolvedPlayback(playback, true);
void this.portalPlayer.openResolvedPlayback(
resolvedPlayback,
true
);
} catch (error) {
this.logger.error('Failed to start inline series playback', error);
const errorMessage =