From 0d650de289c00ca518a22b5ee73b118c7f369a9b Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 1 Jul 2026 20:41:23 +0200 Subject: [PATCH] fix(epg): don't reset timeshift playback on the 30s now-tick MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../video-player.component.spec.ts | 32 +++++++++++++++++++ .../video-player/video-player.component.ts | 7 +++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts index 9cf3bf8c7..bca91c1f5 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.spec.ts @@ -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 { diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts index be43235a5..edda8c7f4 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts @@ -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()); } });