mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(settings): reset a locked M3U channel however it becomes active
The enforcement service now checks the active M3U channel whenever it changes while locked, so numeric zapping, next/previous and remote commands, which select from the full channel list, cannot start a channel of a locked group. Numeric zapping also skips such a channel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
10cd201ebf
commit
4c64b798e1
4 files changed
+54
-1
No files matched your search
@@ -132,6 +132,31 @@ describe('ParentalLockEnforcementService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('resets a channel of a locked group as soon as it becomes active while locked', () => {
|
||||
// Numeric zapping, next/previous or a remote command can select a
|
||||
// channel without any lock-version change following it.
|
||||
router.url = '/workspace/playlists/m3u-1';
|
||||
parentalLock.active.set(true);
|
||||
parentalLock.isM3uGroupLocked.mockImplementation(
|
||||
(_playlistId: string, group: string) => group === 'Adult'
|
||||
);
|
||||
TestBed.runInInjectionContext(() =>
|
||||
(service as unknown as { start(): void }).start()
|
||||
);
|
||||
TestBed.flushEffects();
|
||||
dispatch.mockClear();
|
||||
|
||||
activeChannel.set({ group: { title: 'News' } });
|
||||
TestBed.flushEffects();
|
||||
expect(dispatch).not.toHaveBeenCalled();
|
||||
|
||||
activeChannel.set({ group: { title: 'Adult' } });
|
||||
TestBed.flushEffects();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
parentalLock.isM3uGroupLocked.mockReset();
|
||||
parentalLock.isM3uGroupLocked.mockReturnValue(false);
|
||||
});
|
||||
|
||||
describe('Stalker', () => {
|
||||
it('leaves the Stalker route when the Stalker step cannot load', async () => {
|
||||
router.url = '/workspace/stalker/stalker-1/itv';
|
||||
|
||||
@@ -65,6 +65,17 @@ export class ParentalLockEnforcementService {
|
||||
void this.loadStalker().catch(() => undefined);
|
||||
}
|
||||
});
|
||||
// Any path that makes a channel active (numeric zapping, next /
|
||||
// previous, remote commands, a stale list) is checked here: a
|
||||
// channel of a locked group is reset as soon as it becomes active.
|
||||
effect(() => {
|
||||
this.activeChannel();
|
||||
untracked(() => {
|
||||
if (this.parentalLock.active()) {
|
||||
this.applyM3u();
|
||||
}
|
||||
});
|
||||
});
|
||||
effect(() => {
|
||||
const version = this.parentalLock.version();
|
||||
untracked(() => {
|
||||
|
||||
@@ -191,7 +191,11 @@ on either side.
|
||||
surfaces (the M3U active channel, the Stalker selection) are stepped off
|
||||
BEFORE the Xtream reloads are awaited, so a playing M3U channel never
|
||||
waits behind a slow portal read — the Xtream store stays populated after
|
||||
leaving that portal, so its reload runs on every apply. Applies run one
|
||||
leaving that portal, so its reload runs on every apply. The M3U check
|
||||
also runs whenever the active channel changes while locked: numeric
|
||||
zapping, next/previous and remote commands select from the full channel
|
||||
list without any lock change following, so a channel of a locked group
|
||||
is reset as soon as it becomes active (numeric zapping also skips it). Applies run one
|
||||
at a time and each is
|
||||
abandoned once a newer `version` exists (the queued apply reads the latest
|
||||
state): `ElectronXtreamDataSource` shares in-flight category/content
|
||||
|
||||
@@ -114,6 +114,7 @@ import { createPlaybackSessionKey } from '@iptvnator/playback/util';
|
||||
import { ChannelListLoadingStateComponent } from '@iptvnator/ui/components';
|
||||
import {
|
||||
DataService,
|
||||
ParentalLockService,
|
||||
PlaylistsService,
|
||||
RecordingsService,
|
||||
RuntimeCapabilitiesService,
|
||||
@@ -241,6 +242,7 @@ export class VideoPlayerComponent
|
||||
private readonly router = inject(Router);
|
||||
private readonly runtime = inject(RuntimeCapabilitiesService);
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
private readonly parentalLock = inject(ParentalLockService);
|
||||
private readonly storage = inject(StorageMap);
|
||||
private readonly store = inject(Store);
|
||||
private readonly epgService = inject(EpgService);
|
||||
@@ -1437,8 +1439,19 @@ export class VideoPlayerComponent
|
||||
)
|
||||
)
|
||||
.subscribe((channel) => {
|
||||
// The number indexes the full list; a channel of a locked
|
||||
// group is not zapped to (the enforcement service would
|
||||
// reset it anyway, after playback had started).
|
||||
const playlistId = this.activePlaylistMeta()?._id;
|
||||
if (
|
||||
channel &&
|
||||
!(
|
||||
playlistId &&
|
||||
this.parentalLock.isM3uGroupLocked(
|
||||
playlistId,
|
||||
channel.group?.title ?? ''
|
||||
)
|
||||
) &&
|
||||
(!this.isLivePlayerFullscreen() ||
|
||||
this.keepsInlinePlayer(channel))
|
||||
) {
|
||||
|
||||
Reference in new issue
Block a user