mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
docs: add architecture documentation for M3U playlist, remote control, EPG, and Stalker portal
This commit is contained in:
1 parent
67b32e82fe
commit
935ef35618
5 files changed
+1060
No files matched your search
@@ -0,0 +1,321 @@
|
||||
# M3U Playlist Module Architecture
|
||||
|
||||
This document describes the M3U playlist module architecture, which handles traditional M3U/M3U8 playlists (as opposed to Xtream Codes or Stalker Portal).
|
||||
|
||||
## Overview
|
||||
|
||||
The M3U playlist module provides:
|
||||
- Channel list display with virtual scrolling (90,000+ channels support)
|
||||
- EPG (Electronic Program Guide) integration
|
||||
- Favorites management with drag-and-drop reordering
|
||||
- Channel grouping and search
|
||||
- Video playback with multiple player backends
|
||||
|
||||
## Module Structure
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ VIDEO PLAYER PAGE │
|
||||
│ apps/web/src/app/home/video-player/ │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────┐ ┌──────────────────────┐ ┌────────────────────┐ │
|
||||
│ │ Sidebar │ │ Video Player │ │ EPG List │ │
|
||||
│ │ │ │ (ArtPlayer/Video.js)│ │ (Right drawer) │ │
|
||||
│ │ ┌─────────┐ │ │ │ │ │ │
|
||||
│ │ │Channel │ │ │ │ │ │ │
|
||||
│ │ │List │ │ │ │ │ │ │
|
||||
│ │ │Container│ │ │ │ │ │ │
|
||||
│ │ └─────────┘ │ │ │ │ │ │
|
||||
│ └─────────────┘ └──────────────────────┘ └────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ NgRx STORE (m3u-state) │
|
||||
│ libs/m3u-state/ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Playlist │ │ Channel │ │ EPG │ │Favorites │ │ Filter │ │
|
||||
│ │ Reducer │ │ Reducer │ │ Reducer │ │ Reducer │ │ Reducer │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## State Management (libs/m3u-state/)
|
||||
|
||||
### State Structure
|
||||
|
||||
```typescript
|
||||
interface PlaylistState {
|
||||
// Active channel being played
|
||||
active: Channel | undefined;
|
||||
|
||||
// All channels from current playlist
|
||||
channels: Channel[];
|
||||
|
||||
// EPG state
|
||||
epg: {
|
||||
epgAvailable: boolean;
|
||||
activeEpgProgram: EpgProgram | undefined;
|
||||
currentEpgProgram: EpgProgram | undefined;
|
||||
};
|
||||
|
||||
// Playlist metadata (entity adapter)
|
||||
playlistsMeta: {
|
||||
ids: string[];
|
||||
entities: Record<string, PlaylistMeta>;
|
||||
selectedId: string | undefined;
|
||||
allPlaylistsLoaded: boolean;
|
||||
selectedFilters: PlaylistSourceFilter[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Actions
|
||||
|
||||
| Action Group | Actions | Purpose |
|
||||
|--------------|---------|---------|
|
||||
| **PlaylistActions** | `loadPlaylists`, `addPlaylist`, `removePlaylist`, `parsePlaylist`, `setActivePlaylist` | Playlist CRUD |
|
||||
| **ChannelActions** | `setChannels`, `setActiveChannel`, `setAdjacentChannelAsActive` | Channel selection & navigation |
|
||||
| **EpgActions** | `setActiveEpgProgram`, `setCurrentEpgProgram`, `setEpgAvailableFlag` | EPG state |
|
||||
| **FavoritesActions** | `updateFavorites`, `setFavorites` | Favorites management |
|
||||
| **FilterActions** | `setSelectedFilters` | Playlist type filtering |
|
||||
|
||||
### Key Selectors
|
||||
|
||||
```typescript
|
||||
// Channel selectors
|
||||
selectActive // Current playing channel
|
||||
selectChannels // All channels array
|
||||
selectFavorites // Favorite channel URLs
|
||||
|
||||
// Playlist selectors
|
||||
selectAllPlaylistsMeta // All playlists
|
||||
selectActivePlaylistId // Selected playlist ID
|
||||
selectCurrentPlaylist // Active playlist object
|
||||
selectPlaylistTitle // Title with "Global favorites" fallback
|
||||
|
||||
// EPG selectors
|
||||
selectIsEpgAvailable // EPG data available flag
|
||||
selectCurrentEpgProgram // Current playing program
|
||||
```
|
||||
|
||||
## Channel List Container
|
||||
|
||||
**Location**: `libs/ui/components/src/lib/channel-list-container/`
|
||||
|
||||
### Component Architecture
|
||||
|
||||
```
|
||||
channel-list-container/
|
||||
├── channel-list-container.component.ts # Parent - shared state coordinator
|
||||
├── channel-list-container.component.html
|
||||
├── channel-list-container.component.scss
|
||||
│
|
||||
├── all-channels-tab/ # Virtual scroll + search
|
||||
│ ├── all-channels-tab.component.ts
|
||||
│ ├── all-channels-tab.component.html
|
||||
│ └── all-channels-tab.component.scss
|
||||
│
|
||||
├── groups-tab/ # Expansion panels + infinite scroll
|
||||
│ ├── groups-tab.component.ts
|
||||
│ ├── groups-tab.component.html
|
||||
│ └── groups-tab.component.scss
|
||||
│
|
||||
├── favorites-tab/ # Drag-drop reordering
|
||||
│ ├── favorites-tab.component.ts
|
||||
│ ├── favorites-tab.component.html
|
||||
│ └── favorites-tab.component.scss
|
||||
│
|
||||
└── channel-list-item/ # Individual channel display
|
||||
├── channel-list-item.component.ts
|
||||
├── channel-list-item.component.html
|
||||
└── channel-list-item.component.scss
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ ChannelListContainerComponent │
|
||||
│ (Parent) │
|
||||
│ ┌────────────────────────────────────────────────────────┐ │
|
||||
│ │ Shared State (Signals): │ │
|
||||
│ │ - channelEpgMap: Map<string, EpgProgram> │ │
|
||||
│ │ - progressTick: number (30s interval) │ │
|
||||
│ │ - shouldShowEpg: boolean │ │
|
||||
│ │ - favoriteIds: Set<string> │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────────┼─────────────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
|
||||
│ │ All │ │ Groups │ │ Favorites │ │
|
||||
│ │Channels │ │ Tab │ │ Tab │ │
|
||||
│ │ Tab │ │ │ │ │ │
|
||||
│ └────┬────┘ └────┬─────┘ └─────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ └───────────────────┴─────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ (channelSelected) output │
|
||||
│ │ │
|
||||
└──────────────────────────┼───────────────────────────────────┘
|
||||
▼
|
||||
Store Dispatch
|
||||
ChannelActions.setActiveChannel
|
||||
```
|
||||
|
||||
### EnrichedChannel Pattern
|
||||
|
||||
For performance optimization, channels are pre-enriched with EPG data:
|
||||
|
||||
```typescript
|
||||
interface EnrichedChannel extends Channel {
|
||||
epgProgram: EpgProgram | null | undefined;
|
||||
progressPercentage: number; // Pre-computed by parent
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Optimizations
|
||||
|
||||
| Optimization | Implementation |
|
||||
|--------------|----------------|
|
||||
| **Virtual Scroll** | CDK virtual scroll for 90,000+ channels |
|
||||
| **Computed Signals** | `enrichedChannels` computed signal replaces template pipe |
|
||||
| **Debounced Search** | 300ms debounce on search input |
|
||||
| **Global Progress Tick** | Single 30s interval instead of per-item intervals |
|
||||
| **OnPush Change Detection** | All components use OnPush |
|
||||
| **Infinite Scroll in Groups** | IntersectionObserver loads 50 channels at a time |
|
||||
| **Memoized Group Enrichment** | `enrichedGroupChannelsMap` computed signal |
|
||||
|
||||
### Tab Components
|
||||
|
||||
#### AllChannelsTabComponent
|
||||
- **Inputs**: `channels`, `channelEpgMap`, `progressTick`, `shouldShowEpg`, `itemSize`, `activeChannelUrl`, `favoriteIds`
|
||||
- **Outputs**: `channelSelected`, `favoriteToggled`
|
||||
- **Features**: Search with 300ms debounce, virtual scrolling, no-results placeholder
|
||||
|
||||
#### GroupsTabComponent
|
||||
- **Inputs**: Same as AllChannelsTab + `groupedChannels`
|
||||
- **Outputs**: `channelSelected`, `favoriteToggled`
|
||||
- **Features**: Expansion panels, infinite scroll with IntersectionObserver, lazy loading
|
||||
|
||||
#### FavoritesTabComponent
|
||||
- **Inputs**: `favorites`, `channelEpgMap`, `progressTick`, `shouldShowEpg`, `activeChannelUrl`
|
||||
- **Outputs**: `channelSelected`, `favoriteToggled`, `favoritesReordered`
|
||||
- **Features**: Drag-and-drop reordering with CDK DragDrop
|
||||
|
||||
## EPG Integration
|
||||
|
||||
### EpgService (libs/services/)
|
||||
|
||||
```typescript
|
||||
class EpgService {
|
||||
// Fetch EPG for multiple URLs
|
||||
fetchEpg(urls: string[]): void;
|
||||
|
||||
// Get programs for a channel
|
||||
getChannelPrograms(channelId: string): void;
|
||||
|
||||
// Batch fetch current programs
|
||||
getCurrentProgramsForChannels(channelIds: string[]): Observable<Map<string, EpgProgram>>;
|
||||
|
||||
// Observables
|
||||
epgAvailable$: Observable<boolean>;
|
||||
currentEpgPrograms$: Observable<EpgProgram[]>;
|
||||
}
|
||||
```
|
||||
|
||||
### EPG Components
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| `EpgListComponent` | Timeline view for single channel |
|
||||
| `EpgListItemComponent` | Individual program in timeline |
|
||||
| `EpgItemDescriptionComponent` | Program details dialog |
|
||||
| `MultiEpgContainerComponent` | Grid view of all channels' schedules |
|
||||
|
||||
## Video Player
|
||||
|
||||
**Location**: `apps/web/src/app/home/video-player/`
|
||||
|
||||
### Supported Players
|
||||
- **ArtPlayer** (default) - Modern player with plugins
|
||||
- **Video.js** - Fallback with HLS support
|
||||
- **HTML5** - Basic video element
|
||||
- **Audio** - For radio streams
|
||||
|
||||
### Player Features
|
||||
- Channel navigation (prev/next)
|
||||
- Favorites toggle
|
||||
- EPG sidebar
|
||||
- Multi-EPG modal view
|
||||
- Channel info overlay
|
||||
- External player support (MPV, VLC) in Electron
|
||||
|
||||
## Interfaces
|
||||
|
||||
### Channel Interface
|
||||
```typescript
|
||||
interface Channel {
|
||||
id: string;
|
||||
url: string;
|
||||
name: string;
|
||||
group: { title: string };
|
||||
tvg: {
|
||||
id: string; // For EPG matching
|
||||
name: string;
|
||||
url: string;
|
||||
logo: string;
|
||||
rec: string;
|
||||
};
|
||||
epgParams?: string;
|
||||
timeshift?: string;
|
||||
catchup?: { type?: string; source?: string; days?: string };
|
||||
http: {
|
||||
referrer: string;
|
||||
'user-agent': string;
|
||||
origin: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### EpgProgram Interface
|
||||
```typescript
|
||||
interface EpgProgram {
|
||||
start: string; // ISO string
|
||||
stop: string; // ISO string
|
||||
channel: string; // TVG ID
|
||||
title: string;
|
||||
desc: string | null;
|
||||
category: string | null;
|
||||
episodeNum?: string | null;
|
||||
iconUrl?: string | null;
|
||||
rating?: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
## Routes
|
||||
|
||||
```
|
||||
/playlists/:id # Video player with playlist
|
||||
/iptv # Default IPTV route
|
||||
```
|
||||
|
||||
## Adding New Features
|
||||
|
||||
### To add a new tab to channel list:
|
||||
1. Create component in `channel-list-container/new-tab/`
|
||||
2. Accept inputs: `channels`, `channelEpgMap`, `progressTick`, `shouldShowEpg`, `activeChannelUrl`
|
||||
3. Emit `channelSelected` output
|
||||
4. Add to parent template and imports
|
||||
|
||||
### To add EPG-related features:
|
||||
1. Use `EpgService` for data fetching
|
||||
2. Subscribe to `channelEpgMap` signal for current programs
|
||||
3. Dispatch `EpgActions` for state updates
|
||||
|
||||
### To modify favorites behavior:
|
||||
1. Dispatch `FavoritesActions.updateFavorites` for toggle
|
||||
2. Dispatch `FavoritesActions.setFavorites` for reordering
|
||||
3. Effects automatically persist to database
|
||||
@@ -0,0 +1,245 @@
|
||||
# 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:
|
||||
|
||||
- [Stalker Portal Architecture](./stalker-portal.md)
|
||||
- [Stalker Portal EPG Architecture](./stalker-epg.md)
|
||||
|
||||
## 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
|
||||
|
||||
1. User opens remote web UI (`http://<local-ip>:<port>`).
|
||||
2. Remote web app calls `/api/remote-control/*`.
|
||||
3. Electron main handles API request and sends IPC to renderer:
|
||||
- `CHANNEL_CHANGE` for up/down
|
||||
- `REMOTE_CONTROL_COMMAND` for numeric/volume commands
|
||||
4. Renderer-specific feature module (M3U/Xtream/Stalker) applies action.
|
||||
5. Renderer pushes status snapshots back to main via:
|
||||
- `REMOTE_CONTROL_STATUS_UPDATE`
|
||||
6. Remote web app polls `/api/remote-control/status` and 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`
|
||||
- Routes `/api/remote-control/*` to registered handlers.
|
||||
- Starts/stops/restarts on settings updates.
|
||||
|
||||
### Remote control event module
|
||||
|
||||
- File: `apps/electron-backend/src/app/events/remote-control.events.ts`
|
||||
- Bootstrapped in: `apps/electron-backend/src/main.ts` via `RemoteControlEvents.bootstrapRemoteControlEvents()`
|
||||
|
||||
Registered endpoints:
|
||||
|
||||
- `POST /api/remote-control/channel/up`
|
||||
- `POST /api/remote-control/channel/down`
|
||||
- `POST /api/remote-control/channel/select-number` with `{ number: <int> }`
|
||||
- `POST /api/remote-control/volume/up`
|
||||
- `POST /api/remote-control/volume/down`
|
||||
- `POST /api/remote-control/volume/toggle-mute`
|
||||
- `GET /api/remote-control/status`
|
||||
|
||||
IPC emitted to renderer:
|
||||
|
||||
- `CHANNEL_CHANGE` payload: `{ direction: 'up' | 'down' }`
|
||||
- `REMOTE_CONTROL_COMMAND` payload:
|
||||
- `{ 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 `RemoteControlStatus` object returned by `/status`
|
||||
|
||||
### Settings integration
|
||||
|
||||
- Main handler: `apps/electron-backend/src/app/events/settings.events.ts`
|
||||
- On `SETTINGS_UPDATE`, reads `remoteControl` and `remoteControlPort`, 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) => unsubscribe`
|
||||
- `onRemoteControlCommand(callback) => unsubscribe`
|
||||
- `updateRemoteControlStatus(status) => void`
|
||||
|
||||
Type definitions:
|
||||
|
||||
- `apps/web/src/typings.d.ts`
|
||||
- `global.d.ts`
|
||||
|
||||
## Renderer Integrations
|
||||
|
||||
## Shared helpers
|
||||
|
||||
- File: `apps/web/src/app/shared/services/remote-channel-navigation.util.ts`
|
||||
|
||||
Functions:
|
||||
|
||||
- `getAdjacentChannelItem(...)`: wraps around on boundaries for up/down
|
||||
- `getChannelItemByNumber(...)`: 1-based number to list item mapping
|
||||
|
||||
Used by M3U, Xtream, and Stalker live integrations.
|
||||
|
||||
## M3U integration
|
||||
|
||||
- File: `apps/web/src/app/home/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(...)`
|
||||
- Applies volume commands:
|
||||
- up/down in 0.1 increments
|
||||
- toggle mute with last non-zero volume restore
|
||||
- persists to `localStorage`
|
||||
- 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: `apps/web/src/app/xtream-tauri/live-stream-layout/live-stream-layout.component.ts`
|
||||
|
||||
Implemented behavior:
|
||||
|
||||
- Subscribes to:
|
||||
- `onChannelChange` for up/down
|
||||
- `onRemoteControlCommand` for number select
|
||||
- Up/down:
|
||||
- Uses selected live item `selectedItem().xtream_id`
|
||||
- Navigates inside `selectItemsFromSelectedCategory()`
|
||||
- Calls `playLive(nextItem)`
|
||||
- Number select:
|
||||
- Maps number to item in current category list
|
||||
- Calls `playLive(channel)`
|
||||
- Publishes status via effect:
|
||||
- `portal: 'xtream'`
|
||||
- `isLiveView` only when selected content type is `live` and item is selected
|
||||
- channel name/number + current EPG item
|
||||
- `supportsVolume: false`
|
||||
- Cleans listeners in `ngOnDestroy`.
|
||||
|
||||
## Stalker integration (ITV live view)
|
||||
|
||||
- File: `apps/web/src/app/stalker/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
|
||||
|
||||
Implemented behavior:
|
||||
|
||||
- Subscribes to:
|
||||
- `onChannelChange` for up/down
|
||||
- `onRemoteControlCommand` for number select
|
||||
- Up/down:
|
||||
- Uses `selectedItem().id`
|
||||
- Navigates inside `itvChannels()`
|
||||
- Calls `playChannel(nextItem)`
|
||||
- Number select:
|
||||
- Maps number into `itvChannels()`
|
||||
- Calls `playChannel(channel)`
|
||||
- Publishes status via effect:
|
||||
- `portal: 'stalker'`
|
||||
- `isLiveView` only for selected content type `itv` with 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.ts`
|
||||
- `libs/ui/remote-control/src/lib/remote-control/remote-control.component.html`
|
||||
- `libs/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 `/status` every 2s
|
||||
- Uses action wrapper to refresh status after command execution
|
||||
|
||||
## Settings UI and discoverability
|
||||
|
||||
- Files:
|
||||
- `apps/web/src/app/settings/settings.component.ts`
|
||||
- `apps/web/src/app/settings/settings.component.html`
|
||||
- Features:
|
||||
- Toggle `remoteControl`
|
||||
- Configure `remoteControlPort`
|
||||
- Display local URLs and QR codes for remote access
|
||||
- Local IP list loaded via `getLocalIpAddresses()`
|
||||
|
||||
## 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 | 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-web` so Electron serves fresh `dist` assets.
|
||||
- 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.
|
||||
@@ -0,0 +1,212 @@
|
||||
# Stalker Portal EPG Architecture
|
||||
|
||||
This document describes the EPG (Electronic Program Guide) implementation for Stalker/Ministra portal live TV (ITV) streams.
|
||||
|
||||
Related architecture docs:
|
||||
|
||||
- [Stalker Portal Architecture](./stalker-portal.md)
|
||||
- [Remote Control Architecture](./remote-control.md)
|
||||
|
||||
## Overview
|
||||
|
||||
The Stalker ITV live stream layout displays EPG data in the right panel when a live channel is playing. EPG is fetched per-channel using the Stalker `get_short_epg` API action, which returns the current program plus the next ~5 upcoming programs. The response is mapped to the shared `EpgItem` interface and rendered by the reusable `EpgViewComponent`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────────────┐
|
||||
│ StalkerLiveStreamLayoutComponent │
|
||||
│ apps/web/src/app/stalker/stalker-live-stream-layout/ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Sidebar │ │ Video Player │ │ EPG Panel │ │
|
||||
│ │ (channels) │ │ (WebPlayerView) │ │ (EpgViewComponent) │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ click ──────┼────┼──► playChannel() ────┼────┼──► loadEpgFor...() │ │
|
||||
│ └──────────────┘ └──────────────────────┘ └─────────────────────┘ │
|
||||
│ │ │ │
|
||||
└──────────────────────────────┼────────────────────────────┼───────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌────────────────────────┐
|
||||
│ StalkerStore │ │ StalkerStore │
|
||||
│ fetchLinkToPlay() │ │ fetchChannelEpg() │
|
||||
└──────────┬───────────┘ └────────────┬───────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ Stalker Portal API │
|
||||
│ action=create_link action=get_short_epg │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Stalker EPG API
|
||||
|
||||
### `get_short_epg` (per-channel)
|
||||
|
||||
**Request:**
|
||||
```
|
||||
GET load.php?type=itv&action=get_short_epg&ch_id={channel_id}&JsHttpRequest=1-xml
|
||||
```
|
||||
Uses standard Stalker auth headers (Bearer token + MAC cookie).
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"js": {
|
||||
"data": [
|
||||
{
|
||||
"id": "123",
|
||||
"ch_id": "45",
|
||||
"name": "Program Title",
|
||||
"descr": "Program description",
|
||||
"time": "2025-01-15 14:00:00",
|
||||
"time_to": "2025-01-15 14:30:00",
|
||||
"duration": "1800",
|
||||
"start_timestamp": "1736949600",
|
||||
"stop_timestamp": "1736951400",
|
||||
"t_time": "14:00",
|
||||
"t_time_to": "14:30"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- If the channel has no `xmltv_id` set on the server, returns an empty array
|
||||
- Response normalization handles both `{ js: { data: [...] } }` and `{ js: [...] }` formats
|
||||
- No backend (Electron IPC) changes needed — the generic `stalker.events.ts` handler forwards any `params` to the portal URL
|
||||
|
||||
### `get_epg_info` (bulk — reserved for future use)
|
||||
|
||||
```
|
||||
GET load.php?type=itv&action=get_epg_info&period={hours}&JsHttpRequest=1-xml
|
||||
```
|
||||
|
||||
Returns EPG for all channels for a given time period. Currently unused but the enum value `StalkerPortalActions.GetEpgInfo` is defined for future bulk EPG features.
|
||||
|
||||
## Data Mapping
|
||||
|
||||
### Stalker EPG → `EpgItem` Interface
|
||||
|
||||
| Stalker field | `EpgItem` field | Notes |
|
||||
|--------------------|--------------------|-------|
|
||||
| `id` | `id` | Converted to string |
|
||||
| `ch_id` | `channel_id` | Falls back to passed `channelId` |
|
||||
| `name` | `title` | |
|
||||
| `descr` | `description` | |
|
||||
| `time` | `start` | Full datetime string |
|
||||
| `time_to` | `end`, `stop` | Both set to same value |
|
||||
| `start_timestamp` | `start_timestamp` | Unix timestamp as string |
|
||||
| `stop_timestamp` | `stop_timestamp` | Unix timestamp as string |
|
||||
| _(n/a)_ | `epg_id` | Empty string |
|
||||
| _(n/a)_ | `lang` | Empty string |
|
||||
|
||||
### `EpgItem` Interface
|
||||
|
||||
```typescript
|
||||
// libs/shared/interfaces/src/lib/epg-item.interface.ts
|
||||
interface EpgItem {
|
||||
id: string;
|
||||
epg_id: string;
|
||||
title: string;
|
||||
lang: string;
|
||||
start: string;
|
||||
end: string;
|
||||
stop: string;
|
||||
description: string;
|
||||
channel_id: string;
|
||||
start_timestamp: string;
|
||||
stop_timestamp: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `libs/shared/interfaces/src/lib/stalker-portal-actions.enum.ts` | `GetShortEpg`, `GetEpgInfo` enum values |
|
||||
| `apps/web/src/app/stalker/stalker.store.ts` | `fetchChannelEpg()` method |
|
||||
| `apps/web/src/app/stalker/stalker-live-stream-layout/stalker-live-stream-layout.component.ts` | EPG signals + `loadEpgForChannel()` |
|
||||
| `apps/web/src/app/stalker/stalker-live-stream-layout/stalker-live-stream-layout.component.html` | `<app-epg-view>` integration |
|
||||
| `libs/ui/shared-portals/src/lib/epg-view/epg-view.component.ts` | Shared EPG display component |
|
||||
|
||||
### StalkerStore.fetchChannelEpg()
|
||||
|
||||
Location: `apps/web/src/app/stalker/stalker.store.ts` (in `withMethods`)
|
||||
|
||||
```typescript
|
||||
async fetchChannelEpg(channelId: number | string): Promise<EpgItem[]>
|
||||
```
|
||||
|
||||
- Sends `get_short_epg` request with `ch_id` param
|
||||
- Supports both full Stalker portals (authenticated via `StalkerSessionService`) and simple portals (direct IPC)
|
||||
- Returns mapped `EpgItem[]` or empty array on failure
|
||||
- No store state mutation — returns data directly to the component
|
||||
|
||||
### StalkerLiveStreamLayoutComponent EPG Integration
|
||||
|
||||
The component manages EPG state locally with signals:
|
||||
|
||||
```typescript
|
||||
readonly epgItems = signal<EpgItem[]>([]);
|
||||
readonly isLoadingEpg = signal(false);
|
||||
```
|
||||
|
||||
**Flow:**
|
||||
1. User clicks a channel in the sidebar → `playChannel(item)`
|
||||
2. `fetchLinkToPlay()` gets the stream URL
|
||||
3. If using embedded player, `streamUrl` is set and `loadEpgForChannel(item.id)` is called
|
||||
4. `loadEpgForChannel()` sets loading state, calls `stalkerStore.fetchChannelEpg()`, updates `epgItems`
|
||||
5. Template renders `<app-epg-view [epgItems]="epgItems()">` or a loading spinner
|
||||
|
||||
### EpgViewComponent (shared)
|
||||
|
||||
Location: `libs/ui/shared-portals/src/lib/epg-view/`
|
||||
|
||||
Reusable component shared between Stalker and Xtream live stream layouts:
|
||||
- **Input:** `epgItems: EpgItem[]`
|
||||
- Displays program list with time, title, and info button
|
||||
- Highlights current program with green progress bar
|
||||
- Handles empty state (shows "EPG not available" message)
|
||||
- Info button opens `EpgItemDescriptionComponent` dialog with title and description
|
||||
|
||||
**Current program detection:**
|
||||
```typescript
|
||||
isCurrentProgram(item: EpgItem): boolean {
|
||||
const now = new Date().getTime();
|
||||
const start = new Date(item.start).getTime();
|
||||
const stop = new Date(item.stop ?? item.end).getTime();
|
||||
return now >= start && now <= stop;
|
||||
}
|
||||
```
|
||||
|
||||
This works with Stalker's datetime format (`"2025-01-15 14:00:00"`) because `new Date()` parses it correctly.
|
||||
|
||||
## Authentication
|
||||
|
||||
EPG requests follow the same authentication pattern as all Stalker API calls:
|
||||
|
||||
| Portal Type | Auth Method |
|
||||
|-------------|-------------|
|
||||
| **Full Stalker** (`isFullStalkerPortal: true`) | `StalkerSessionService.makeAuthenticatedRequest()` — handles token refresh and retry on 401 |
|
||||
| **Simple Stalker** | Direct IPC via `DataService.sendIpcEvent(STALKER_REQUEST, ...)` — no auth headers |
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Bulk EPG in Channel List Sidebar
|
||||
|
||||
Use `get_epg_info` with `period=3` to pre-fetch current program titles for all channels when a category is selected:
|
||||
- Call `get_epg_info` after category selection
|
||||
- Build a `Map<channelId, currentProgram>`
|
||||
- Show current program name below channel title in the sidebar
|
||||
- Display progress bar per channel item
|
||||
|
||||
This is a separate task due to Stalker's lazy-loaded channel pagination model.
|
||||
|
||||
### EPG Auto-Refresh
|
||||
|
||||
Currently EPG is fetched once per channel selection. A future enhancement could add a timer to refresh EPG data periodically (e.g., every 5 minutes) to keep the "current program" indicator accurate during long viewing sessions.
|
||||
@@ -0,0 +1,156 @@
|
||||
# Stalker Portal Architecture
|
||||
|
||||
This document describes the Stalker portal implementation in IPTVnator and where each feature is integrated.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [Stalker Portal EPG Architecture](./stalker-epg.md)
|
||||
- [Remote Control Architecture](./remote-control.md)
|
||||
- [Download Manager](./download-manager.md)
|
||||
- [Category Management](./category-management.md)
|
||||
- [Stalker Store API Baseline](./stalker-store-api-baseline.md)
|
||||
|
||||
## Scope
|
||||
|
||||
Stalker support covers:
|
||||
|
||||
- Live TV (`itv`)
|
||||
- VOD (`vod`)
|
||||
- Series (`series`)
|
||||
- VOD-as-series flows (`is_series=1` and embedded `series[]`)
|
||||
- Favorites and recently viewed collections
|
||||
- Search
|
||||
- External player playback (shared Xtream player infrastructure)
|
||||
- Remote control for live ITV navigation
|
||||
|
||||
## Routing Structure
|
||||
|
||||
Primary route tree lives in `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker.routes.ts`.
|
||||
|
||||
- `/stalker/:id/vod`
|
||||
- `/stalker/:id/series`
|
||||
- `/stalker/:id/itv`
|
||||
- `/stalker/:id/favorites`
|
||||
- `/stalker/:id/recent`
|
||||
- `/stalker/:id/search`
|
||||
- `/stalker/:id/downloads` (shared downloads module from Xtream UI)
|
||||
|
||||
## Runtime Architecture
|
||||
|
||||
1. Angular Stalker screens call methods/resources in `StalkerStore`.
|
||||
2. `StalkerStore` builds request params based on selected content type and current view state.
|
||||
3. Requests go through `DataService.sendIpcEvent(STALKER_REQUEST, ...)` or `StalkerSessionService` (full portal auth).
|
||||
4. Electron main process handles `STALKER_REQUEST` in `/Users/4gray/Code/iptvnator/apps/electron-backend/src/app/events/stalker.events.ts`.
|
||||
5. Axios calls Stalker `load.php` API with required headers/cookies and returns normalized payloads to renderer.
|
||||
|
||||
## Main UI Components
|
||||
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-main-container.component.ts`
|
||||
- Category + content layout for `vod` and `series`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
|
||||
- ITV live playback, channel navigation, EPG panel integration
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-series-view/stalker-series-view.component.ts`
|
||||
- Season/episode UI for all Stalker series modes
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-favorites/stalker-favorites.component.ts`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/recently-viewed/recently-viewed.component.ts`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-search/stalker-search.component.ts`
|
||||
|
||||
## Store and Data Flow
|
||||
|
||||
Stalker store is now feature-composed:
|
||||
|
||||
- Facade: `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker.store.ts`
|
||||
- Feature slices: `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stores/features/*`
|
||||
- Shared store utils: `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stores/utils/*`
|
||||
|
||||
Important store responsibilities:
|
||||
|
||||
- Selected content/category/item state
|
||||
- Category and paginated content resources
|
||||
- ITV channel list + pagination
|
||||
- Regular series seasons resource
|
||||
- VOD-series (`is_series=1`) seasons + episodes resources
|
||||
- Playback link creation (`create_link` flow)
|
||||
- Favorites and recently viewed persistence helpers
|
||||
|
||||
## VOD/Series Modes
|
||||
|
||||
Stalker has multiple real-world data shapes. The current implementation supports all three:
|
||||
|
||||
1. Regular Series (`/series`):
|
||||
- Seasons come from API resource (`serialSeasonsResource`).
|
||||
- Episodes are derived from season payload.
|
||||
|
||||
2. VOD with Embedded `series[]`:
|
||||
- Item is opened under VOD, but already contains episodes.
|
||||
- `StalkerSeriesViewComponent` creates a pseudo-season and renders episodes directly.
|
||||
|
||||
3. VOD with `is_series=1` (Ministra plugin behavior):
|
||||
- Treated as series flow from VOD context.
|
||||
- Seasons are fetched lazily.
|
||||
- Episodes are fetched on season select.
|
||||
- Uses unique generated tracking IDs for episode playback position compatibility.
|
||||
|
||||
Core decision logic and normalization are centralized in:
|
||||
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-vod.utils.ts`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/models/*.ts`
|
||||
|
||||
## Favorites and Recently Viewed
|
||||
|
||||
Current implementation is shared via Stalker-specific helpers:
|
||||
|
||||
- `createPortalCollectionResource(...)` generic collection loader
|
||||
- `createPortalFavoritesResource(...)` favorites wrapper
|
||||
- `createStalkerDetailViewState(...)` unified "open detail" decision
|
||||
- `toggleStalkerVodFavorite(...)` shared add/remove behavior
|
||||
- `normalizeStalkerEntityId(...)` and `normalizeStalkerEntityIdAsNumber(...)` for stable ID matching
|
||||
- `matchesFavoriteById(...)` for cross-shape favorite matching
|
||||
|
||||
Where this is used:
|
||||
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-favorites/stalker-favorites.component.ts`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/recently-viewed/recently-viewed.component.ts`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-search/stalker-search.component.ts`
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/favorites-button/favorites-button.component.ts`
|
||||
|
||||
## Remote Control Integration
|
||||
|
||||
Stalker live remote control is implemented in:
|
||||
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
|
||||
|
||||
Supported today:
|
||||
|
||||
- Channel up/down
|
||||
- Numeric channel selection (list-position based)
|
||||
- Status publish for remote UI (portal/channel/current program)
|
||||
|
||||
See full backend and web-remote flow in [Remote Control Architecture](./remote-control.md).
|
||||
|
||||
## EPG Integration
|
||||
|
||||
EPG in Stalker ITV uses `get_short_epg` and shared `EpgViewComponent`. Full details are documented in [Stalker Portal EPG Architecture](./stalker-epg.md).
|
||||
|
||||
## Shared/Reusable Infrastructure
|
||||
|
||||
Stalker reuses some Xtream UI infrastructure deliberately:
|
||||
|
||||
- Category content rendering route uses Xtream category content component
|
||||
- Season container for episodes uses shared Xtream season UI component
|
||||
- Playback position handling for series episodes reuses Xtream store position mechanisms
|
||||
- Downloads route reuses shared downloads feature
|
||||
|
||||
This reduces duplicate UI logic across portal types and keeps compatibility behavior aligned.
|
||||
|
||||
## Regression Coverage
|
||||
|
||||
Focused regression tests for Stalker VOD mode branching live in:
|
||||
|
||||
- `/Users/4gray/Code/iptvnator/apps/web/src/app/stalker/stalker-vod.utils.spec.ts`
|
||||
|
||||
Covered scenarios include:
|
||||
|
||||
- Embedded `series[]` opens series view state
|
||||
- `is_series=1` opens lazy series state
|
||||
- Favorite toggle helper path invokes the expected add/remove flow
|
||||
@@ -0,0 +1,126 @@
|
||||
# Stalker Store API Baseline
|
||||
|
||||
This is the compatibility baseline for refactoring `apps/web/src/app/stalker/stalker.store.ts`.
|
||||
|
||||
Goal: keep this public surface stable while splitting to feature stores.
|
||||
|
||||
## Source of Truth
|
||||
|
||||
- Store implementation: `apps/web/src/app/stalker/stalker.store.ts`
|
||||
- Baseline created on current branch state before feature-store extraction.
|
||||
|
||||
## Public State Signals
|
||||
|
||||
Direct signal properties currently exposed by `signalStore`:
|
||||
|
||||
- `selectedContentType: 'vod' | 'itv' | 'series'`
|
||||
- `selectedCategoryId: string | null | undefined`
|
||||
- `selectedVodId: string | undefined`
|
||||
- `selectedSerialId: string | undefined`
|
||||
- `selectedItvId: string | undefined`
|
||||
- `limit: number`
|
||||
- `page: number`
|
||||
- `searchPhrase: string`
|
||||
- `currentPlaylist: PlaylistMeta`
|
||||
- `totalCount: number`
|
||||
- `selectedItem: StalkerVodSource | null | undefined`
|
||||
- `vodCategories: StalkerCategoryItem[]`
|
||||
- `seriesCategories: StalkerCategoryItem[]`
|
||||
- `itvCategories: StalkerCategoryItem[]`
|
||||
- `hasMoreChannels: boolean`
|
||||
- `itvChannels: StalkerItvChannel[]`
|
||||
- `vodSeriesSeasons: StalkerVodSeriesSeason[]`
|
||||
- `vodSeriesEpisodes: StalkerVodSeriesEpisode[]`
|
||||
- `selectedVodSeriesSeasonId: string | undefined`
|
||||
|
||||
## Public Computed Selectors
|
||||
|
||||
- `getTotalPages: number`
|
||||
- `getPaginatedContent: StalkerContentItem[] | undefined`
|
||||
- `isPaginatedContentLoading: boolean`
|
||||
- `isPaginatedContentFailed: unknown`
|
||||
- `getSerialSeasonsResource: StalkerSeason[]`
|
||||
- `isSerialSeasonsLoading: boolean`
|
||||
- `getVodSeriesSeasonsResource: StalkerVodSeriesSeason[]`
|
||||
- `isVodSeriesSeasonsLoading: boolean`
|
||||
- `getCategoryResource: StalkerCategoryItem[]`
|
||||
- `isCategoryResourceLoading: boolean`
|
||||
- `isCategoryResourceFailed: unknown`
|
||||
- `getSelectedCategoryName: string`
|
||||
|
||||
## Exposed Resources/Props
|
||||
|
||||
These are currently reachable on the store object and used internally by computed selectors:
|
||||
|
||||
- `getCategoryResource` (resource)
|
||||
- `getContentResource` (resource)
|
||||
- `serialSeasonsResource` (resource)
|
||||
- `vodSeriesSeasonsResource` (resource)
|
||||
- `makeStalkerRequest(...)`
|
||||
|
||||
During refactor:
|
||||
- Keep compatibility for external callers that may read these directly.
|
||||
- If moved/renamed internally, provide facade aliases.
|
||||
|
||||
## Public Methods (Compatibility Contract)
|
||||
|
||||
- `setSelectedContentType(type: 'vod' | 'itv' | 'series'): void`
|
||||
- `setSelectedCategory(id: string | number | null): void`
|
||||
- `setSelectedSerialId(id: string): void`
|
||||
- `setSelectedVodId(id: string): void`
|
||||
- `setSelectedItvId(id: string): void`
|
||||
- `setLimit(limit: number): void`
|
||||
- `setPage(page: number): void`
|
||||
- `setCurrentPlaylist(playlist: PlaylistMeta | undefined): Promise<void>`
|
||||
- `setSelectedItem(selectedItem: StalkerVodSource | null | undefined): void`
|
||||
- `clearSelectedItem(): void`
|
||||
- `setCategories(type: 'vod' | 'series' | 'itv', categories: StalkerCategoryItem[]): void`
|
||||
- `resetCategories(): void`
|
||||
- `setItvChannels(channels: StalkerItvChannel[]): void`
|
||||
- `setSearchPhrase(phrase: string): void`
|
||||
- `fetchVodSeriesEpisodes(videoId: string, seasonId: string): Promise<StalkerVodSeriesEpisode[]>`
|
||||
- `getSelectedCategory(): { id: string | number; name: string; type: 'vod' | 'itv' | 'series' }`
|
||||
- `fetchLinkToPlay(portalUrl: string, macAddress: string, cmd: string, series?: number): Promise<string>`
|
||||
- `getExpireDate(): Promise<string>`
|
||||
- `addToFavorites(item: any, onDone?: () => void): void`
|
||||
- `removeFromFavorites(favoriteId: string, onDone?: () => void): void`
|
||||
- `fetchMovieFileId(movieId: string): Promise<string | null>`
|
||||
- `createLinkToPlayVod(cmd?: string, title?: string, thumbnail?: string, episodeNum?: number, episodeId?: number, startTime?: number): Promise<void>`
|
||||
- `addToRecentlyViewed(item: any): void`
|
||||
- `removeFromRecentlyViewed(itemId: number, onComplete?: () => void): void`
|
||||
- `fetchChannelEpg(channelId: number | string, size?: number): Promise<EpgItem[]>`
|
||||
|
||||
## Current Consumers (Observed)
|
||||
|
||||
Top observed store API usage in app code:
|
||||
|
||||
- `currentPlaylist` (16 references)
|
||||
- `setSelectedContentType` (9)
|
||||
- `selectedItem` (9)
|
||||
- `setSelectedItem` (7)
|
||||
- `setSelectedCategory` (6)
|
||||
- `createLinkToPlayVod` (6)
|
||||
- `removeFromFavorites` (5)
|
||||
- `setPage` (4)
|
||||
- `addToFavorites` (4)
|
||||
- `fetchChannelEpg` (3)
|
||||
- plus lower-frequency calls for paging/resources/series/recent.
|
||||
|
||||
Consumer directories sampled:
|
||||
|
||||
- `apps/web/src/app/stalker/**`
|
||||
- `apps/web/src/app/xtream-tauri/**`
|
||||
- `apps/web/src/app/shared/**`
|
||||
|
||||
## Invariants to Preserve During Refactor
|
||||
|
||||
- Selection IDs (`selectedVodId`, `selectedSerialId`, `selectedItvId`) are synchronized in `setSelectedItem`.
|
||||
- `setSelectedCategory(...)` resets `page` to `0`.
|
||||
- `createLinkToPlayVod(...)` continues to:
|
||||
- support episode playback metadata
|
||||
- append recently viewed
|
||||
- preserve external player payload shape
|
||||
- Full-portal auth path continues through `StalkerSessionService`.
|
||||
- Non-auth/simple path continues through `DataService.sendIpcEvent(STALKER_REQUEST, ...)`.
|
||||
- Resource-driven loading signals preserve existing names.
|
||||
|
||||
Reference in new issue
Block a user