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> {
+6 -1
View File
@@ -151,7 +151,12 @@ on either side.
"All", recently added or search has no selected category to vanish
with, so its row disappearing from a list is not enough — the item is
cleared and the route returns to the section root while the (still
readable) selected category stays. Applies run one at a time and each is
readable) selected category stays. Within one apply the synchronous
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
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
reads per playlist and type, and its share key carries the lock version,