Merge branch 'master' into perf/onpush-libs-workspace

This commit is contained in:
4gray authored and GitHub committed 2026-10-06 14:13:01 +02:00
commit 545dcf548f
58 files changed
+1033 -144

No files matched your search

@@ -1,10 +1,10 @@
/**
* Public Shaka error values audited against the locked 5.2.4 runtime.
* Public Shaka error values audited against the locked 5.2.12 runtime.
*
* Keep the version assertion in the contract spec: a Shaka upgrade must stop
* here for a new audit instead of silently accepting new error layouts.
*/
export const SHAKA_DIAGNOSTIC_VERSION = 'v5.2.4';
export const SHAKA_DIAGNOSTIC_VERSION = 'v5.2.12';
export const SHAKA_ERROR_SEVERITY = {
RECOVERABLE: 1,
@@ -123,7 +123,7 @@ export const SHAKA_ERROR_CODE = {
MISSING_EME_SUPPORT: 6020,
LOAD_INTERRUPTED: 7000,
} as const;
/** Shaka 5.2.4 public NetworkingEngine request types used by diagnostics. */
/** Shaka 5.2.12 public NetworkingEngine request types used by diagnostics. */
export const SHAKA_REQUEST_TYPE = {
MANIFEST: 0,
SEGMENT: 1,
@@ -65,7 +65,7 @@ describe('Shaka playback evidence', () => {
}
);
it('matches the installed public Shaka 5.2.4 error contract', () => {
it('matches the installed public Shaka 5.2.12 error contract', () => {
const installed = getInstalledShakaContract();
expect(installed.requestTypes).toEqual(
expect.objectContaining(SHAKA_REQUEST_TYPE)
@@ -51,7 +51,7 @@ export function createShakaPlaybackEvidence(
return httpStatus === undefined ? evidence : { ...evidence, httpStatus };
}
/** Public error.data request-type slots in Shaka 5.2.4; no URL inspection. */
/** Public error.data request-type slots in Shaka 5.2.12; no URL inspection. */
function getNetworkStage(error: Partial<ShakaErrorLike> | null | undefined) {
if (
error?.category !== SHAKA_ERROR_CATEGORY.NETWORK ||
@@ -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 {
@@ -231,8 +231,12 @@ describe('VideoPlayerComponent fullscreen channel panel + zapping', () => {
.overrideComponent(VideoPlayerComponent, {
set: {
imports: [],
template:
'<ng-template #fullscreenChannelPanel></ng-template>',
// The real template's channel-number overlay, so the
// OnPush timer test below can read the rendered state.
template: `<ng-template #fullscreenChannelPanel></ng-template>
@if (showChannelNumberOverlay()) {
<div class="channel-number-overlay">{{ channelNumberInput() }}</div>
}`,
},
})
.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]);
@@ -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()) {
<div class="channel-number-overlay">
<div class="channel-number-display">
{{ channelNumberInput }}
{{ channelNumberInput() }}
</div>
</div>
}
@@ -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<Settings> = {
readonly playerSettings = signal<Partial<Settings>>({
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 {
@@ -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 {
@@ -167,7 +167,7 @@ import { PlaybackHistoryConfirmation } from '../playback-history/playback-histor
</div>
`,
styleUrls: ['./audio-player.component.scss'],
changeDetection: ChangeDetectionStrategy.Eager,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
FormsModule,
MatButtonModule,
@@ -57,7 +57,7 @@ import { TranslateModule } from '@ngx-translate/core';
}
`,
],
changeDetection: ChangeDetectionStrategy.Eager,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
MatButtonModule,
MatCheckboxModule,
@@ -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 {
@@ -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,
@@ -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 {
@@ -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,
@@ -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 {