diff --git a/docs/architecture/zoneless-migration.md b/docs/architecture/zoneless-migration.md index 630d51ff2..a0b7bd251 100644 --- a/docs/architecture/zoneless-migration.md +++ b/docs/architecture/zoneless-migration.md @@ -97,14 +97,14 @@ files that still contain `ChangeDetectionStrategy.Eager`. - [x] `libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts` (idle audit root; also `EpgTrustConfirmDialogComponent`) - [x] `libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts` - [ ] `libs/ui/remote-control/src/lib/remote-control/remote-control.component.ts` (`apps/remote-control-web` only) -- [ ] `libs/ui/playback/src/lib/art-player/art-player.component.ts` -- [ ] `libs/ui/playback/src/lib/audio-player/audio-player.component.ts` -- [ ] `libs/ui/playback/src/lib/external-player-info-dialog/external-player-info-dialog.component.ts` -- [ ] `libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts` -- [ ] `libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts` -- [ ] `libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts` -- [ ] `libs/ui/playback/src/lib/vod-details/vod-details.component.ts` -- [ ] `libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts` +- [x] `libs/ui/playback/src/lib/art-player/art-player.component.ts` +- [x] `libs/ui/playback/src/lib/audio-player/audio-player.component.ts` +- [x] `libs/ui/playback/src/lib/external-player-info-dialog/external-player-info-dialog.component.ts` +- [x] `libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts` +- [x] `libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts` +- [x] `libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts` +- [x] `libs/ui/playback/src/lib/vod-details/vod-details.component.ts` +- [x] `libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts` `libs/ui/playback` (8) goes with the playback PR, not the `libs/ui` one. @@ -131,8 +131,8 @@ files that still contain `ChangeDetectionStrategy.Eager`. - [ ] `libs/playlist/import/feature/src/lib/text-import/text-import.component.ts` - [ ] `libs/playlist/import/feature/src/lib/url-upload/url-upload.component.ts` - [ ] `libs/playlist/import/feature/src/lib/xtream-code-import/xtream-code-import.component.ts` -- [ ] `libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.ts` -- [ ] `libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts` +- [x] `libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.ts` +- [x] `libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts` - [ ] `libs/playlist/shared/ui/src/lib/recent-playlists/empty-state/empty-state.component.ts` - [ ] `libs/playlist/shared/ui/src/lib/recent-playlists/playlist-info/playlist-info.component.ts` - [ ] `libs/playlist/shared/ui/src/lib/recent-playlists/playlist-item/playlist-item.component.ts` @@ -165,8 +165,8 @@ the field a signal (or a `computed`), or writes it through one. | Done | Site | What depends on the zone | Owning PR | | --- | --- | --- | --- | -| [ ] | `libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts` `onChannelNumberInput`/`clearChannelNumberInput` | 2 s `window.setTimeout` hides the channel-number overlay through plain `showChannelNumberOverlay`/`channelNumberInput` | playback | -| [ ] | same file, `applySettings` and the settings `effect()` | IndexedDB `storage.get(...).subscribe` and an effect assign plain `playerSettings`, which picks the player in the template | playback | +| [x] | `libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts` `onChannelNumberInput`/`clearChannelNumberInput` | 2 s `window.setTimeout` hides the channel-number overlay through plain `showChannelNumberOverlay`/`channelNumberInput` | playback | +| [x] | same file, `applySettings` and the settings `effect()` | IndexedDB `storage.get(...).subscribe` and an effect assign plain `playerSettings`, which picks the player in the template | playback | | [ ] | `libs/playlist/shared/ui/src/lib/recent-playlists/playlist-item/playlist-item.component.ts` `checkPortalStatus` | plain `portalStatus` assigned after `await` in `ngOnInit` (PWA only: skipped when source health is supported) | playlist | | [ ] | `libs/playlist/shared/ui/src/lib/recent-playlists/playlist-info/playlist-info.component.ts` (EPG clear and EPG file pick handlers) | plain `playlist` reassigned after `await` | playlist | | [ ] | `libs/playlist/import/feature/src/lib/stalker-portal-import/stalker-portal-import.component.ts` (device-id derivation) | `form.patchValue` after `await`; template getters read `control.value`, which is not signal-backed | playlist | @@ -185,7 +185,7 @@ before: with zone.js on they still matter. - [ ] `apps/web/src/app/settings/settings-unload-guard.service.ts`: two `zone.run` calls around the window-close dialog (IPC `onWindowCloseRequested` and `beforeunload`). -- [ ] `libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts`: +- [x] `libs/ui/playback/src/lib/embedded-mpv-player/embedded-mpv-session-controller.ts`: `runOutsideAngular(() => setInterval(...))` for the position poll; `embedded-mpv-session-controller.position.spec.ts` asserts the call and changes with it. diff --git a/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.ts b/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.ts index cd5b6adb8..c5d7fcb18 100644 --- a/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.ts +++ b/libs/playlist/m3u/feature-player/src/lib/m3u-vod-detail/m3u-vod-detail.component.ts @@ -71,7 +71,7 @@ import { TranslatePipe, ], templateUrl: './m3u-vod-detail.component.html', - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./m3u-vod-detail.component.scss'], }) export class M3uVodDetailComponent { diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts index d8ebfef71..97805fe02 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.fullscreen-panel.spec.ts @@ -231,8 +231,12 @@ describe('VideoPlayerComponent fullscreen channel panel + zapping', () => { .overrideComponent(VideoPlayerComponent, { set: { imports: [], - template: - '', + // The real template's channel-number overlay, so the + // OnPush timer test below can read the rendered state. + template: ` + @if (showChannelNumberOverlay()) { +
{{ channelNumberInput() }}
+ }`, }, }) .compileComponents(); @@ -247,6 +251,31 @@ describe('VideoPlayerComponent fullscreen channel panel + zapping', () => { fixture.destroy(); }); + // OnPush: the overlay hides from a 2 s timer, outside any template + // event, so it must leave the DOM without a zone-triggered tick. + it('hides the channel-number overlay when its debounce fires', () => { + jest.useFakeTimers(); + try { + const overlay = () => + (fixture.nativeElement as HTMLElement).querySelector( + '.channel-number-overlay' + ); + component.handleChannelNumberInput('2'); + fixture.detectChanges(); + expect(overlay()?.textContent).toBe('2'); + + jest.advanceTimersByTime(2000); + fixture.detectChanges(); + + expect(overlay()).toBeNull(); + expect(storeMock.dispatch).toHaveBeenCalledWith( + setActiveChannelDispatch(nextChannel) + ); + } finally { + jest.useRealTimers(); + } + }); + describe('FULLSCREEN_CHANNEL_PANEL host', () => { it.each([VideoPlayer.MPV, VideoPlayer.VLC])( 'withholds rows that would leave forced-inline DASH for %s', @@ -260,7 +289,7 @@ describe('VideoPlayerComponent fullscreen channel panel + zapping', () => { url: 'http://localhost/next.mpd', }; player.set(externalPlayer); - component.playerSettings.player = externalPlayer; + component.playerSettings.set({ player: externalPlayer }); setActive(dashChannel); channels.set([dashChannel, sampleChannel, nextDashChannel]); diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html index 481352fc8..274f29958 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html @@ -85,7 +85,7 @@ [playbackSessionKey]="playbackSessionKey()" [inlinePlayerAvailable]="shouldShowInlinePlayer(activeChannel)" [volume]="volume()" - [playerOverride]="playerSettings.player ?? null" + [playerOverride]="playerSettings().player ?? null" (playbackStarted)="refreshVolumeFromBus()" (externalFallbackRequested)=" handleExternalFallbackRequest($event) @@ -124,7 +124,7 @@ [playerOverride]=" activeChannelIsDash() ? dashPlayerOverride() - : (playerSettings.player ?? null) + : (playerSettings().player ?? null) " [volume]="volume()" [timelineSegments]="catchupTimelineSegments()" @@ -255,10 +255,10 @@ } } - @if (showChannelNumberOverlay) { + @if (showChannelNumberOverlay()) {
- {{ channelNumberInput }} + {{ channelNumberInput() }}
} 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 e10c4f119..592f5bd55 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 @@ -231,7 +231,7 @@ function isInsideScrollableRegion( { provide: EPG_GUIDE_SOURCE, useExisting: M3uEpgGuideSourceService }, ], templateUrl: './video-player.component.html', - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrl: './video-player.component.scss', }) export class VideoPlayerComponent @@ -713,9 +713,9 @@ export class VideoPlayerComponent ); /** Selected video player options */ - playerSettings: Partial = { + readonly playerSettings = signal>({ player: VideoPlayer.VideoJs, - }; + }); readonly isDesktop = this.runtime.isElectron; readonly supportsEpg = this.runtime.supportsEpg; @@ -731,8 +731,8 @@ export class VideoPlayerComponent ); /** Channel number input state */ - channelNumberInput = ''; - showChannelNumberOverlay = false; + readonly channelNumberInput = signal(''); + readonly showChannelNumberOverlay = signal(false); private channelNumberTimeout?: number; /** @@ -795,9 +795,9 @@ export class VideoPlayerComponent // React to settings changes effect(() => { - this.playerSettings = { + this.playerSettings.set({ player: this.settingsStore.player(), - }; + }); }); // Keep "now" fresh so EPG state re-evaluates over time. @@ -1191,10 +1191,10 @@ export class VideoPlayerComponent applySettings(): void { this.storage.get(STORE_KEY.Settings).subscribe((settings: unknown) => { if (settings && Object.keys(settings as Settings).length > 0) { - this.playerSettings = { + this.playerSettings.set({ player: (settings as Settings).player || VideoPlayer.VideoJs, - }; + }); } }); } @@ -1472,12 +1472,14 @@ export class VideoPlayerComponent } // Add digit to current input - this.channelNumberInput += digit; - this.showChannelNumberOverlay = true; + this.channelNumberInput.update((input) => input + digit); + this.showChannelNumberOverlay.set(true); // Set timeout to switch channel after 2 seconds of no input this.channelNumberTimeout = window.setTimeout(() => { - this.switchToChannelByNumber(parseInt(this.channelNumberInput, 10)); + this.switchToChannelByNumber( + parseInt(this.channelNumberInput(), 10) + ); this.clearChannelNumberInput(); }, 2000); } @@ -1547,8 +1549,8 @@ export class VideoPlayerComponent * Clear channel number input and hide overlay */ clearChannelNumberInput(): void { - this.channelNumberInput = ''; - this.showChannelNumberOverlay = false; + this.channelNumberInput.set(''); + this.showChannelNumberOverlay.set(false); if (this.channelNumberTimeout) { clearTimeout(this.channelNumberTimeout); this.channelNumberTimeout = undefined; @@ -1663,7 +1665,7 @@ export class VideoPlayerComponent return true; } - const player = this.playerSettings.player; + const player = this.playerSettings().player; return ( !this.isExternalPlayer(player) && player !== VideoPlayer.EmbeddedMpv ); @@ -1685,7 +1687,7 @@ export class VideoPlayerComponent return true; } - return !this.isExternalPlayer(this.playerSettings.player); + return !this.isExternalPlayer(this.playerSettings().player); } handleExternalFallbackRequest(request: PlaybackFallbackRequest): void { diff --git a/libs/ui/playback/src/lib/art-player/art-player.component.ts b/libs/ui/playback/src/lib/art-player/art-player.component.ts index 208b36c4f..5710777a8 100644 --- a/libs/ui/playback/src/lib/art-player/art-player.component.ts +++ b/libs/ui/playback/src/lib/art-player/art-player.component.ts @@ -51,7 +51,7 @@ Artplayer.AUTO_PLAYBACK_TIMEOUT = 10000; ], providers: [WebVideoControlsAdapter], templateUrl: './art-player.component.html', - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./art-player.component.scss'], }) export class ArtPlayerComponent implements OnInit, OnDestroy, OnChanges { diff --git a/libs/ui/playback/src/lib/audio-player/audio-player.component.ts b/libs/ui/playback/src/lib/audio-player/audio-player.component.ts index 34469d51a..b8464d100 100644 --- a/libs/ui/playback/src/lib/audio-player/audio-player.component.ts +++ b/libs/ui/playback/src/lib/audio-player/audio-player.component.ts @@ -167,7 +167,7 @@ import { PlaybackHistoryConfirmation } from '../playback-history/playback-histor `, styleUrls: ['./audio-player.component.scss'], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ FormsModule, MatButtonModule, diff --git a/libs/ui/playback/src/lib/external-player-info-dialog/external-player-info-dialog.component.ts b/libs/ui/playback/src/lib/external-player-info-dialog/external-player-info-dialog.component.ts index f9e3ab956..415a2ab28 100644 --- a/libs/ui/playback/src/lib/external-player-info-dialog/external-player-info-dialog.component.ts +++ b/libs/ui/playback/src/lib/external-player-info-dialog/external-player-info-dialog.component.ts @@ -57,7 +57,7 @@ import { TranslateModule } from '@ngx-translate/core'; } `, ], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ MatButtonModule, MatCheckboxModule, diff --git a/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts b/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts index a0384a293..924e72e07 100644 --- a/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts +++ b/libs/ui/playback/src/lib/html-video-player/html-video-player.component.ts @@ -71,7 +71,7 @@ const debugHtmlPlayer = createDevLogger('HtmlVideoPlayer'); SeriesPlaybackNavigationControlsComponent, ], providers: [WebVideoControlsAdapter], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, }) export class HtmlVideoPlayerComponent implements OnInit, OnChanges, OnDestroy { diff --git a/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts b/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts index 3e2cc0cfe..3f97a4dbe 100644 --- a/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts +++ b/libs/ui/playback/src/lib/video-player/sidebar/sidebar.component.ts @@ -20,7 +20,7 @@ import { ChannelListContainerComponent } from '@iptvnator/ui/components'; selector: 'app-sidebar', templateUrl: './sidebar.component.html', styleUrls: ['./sidebar.component.scss'], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ ChannelListContainerComponent, MatIcon, diff --git a/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts b/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts index 6770f466e..2996a44cc 100644 --- a/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts +++ b/libs/ui/playback/src/lib/vjs-player/vjs-player.component.ts @@ -66,7 +66,7 @@ const debugVjsPlayer = createDevLogger('VjsPlayer'); SeriesPlaybackNavigationControlsComponent, ], providers: [WebVideoControlsAdapter], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, }) export class VjsPlayerComponent implements OnInit, OnChanges, OnDestroy { diff --git a/libs/ui/playback/src/lib/vod-details/vod-details.component.ts b/libs/ui/playback/src/lib/vod-details/vod-details.component.ts index 4d3b658e2..9e4e4f49f 100644 --- a/libs/ui/playback/src/lib/vod-details/vod-details.component.ts +++ b/libs/ui/playback/src/lib/vod-details/vod-details.component.ts @@ -82,7 +82,7 @@ import { createVodSimilarInPortals } from './vod-similar-in-portals.state'; selector: 'app-vod-details', templateUrl: './vod-details.component.html', styleUrls: ['../styles/detail-view.scss'], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CastCrewRowComponent, DetailActionButtonComponent, diff --git a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts index a1c55b8f9..ab0ee9aff 100644 --- a/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts +++ b/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts @@ -91,7 +91,7 @@ import { resolveWebPlayerSharedControls } from './web-player-shared-controls'; useFactory: resolveWebPlayerSharedControls, }, ], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class WebPlayerViewComponent implements OnDestroy {