fix(epg): don't reset timeshift playback on the 30s now-tick

The video player's 30s "now" tick effect dispatched resetActiveEpgProgram
whenever no live programme was airing at the current clock time. That reducer
nulls activePlaybackUrl (shared with returnToLivePlayback), so a user watching
catch-up/timeshift on a channel with an EPG gap at the present moment was
silently kicked back to the live stream every 30 seconds. The reducer doesn't
touch currentEpgProgram (the live-programme display), so this else branch had no
legitimate effect — only the regression. Flagged by Greptile (the 4/5 blocker).

Guard the reset to fire only when not in catch-up (`!activePlaybackUrl()`), so an
in-progress archive playback survives EPG gaps while live playback still clears
stale state as before.

Tests: two video-player specs assert the now-tick leaves timeshift alone during a
gap but still resets when live. playlist-m3u-feature-player 32 green, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 4.8 committed 2026-07-01 20:41:23 +02:00
1 parent a4f3a5546f
commit 0d650de289
2 files changed
+38 -1

No files matched your search

@@ -929,6 +929,38 @@ describe('VideoPlayerComponent', () => {
})
);
});
it('does not reset active timeshift on the now-tick during an EPG gap', () => {
// In catch-up (activePlaybackUrl set) with programmes that don't cover
// "now", the 30s tick must NOT dispatch resetActiveEpgProgram — that
// reducer nulls activePlaybackUrl and would silently drop the user back
// to the live stream every 30 seconds.
syncStoreState(sampleChannel);
activePlaybackUrl.set('http://localhost/catchup.m3u8');
fixture.detectChanges();
storeMock.dispatch.mockClear();
epgPrograms$.next([buildProgram('Earlier')]); // no programme airing now
fixture.detectChanges();
expect(storeMock.dispatch).not.toHaveBeenCalledWith(
EpgActions.resetActiveEpgProgram()
);
});
it('still clears stale EPG state on the now-tick when playing live', () => {
syncStoreState(sampleChannel);
activePlaybackUrl.set(null); // live, not timeshift
fixture.detectChanges();
storeMock.dispatch.mockClear();
epgPrograms$.next([buildProgram('Earlier')]);
fixture.detectChanges();
expect(storeMock.dispatch).toHaveBeenCalledWith(
EpgActions.resetActiveEpgProgram()
);
});
});
function buildProgram(title: string): EpgProgram {
@@ -389,7 +389,12 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
this.store.dispatch(
EpgActions.setCurrentEpgProgram({ program: currentProgram })
);
} else {
} else if (!this.activePlaybackUrl()) {
// No live programme right now. Only clear stale EPG state when
// NOT in catch-up/timeshift: resetActiveEpgProgram also nulls
// activePlaybackUrl, so firing it on every 30s tick would knock
// the user out of an in-progress archive playback whenever the
// channel has an EPG gap at the current clock time.
this.store.dispatch(EpgActions.resetActiveEpgProgram());
}
});