diff --git a/docs/architecture/zoneless-migration.md b/docs/architecture/zoneless-migration.md
index 84fdb1ede..63e70d481 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`
@@ -168,8 +168,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 |
@@ -188,7 +188,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..72af02b4e 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,33 @@ describe('VideoPlayerComponent fullscreen channel panel + zapping', () => {
fixture.destroy();
});
+ // OnPush: the overlay hides from a 2 s timer, outside any template
+ // event, so the signal write itself must schedule the render. The test
+ // never forces one after the timer: a plain-field write would leave the
+ // overlay in the DOM.
+ it('hides the channel-number overlay when its debounce fires', async () => {
+ jest.useFakeTimers();
+ try {
+ const overlay = () =>
+ (fixture.nativeElement as HTMLElement).querySelector(
+ '.channel-number-overlay'
+ );
+ fixture.autoDetectChanges();
+ component.handleChannelNumberInput('2');
+ await jest.advanceTimersByTimeAsync(50);
+ expect(overlay()?.textContent).toBe('2');
+
+ await jest.advanceTimersByTimeAsync(2000);
+
+ 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 +291,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 {