mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(remote-control): cover live collections, honest volume, status resets (#1399)
* feat(remote-control): cover live collections, honest volume, status resets Remote control previously worked only on the three routed live layouts (M3U player, Xtream live, Stalker ITV); playing live TV from favorites, recently viewed, or the global collections left the mobile remote inert. - Wire channel up/down, number select, and status publishing into the unified live tab, covering per-portal and global favorites/recent for M3U, Xtream, and Stalker; navigation follows the search-filtered, sorted list exactly as rendered (shared deriveVisibleFavoriteChannels) - Treat non-live status updates as snapshots in the main process so stale now-playing fields are cleared instead of merged forever - Publish a reset snapshot from every integration on destroy, so leaving a live view clears the remote instead of freezing it - Report M3U supportsVolume only for built-in inline playback and no-op volume commands while MPV/VLC/Embedded MPV owns the audio - Publish live status for Stalker radio (same layout, same handlers) and fix its channel-number lookup for non-numeric radio ids Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmMT33wgK52QL6JAz468eH * fix(remote-control): review-loop hardening for status honesty - Make the non-live status update an authoritative reset in the main process: only portal survives, supportsVolume is forced false, stray now-playing fields from callers are dropped (Copilot review) - Stop Stalker radio status from leaking an unrelated TV channel's EPG: the ITV-keyed bulk cache survives itv->radio navigation and Ministra ids collide across the two lists, so EPG fields publish for itv only - Publish the reset snapshot when the M3U active channel clears in place (e.g. quitting external MPV), not only on route destroy - Consider a live external session in the M3U volume gate: a diagnostic-recovery MPV/VLC launch owns the audio even while a web player is configured; republish capability on session start/end - Share one REMOTE_CONTROL_RESET_STATUS constant across all four integrations instead of four hand-copied literals Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmMT33wgK52QL6JAz468eH * fix(remote-control): external session outranks DASH in M3U volume gate The managed clear-DASH MPV/VLC fallback (Shaka browser-support preflight failure) leaves activeChannelIsDash() true while the external session owns the audio, so the DASH shortcut bypassed the session check and kept advertising remote volume support. The live-session check now precedes the DASH branch; radio stays first because its inline audio element is always mounted and remains audible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmMT33wgK52QL6JAz468eH --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
9aeb83e515
commit
5b211faf73
16 files changed
+1321
-41
No files matched your search
@@ -236,6 +236,79 @@ describe('RemoteControlEvents HTTP endpoints', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('clears now-playing fields when a non-live snapshot arrives', async () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2026-07-25T10:00:00.000Z'));
|
||||
bootstrapRemoteControl();
|
||||
const updateStatus = getIpcListener('REMOTE_CONTROL_STATUS_UPDATE');
|
||||
|
||||
updateStatus(
|
||||
{},
|
||||
{
|
||||
portal: 'xtream',
|
||||
isLiveView: true,
|
||||
channelName: 'News',
|
||||
channelNumber: 4,
|
||||
epgTitle: 'Evening News',
|
||||
epgStart: '2026-07-25T09:30:00.000Z',
|
||||
epgEnd: '2026-07-25T10:30:00.000Z',
|
||||
supportsVolume: true,
|
||||
volume: 0.5,
|
||||
muted: false,
|
||||
}
|
||||
);
|
||||
// Browsing away from live (e.g. VOD) publishes a non-live snapshot;
|
||||
// it must replace the live state instead of merging into it.
|
||||
updateStatus(
|
||||
{},
|
||||
{ portal: 'xtream', isLiveView: false, supportsVolume: false }
|
||||
);
|
||||
|
||||
const result = await invokeHttpHandler(REMOTE_CONTROL_PATHS.STATUS, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
expect(result.response).toEqual({
|
||||
statusCode: 200,
|
||||
headers: JSON_HEADERS,
|
||||
body: JSON.stringify({
|
||||
portal: 'xtream',
|
||||
isLiveView: false,
|
||||
supportsVolume: false,
|
||||
updatedAt: '2026-07-25T10:00:00.000Z',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps only the portal on a non-live snapshot, dropping stray fields', async () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2026-07-25T10:00:00.000Z'));
|
||||
bootstrapRemoteControl();
|
||||
const updateStatus = getIpcListener('REMOTE_CONTROL_STATUS_UPDATE');
|
||||
|
||||
updateStatus(
|
||||
{},
|
||||
{ portal: 'stalker', isLiveView: true, channelName: 'ITV One' }
|
||||
);
|
||||
// Stray now-playing fields on a non-live update must be dropped too:
|
||||
// the reset is authoritative, not a merge base.
|
||||
updateStatus(
|
||||
{},
|
||||
{ isLiveView: false, channelName: 'Stray', supportsVolume: true }
|
||||
);
|
||||
|
||||
const result = await invokeHttpHandler(REMOTE_CONTROL_PATHS.STATUS, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
expect(JSON.parse(result.response.body)).toEqual({
|
||||
portal: 'stalker',
|
||||
isLiveView: false,
|
||||
supportsVolume: false,
|
||||
updatedAt: '2026-07-25T10:00:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
path: REMOTE_CONTROL_PATHS.CHANNEL_UP,
|
||||
|
||||
@@ -88,11 +88,28 @@ export class RemoteControlEvents {
|
||||
ipcMain.on(
|
||||
'REMOTE_CONTROL_STATUS_UPDATE',
|
||||
(_event, status: Partial<RemoteControlStatus>) => {
|
||||
this.remoteControlStatus = {
|
||||
...this.remoteControlStatus,
|
||||
...status,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
// A non-live update is an authoritative reset, not a patch:
|
||||
// merging it into the previous live state would keep stale
|
||||
// now-playing fields (channel name, EPG, volume) on the
|
||||
// remote forever after the player view is left or switched
|
||||
// to VOD. Only `portal` survives; every other field is
|
||||
// dropped even if a caller accidentally includes one, and a
|
||||
// non-live view never supports volume.
|
||||
this.remoteControlStatus =
|
||||
status.isLiveView === false
|
||||
? {
|
||||
portal:
|
||||
status.portal ??
|
||||
this.remoteControlStatus.portal,
|
||||
isLiveView: false,
|
||||
supportsVolume: false,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
: {
|
||||
...this.remoteControlStatus,
|
||||
...status,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user