fix(settings): step off the M3U channel and Stalker selection before awaiting the Xtream relock reload

The Xtream store stays populated after leaving that portal, so its reload
runs on every apply; a locked M3U channel no longer keeps playing behind a
slow database or provider read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 committed 2026-09-26 10:28:12 +02:00
1 parent 5c141b24b8
commit d4d0754e12
3 files changed
+43 -9

No files matched your search

@@ -9,12 +9,15 @@ import { ParentalLockEnforcementService } from './parental-lock-enforcement.serv
import { PlaybackKeepAwakeService } from './playback-keep-awake.service';
interface Applier {
apply(): Promise<void>;
applyXtream(version: number): Promise<void>;
applyStalker(): Promise<void>;
}
describe('ParentalLockEnforcementService', () => {
const router = { url: '/', navigate: jest.fn() };
const activeChannel = signal<{ group?: { title: string } } | null>(null);
const dispatch = jest.fn();
const lockedStalkerIds = new Set<string>();
const parentalLock = {
version: signal(0),
@@ -76,10 +79,7 @@ describe('ParentalLockEnforcementService', () => {
{ provide: Router, useValue: router },
{
provide: Store,
useValue: {
selectSignal: () => signal(null),
dispatch: jest.fn(),
},
useValue: { selectSignal: () => activeChannel, dispatch },
},
{
provide: PlaybackKeepAwakeService,
@@ -87,11 +87,38 @@ describe('ParentalLockEnforcementService', () => {
},
],
});
activeChannel.set(null);
service = TestBed.inject(
ParentalLockEnforcementService
) as unknown as Applier;
});
describe('M3U', () => {
it('resets a locked playing channel before awaiting the portal reloads', async () => {
router.url = '/workspace/playlists/m3u-1';
activeChannel.set({ group: { title: 'Adult' } });
parentalLock.isM3uGroupLocked.mockReturnValue(true);
let releaseReload: () => void = () => undefined;
xtreamStore.reloadCategories.mockImplementationOnce(
() => new Promise<void>((resolve) => (releaseReload = resolve))
);
const applying = service.apply();
await Promise.resolve();
expect(parentalLock.isM3uGroupLocked).toHaveBeenCalledWith(
'm3u-1',
'Adult'
);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(xtreamStore.reloadCategories).toHaveBeenCalledTimes(1);
releaseReload();
await applying;
parentalLock.isM3uGroupLocked.mockReturnValue(false);
});
});
describe('Stalker', () => {
it('clears a detail opened from All whose own genre is withheld', async () => {
router.url = '/workspace/stalker/stalker-1/vod/42';
@@ -79,11 +79,13 @@ export class ParentalLockEnforcementService {
private async apply(): Promise<void> {
const version = this.parentalLock.version();
await Promise.all([this.applyXtream(version), this.applyStalker()]);
if (this.parentalLock.version() !== version) {
return;
}
// The synchronous surfaces first: an M3U channel or a Stalker
// selection must not keep playing behind a slow Xtream database or
// provider reload — the Xtream store stays populated after leaving
// that portal, so its reload runs on every apply.
this.applyM3u();
await this.applyStalker();
await this.applyXtream(version);
}
private async applyXtream(version: number): Promise<void> {