feat(stalker): add radio support

Entire-Checkpoint: f957cd9849e0
This commit is contained in:
4gray committed 2026-05-09 01:28:39 +02:00
1 parent e9875f9f29
commit ab905af285
59 files changed
+1059 -179

No files matched your search

+2 -2
View File
@@ -151,8 +151,8 @@ The shared row should be reused instead of rebuilding channel markup per view.
shell context sidebar (the "Live Categories" rail rendered by
`WorkspaceShellContextSidebarComponent`), matching M3U's "everything quiets"
behaviour. The shell categories rail only collapses when the active section
is `live` (Xtream) or `itv` (Stalker); movies, series, favorites, and recent
routes leave it untouched.
is `live` (Xtream) or `itv`/`radio` (Stalker); movies, series, favorites,
and recent routes leave it untouched.
- Collapsed state is owned by `LiveLayoutSidebarStateService`
(`providedIn: 'root'`) in `@iptvnator/portal/shared/util`. Every surface that
participates injects the service and reads `isCollapsed`; any toggle calls
+8 -1
View File
@@ -47,6 +47,9 @@ faker.seed(macToNumber(mac))
│ └── generateChannels() → channels Map<categoryId, channel[]>
│ └── generateEpg() → epg Map<channelId, program[]>
│
├── generateCategories('radio', N) → radioCategories[]
│ └── generateRadioStations() → radio Map<categoryId, station[]>
│
├── generateCategories('vod', N) → vodCategories[]
│ └── generateVodItems() → vod Map<categoryId, item[]>
│ ├── normal VOD items
@@ -107,6 +110,10 @@ All responses follow the Stalker `portal.php` envelope:
}
```
For `type=radio`, items use the same paginated response envelope as live TV
channels, but each item is generated as a radio station with a `radio: true`
marker and an `ffrt4://radio/...` command.
### `get_ordered_list` (seasons — when `movie_id` is present)
```json
@@ -204,7 +211,7 @@ interface ScenarioConfig {
name: string;
description: string;
seed: number;
categoryCount: { itv: number; vod: number; series: number };
categoryCount: { itv: number; radio: number; vod: number; series: number };
itemsPerCategory: number;
seasonsPerSeries: number;
episodesPerSeason: number;
+25 -1
View File
@@ -18,6 +18,7 @@ This document describes the Stalker portal implementation in IPTVnator and where
Stalker support covers:
- Live TV (`itv`)
- Radio (`radio`)
- VOD (`vod`)
- Series (`series`)
- VOD-as-series flows (`is_series=1` and embedded `series[]`)
@@ -33,6 +34,7 @@ Primary route tree lives in `/Users/4gray/Code/iptvnator/libs/portal/stalker/fea
- `/stalker/:id/vod`
- `/stalker/:id/series`
- `/stalker/:id/itv`
- `/stalker/:id/radio`
- `/stalker/:id/favorites`
- `/stalker/:id/recent`
- `/stalker/:id/search`
@@ -51,7 +53,9 @@ Primary route tree lives in `/Users/4gray/Code/iptvnator/libs/portal/stalker/fea
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-main-container.component.ts`
- Category + content layout for `vod` and `series`
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-live-stream-layout/stalker-live-stream-layout.component.ts`
- ITV live playback, channel navigation, EPG panel integration
- ITV live playback, radio playback, channel/station navigation, EPG panel integration
- `/Users/4gray/Code/iptvnator/libs/ui/playback/src/lib/audio-player/audio-player.component.ts`
- Shared inline audio player used by M3U radio channels and Stalker radio stations
- `/Users/4gray/Code/iptvnator/libs/ui/components/src/lib/stalker-series-view/stalker-series-view.component.ts`
- Season/episode UI for all Stalker series modes
- `/Users/4gray/Code/iptvnator/libs/portal/stalker/feature/src/lib/stalker-favorites/stalker-favorites.component.ts`
@@ -71,6 +75,7 @@ Important store responsibilities:
- Selected content/category/item state
- Category and paginated content resources
- ITV channel list + pagination
- Radio category/station list + pagination
- Regular series seasons resource
- VOD-series (`is_series=1`) seasons + episodes resources
- Playback link creation (`create_link` flow)
@@ -98,6 +103,25 @@ Failure-handling rule:
not `undefined` collections or renderer exceptions. The workspace Stalker
context panel and live layout rely on this guarantee.
## Live TV and Radio
The Stalker live route and radio route intentionally share
`StalkerLiveStreamLayoutComponent`:
- `itv` uses `type=itv&action=get_ordered_list`, stores results in
`itvChannels`, resolves playback through `resolveItvPlayback(...)`, and keeps
the EPG panel visible.
- `radio` uses `type=radio&action=get_ordered_list`, stores results in
`radioChannels`, resolves playback through `resolveRadioPlayback(...)`, and
renders `AudioPlayerComponent` instead of a video player.
- Radio hides the EPG panel and must not call Stalker EPG endpoints because
radio stations do not have EPG data.
- Radio always uses the inline audio player. External player settings are
ignored for Stalker radio, matching M3U radio behavior.
- Some Stalker portals do not expose radio categories. Radio category loading
falls back to a synthetic `PORTALS.ALL_RADIO` category with
`category_id: '*'` so the station list can still be loaded.
## VOD/Series Modes
Stalker has multiple real-world data shapes. The current implementation supports all three:
@@ -13,7 +13,7 @@ Goal: keep this public surface stable while splitting to feature stores.
Direct signal properties currently exposed by `signalStore`:
- `selectedContentType: 'vod' | 'itv' | 'series'`
- `selectedContentType: 'vod' | 'itv' | 'radio' | 'series'`
- `selectedCategoryId: string | null | undefined`
- `selectedVodId: string | undefined`
- `selectedSerialId: string | undefined`
@@ -27,8 +27,10 @@ Direct signal properties currently exposed by `signalStore`:
- `vodCategories: StalkerCategoryItem[]`
- `seriesCategories: StalkerCategoryItem[]`
- `itvCategories: StalkerCategoryItem[]`
- `radioCategories: StalkerCategoryItem[]`
- `hasMoreChannels: boolean`
- `itvChannels: StalkerItvChannel[]`
- `radioChannels: StalkerItvChannel[]`
- `vodSeriesSeasons: StalkerVodSeriesSeason[]`
- `vodSeriesEpisodes: StalkerVodSeriesEpisode[]`
- `selectedVodSeriesSeasonId: string | undefined`
@@ -66,7 +68,7 @@ During refactor:
## Public Methods (Compatibility Contract)
- `setSelectedContentType(type: 'vod' | 'itv' | 'series'): void`
- `setSelectedContentType(type: 'vod' | 'itv' | 'radio' | 'series'): void`
- `setSelectedCategory(id: string | number | null): void`
- `setSelectedSerialId(id: string): void`
- `setSelectedVodId(id: string): void`
@@ -76,15 +78,18 @@ During refactor:
- `setCurrentPlaylist(playlist: PlaylistMeta | undefined): Promise<void>`
- `setSelectedItem(selectedItem: StalkerVodSource | null | undefined): void`
- `clearSelectedItem(): void`
- `setCategories(type: 'vod' | 'series' | 'itv', categories: StalkerCategoryItem[]): void`
- `setCategories(type: 'vod' | 'series' | 'itv' | 'radio', categories: StalkerCategoryItem[]): void`
- `resetCategories(): void`
- `setItvChannels(channels: StalkerItvChannel[]): void`
- `setRadioChannels(channels: StalkerItvChannel[]): void`
- `setSearchPhrase(phrase: string): void`
- `fetchVodSeriesEpisodes(videoId: string, seasonId: string): Promise<StalkerVodSeriesEpisode[]>`
- `getSelectedCategory(): { id: string | number; name: string; type: 'vod' | 'itv' | 'series' }`
- `getSelectedCategory(): { id: string | number; name: string; type: 'vod' | 'itv' | 'radio' | 'series' }`
Backed by `withComputed` for compatibility, not by `withMethods`.
- `fetchLinkToPlay(portalUrl: string, macAddress: string, cmd: string, series?: number): Promise<string>`
- `getExpireDate(): Promise<string>`
- `resolveItvPlayback(item: StalkerPortalItem): Promise<ResolvedPortalPlayback>`
- `resolveRadioPlayback(item: StalkerPortalItem): Promise<ResolvedPortalPlayback>`
- `addToFavorites(item: any, onDone?: () => void): void`
- `removeFromFavorites(favoriteId: string, onDone?: () => void): void`
- `fetchMovieFileId(movieId: string): Promise<string | null>`
@@ -122,6 +127,9 @@ Consumer directories sampled:
- `setSelectedCategory(...)` resets `page` to `0`.
- `getPaginatedContent()` and `getCategoryResource()` always return arrays,
even when the underlying request fails.
- Radio stores stations separately in `radioChannels` and falls back to a
synthetic all-radio category when a portal does not support radio category
responses.
- Request failures must surface through `isPaginatedContentFailed()` and
`isCategoryResourceFailed()` rather than resource reads that throw.
- `createLinkToPlayVod(...)` continues to:
+1 -1
View File
@@ -90,7 +90,7 @@ The shell decides which secondary panel to show from the current route:
1. `WorkspaceSourcesFiltersPanelComponent`
2. Xtream category sections (`live`, `vod`, `series`)
1. `WorkspaceContextPanelComponent`
3. Stalker category sections (`itv`, `vod`, `series`)
3. Stalker category sections (`itv`, `radio`, `vod`, `series`)
1. `WorkspaceContextPanelComponent`
4. `/workspace/settings`
1. `WorkspaceSettingsContextPanelComponent`