refactor: improve archive playback support in video player and unify EPG display across components

This commit is contained in:
4gray committed 2026-04-10 07:54:24 +02:00
1 parent 4005687f18
commit 9c0d49a1dc
6 files changed
+67 -5

No files matched your search

@@ -27,6 +27,7 @@
<div class="content-container">
@if (activeChannel(); as activeChannel) {
@let playbackTarget = playbackChannel();
@if (activeChannel.radio === 'true' || shouldShowInlinePlayer(activeChannel)) {
<div class="video-player">
@if (activeChannel.radio === 'true') {
@@ -42,8 +43,8 @@
sources: [
{
src:
activeChannel.url +
activeChannel.epgParams,
playbackTarget?.url +
playbackTarget?.epgParams,
type: 'application/x-mpegURL',
},
],
@@ -54,14 +55,14 @@
}
@if (playerSettings.player === 'html5') {
<app-html-video-player
[channel]="activeChannel"
[channel]="playbackTarget!"
[volume]="volume"
[showCaptions]="playerSettings.showCaptions!"
/>
}
@if (playerSettings.player === 'artplayer') {
<app-art-player
[channel]="activeChannel"
[channel]="playbackTarget!"
[volume]="volume"
[showCaptions]="playerSettings.showCaptions!"
/>
@@ -73,7 +74,9 @@
@if (activeChannel.radio !== 'true') {
<div class="epg">
<div class="epg-content">
<app-epg-list />
<app-epg-list
[archivePlaybackAvailable]="archivePlaybackAvailable()"
/>
</div>
</div>
}
@@ -10,6 +10,7 @@ import { BehaviorSubject, of } from 'rxjs';
import {
ChannelActions,
selectActive,
selectActivePlaybackUrl,
selectChannels,
selectCurrentEpgProgram,
} from 'm3u-state';
@@ -34,6 +35,7 @@ describe('VideoPlayerComponent', () => {
const playlistId = signal('playlist-1');
const activeChannel = signal<Channel | null>(null);
const activePlaybackUrl = signal<string | null>(null);
const channels = signal<Channel[]>([]);
const currentEpgProgram = signal(null);
@@ -66,6 +68,8 @@ describe('VideoPlayerComponent', () => {
switch (selector) {
case selectActive:
return activeChannel;
case selectActivePlaybackUrl:
return activePlaybackUrl;
case selectChannels:
return channels;
case selectCurrentEpgProgram:
@@ -143,6 +147,7 @@ describe('VideoPlayerComponent', () => {
localStorage.removeItem('m3u-sidebar-width');
player.set(VideoPlayer.VideoJs);
showCaptions.set(false);
activePlaybackUrl.set(null);
currentEpgProgram.set(null);
currentEpgProgram$.next(null);
overlayMock.create.mockClear();
@@ -286,6 +291,25 @@ describe('VideoPlayerComponent', () => {
).not.toBeNull();
});
it('uses the active playback override url when archive playback is active', () => {
syncStoreState(sampleChannel);
player.set(VideoPlayer.VideoJs);
activePlaybackUrl.set(
'http://localhost/archive.m3u8?utc=123&lutc=456'
);
fixture.detectChanges();
expect(component.playbackChannel()?.url).toBe(
'http://localhost/archive.m3u8?utc=123&lutc=456'
);
activePlaybackUrl.set(null);
fixture.detectChanges();
expect(component.playbackChannel()?.url).toBe(sampleChannel.url);
});
it('updates the outer sidebar width while grouped view requests a larger total width', () => {
fixture.detectChanges();
@@ -18,6 +18,7 @@ import { Store } from '@ngrx/store';
import { StorageMap } from '@ngx-pwa/local-storage';
import { TranslatePipe } from '@ngx-translate/core';
import { ResizableDirective } from 'components';
import { isM3uCatchupPlaybackSupported } from 'm3u-utils';
import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util';
import {
COMPONENT_OVERLAY_REF,
@@ -28,6 +29,7 @@ import {
ChannelActions,
PlaylistActions,
selectActive,
selectActivePlaybackUrl,
selectChannels,
selectCurrentEpgProgram,
} from 'm3u-state';
@@ -114,8 +116,31 @@ export class VideoPlayerComponent implements OnInit, OnDestroy {
/** Active selected channel */
readonly activeChannel = this.store.selectSignal(selectActive);
readonly activePlaybackUrl = this.store.selectSignal(
selectActivePlaybackUrl
);
readonly activePlaylistId = this.playlistContext.resolvedPlaylistId;
readonly channels = this.store.selectSignal(selectChannels);
readonly archivePlaybackAvailable = computed(() =>
isM3uCatchupPlaybackSupported(this.activeChannel())
);
readonly playbackChannel = computed<Channel | null>(() => {
const activeChannel = this.activeChannel();
if (!activeChannel) {
return null;
}
const playbackUrl = this.activePlaybackUrl();
if (!playbackUrl) {
return activeChannel;
}
return {
...activeChannel,
url: playbackUrl,
epgParams: '',
} as Channel;
});
readonly sidebarStorageKey = computed(() =>
this.activeView() === 'groups'
? M3U_GROUPS_SIDEBAR_STORAGE_KEY
@@ -112,6 +112,9 @@
<app-epg-list
[controlledChannel]="currentM3uChannel()"
[controlledPrograms]="currentM3uPrograms()"
[archivePlaybackAvailable]="
currentM3uArchivePlaybackAvailable()
"
/>
} @else {
<app-epg-view [epgItems]="currentPortalEpgItems()" />
@@ -12,6 +12,7 @@ import {
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { isM3uCatchupPlaybackSupported } from 'm3u-utils';
import {
matchesOpenLiveCollectionItem,
OpenLiveCollectionItemState,
@@ -106,6 +107,9 @@ export class UnifiedLiveTabComponent {
return detail.channel ?? null;
});
readonly currentM3uArchivePlaybackAvailable = computed(() =>
isM3uCatchupPlaybackSupported(this.currentM3uChannel())
);
readonly activeRadioChannel = computed(() => {
const channel = this.currentM3uChannel();
return channel?.radio === 'true' ? channel : null;
@@ -107,6 +107,9 @@
<app-epg-list
[controlledPrograms]="epgPrograms"
[controlledArchiveDays]="controlledArchiveDays()"
[archivePlaybackAvailable]="
archivePlaybackAvailable()
"
(programActivated)="onProgramActivated($event)"
/>
} @else if (!isElectron && epgItems().length > 0) {