settings.component.ts had grown to 819 lines — past the CLAUDE.md target (<300) and hard maximum, passing lint only because it sat in the max-lines baseline. The behaviour moves into facades the template binds to directly, following the precedent already in this folder: new app-update (218), form (197), epg (123), embedded-mpv (74) and remote-control (37) facades, with playlist-reset extended to 143 and settings-options to 200. The component is now a 259-line coordinator holding capability flags, section nav, players() and the cross-facade flows. settings.component.ts is removed from the max-lines baseline. No behaviour change. One ordering detail: applyChangedSettings now applies language/theme before kicking off the EPG re-fetch; changeTheme only touches DOM theme sync and translate.use does not touch the form, so the two are independent.
10 KiB
Remote Control Architecture
This document describes the current remote control implementation in IPTVnator, including:
- HTTP API exposed by Electron main process
- IPC bridge between Electron main and Angular renderer
- Feature support and integration points for M3U, Xtream, and Stalker
- Remote web UI structure and behavior
Related architecture docs:
Scope
Remote control is a desktop-only feature that serves a mobile-friendly web app from the Electron backend and routes remote actions into the running renderer.
Current capabilities:
- Channel up / down
- Channel select by number
- Volume commands (implemented in command layer; active support currently in M3U flow)
- Playback status polling (portal, live-state, channel name/number, EPG now, volume capability)
High-Level Flow
- User opens remote web UI (
http://<local-ip>:<port>). - Remote web app calls
/api/remote-control/*. - Electron main handles API request and sends IPC to renderer:
CHANNEL_CHANGEfor up/downREMOTE_CONTROL_COMMANDfor numeric/volume commands
- Renderer-specific feature module (M3U/Xtream/Stalker) applies action.
- Renderer pushes status snapshots back to main via:
REMOTE_CONTROL_STATUS_UPDATE
- Remote web app polls
/api/remote-control/statusand updates UI.
Backend (Electron Main)
HTTP server and static app hosting
- File:
apps/electron-backend/src/app/server/http-server.ts - Responsibilities:
- Serves static remote app from:
- dev:
dist/apps/remote-control-web/browser - prod:
<appPath>/remote-control-web/browser
- dev:
- Keeps every non-API request inside that configured static root: the request
pathname is decoded once, malformed encoding and NUL bytes fail closed,
and the platform-specific resolved path must remain the root or its
descendant. Angular route fallback may serve only that root's
index.html; it must never bypass the containment check. - Routes
/api/remote-control/*to registered handlers. - Starts/stops/restarts on settings updates.
- Serves static remote app from:
Remote control event module
- File:
apps/electron-backend/src/app/events/remote-control.events.ts - Bootstrapped in:
apps/electron-backend/src/main.tsviaRemoteControlEvents.bootstrapRemoteControlEvents()
Registered endpoints:
POST /api/remote-control/channel/upPOST /api/remote-control/channel/downPOST /api/remote-control/channel/select-numberwith{ number: <int> }POST /api/remote-control/volume/upPOST /api/remote-control/volume/downPOST /api/remote-control/volume/toggle-muteGET /api/remote-control/status
IPC emitted to renderer:
CHANNEL_CHANGEpayload:{ direction: 'up' | 'down' }REMOTE_CONTROL_COMMANDpayload:{ type: 'channel-select-number', number }{ type: 'volume-up' | 'volume-down' | 'volume-toggle-mute' }
Status ingestion from renderer:
- Listens on
REMOTE_CONTROL_STATUS_UPDATE - Maintains in-memory
RemoteControlStatusobject returned by/status
Settings integration
- Main handler:
apps/electron-backend/src/app/events/settings.events.ts - On
SETTINGS_UPDATE, readsremoteControlandremoteControlPort, persists to store, and calls:httpServer.updateSettings(enabled, port)
Preload Bridge
- File:
apps/electron-backend/src/app/api/main.preload.ts
Exposed APIs relevant to remote control:
onChannelChange(callback) => unsubscribeonRemoteControlCommand(callback) => unsubscribeupdateRemoteControlStatus(status) => void
Type definitions:
apps/web/src/typings.d.tsglobal.d.ts
Renderer Integrations
All renderer integrations resolve the Electron remote-control bridge through
RuntimeCapabilitiesService.supportsRemoteControl. A partial Electron bridge
is treated as unsupported unless it exposes all remote-control methods:
updateRemoteControlStatus, onChannelChange, and
onRemoteControlCommand. This keeps PWA/self-hosted builds and partial test
bridges from accidentally activating desktop-only remote-control behavior.
Shared helpers
- File:
libs/portal/shared/util/src/lib/remote-channel-navigation.ts
Functions:
getAdjacentChannelItem(...): wraps around on boundaries for up/downgetChannelItemByNumber(...): 1-based number to list item mapping
Used by M3U, Xtream, and Stalker live integrations.
M3U integration
- File:
libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.ts
Implemented behavior:
- Subscribes to:
onChannelChange(up/down)onRemoteControlCommand(number + volume)
- Applies channel up/down by active channel URL over
channels$ - Applies number select through existing
switchToChannelByNumber(...) - Dispatches remote channel changes as explicit playback requests so MPV/VLC starts immediately even when mouse channel rows require double-click before external playback.
- Applies volume commands:
- up/down in 0.1 increments
- toggle mute with last non-zero volume restore
- persists to
localStorage - propagates to built-in inline players: Video.js, HTML5, ArtPlayer, and radio
AudioPlayerComponent - does not control external MPV/VLC sessions or the experimental Embedded MPV player
- Publishes status snapshots via
updateRemoteControlStatus(...):portal: 'm3u'isLiveView: true- channel name/number
- EPG now fields
supportsVolume: true,volume,muted
- Cleans listeners/subscriptions in
ngOnDestroy.
Xtream integration (live view)
- File:
libs/portal/xtream/feature/src/lib/live-stream-layout/live-stream-layout.component.ts
Implemented behavior:
- Subscribes to:
onChannelChangefor up/downonRemoteControlCommandfor number select
- Up/down:
- Uses selected live item
selectedItem().xtream_id - Navigates inside
selectItemsFromSelectedCategory() - Calls
playLive(nextItem, true)so remote actions explicitly start playback
- Uses selected live item
- Number select:
- Maps number to item in current category list
- Calls
playLive(channel, true)so remote actions explicitly start playback
- Publishes status via effect:
portal: 'xtream'isLiveViewonly when selected content type isliveand item is selected- channel name/number + current EPG item
supportsVolume: false
- Cleans listeners in
ngOnDestroy.
Stalker integration (ITV live view)
- File:
libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts
Implemented behavior:
- Subscribes to:
onChannelChangefor up/downonRemoteControlCommandfor number select
- Up/down:
- Uses
selectedItem().id - Navigates inside
itvChannels() - Calls
playChannel(nextItem, true)so remote actions explicitly start playback
- Uses
- Number select:
- Maps number into
itvChannels() - Calls
playChannel(channel, true)so remote actions explicitly start playback
- Maps number into
- Publishes status via effect:
portal: 'stalker'isLiveViewonly for selected content typeitvwith active item- channel name/number + current EPG item
supportsVolume: false
- Cleans listeners in
ngOnDestroy.
Remote Web App
App shell
- App:
apps/remote-control-web/src/app/app.ts - Template:
apps/remote-control-web/src/app/app.html - Style:
apps/remote-control-web/src/app/app.scss - Renders shared library component:
<lib-remote-control />
Shared remote UI library
- Component:
libs/ui/remote-control/src/lib/remote-control/remote-control.component.tslibs/ui/remote-control/src/lib/remote-control/remote-control.component.htmllibs/ui/remote-control/src/lib/remote-control/remote-control.component.scss
- Service:
libs/ui/remote-control/src/lib/remote-control/remote-control.service.ts
Implemented UI behavior:
- Channel pad (
CH+,CH-) - Numeric keypad (
0-9,DEL,CLR,OK) - Volume controls (
VOL-,MUTE/UNMUTE,VOL+) - Status card (portal, channel name/number, current program)
- Polls
/statusevery 2s - Uses action wrapper to refresh status after command execution
Settings UI and discoverability
- Files:
apps/web/src/app/settings/settings-remote-control-section.component.ts(+.html) — the section rendered bysettings.component.htmlapps/web/src/app/settings/settings-remote-control.facade.ts— LAN address lookup and QR-code visibility state
- Features:
- Toggle
remoteControl - Configure
remoteControlPort - Display local URLs and QR codes for remote access
- Local IP list loaded via
getLocalIpAddresses()
- Toggle
Feature Matrix (Current)
| Capability | M3U | Xtream Live | Stalker ITV |
|---|---|---|---|
| Channel up/down | Yes | Yes | Yes |
| Number select | Yes | Yes | Yes |
| Status publish | Yes | Yes | Yes |
| Volume command handling | Yes, for built-in inline M3U players | No | No |
supportsVolume in status |
true | false | false |
Known limitations
- Volume commands are currently no-op in Xtream and Stalker integrations.
- Remote status uses polling from web UI (2s), not push/WebSocket.
- Number-based selection is list-position based (1-based index in active list scope), not global EPG number mapping.
- Remote API currently has no auth/TLS; intended for trusted local networks.
Operational notes
- UI updates in remote web app require rebuilding
remote-control-webso Electron serves freshdistassets. - If stale UI appears, clear browser cache/hard-refresh mobile browser.
Future extension points
- Add optional auth token for
/api/remote-control/*endpoints. - Add WebSocket/SSE status push for lower latency and reduced polling.
- Add cross-portal volume abstraction and capability negotiation.
- Add last-channel, favorites navigation, and search/select commands.