mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(stalker): add radio support
Entire-Checkpoint: f957cd9849e0
This commit is contained in:
1 parent
e9875f9f29
commit
ab905af285
59 files changed
+1059
-179
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in new issue
Block a user