Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ This is an Nx monorepo with the following structure:
- **ui/shared-portals** - Shared portal types (`LiveEpgPanelSummary`)
- **ui/styles** - Shared styles/theme
- **workspace/{shell,dashboard}** - Workspace shell (layout/navigation) and dashboard
- **workspace/multiview/feature** - Multiview grid page (simultaneous live streams)

### Frontend Architecture (Angular)

Expand Down Expand Up @@ -429,6 +430,7 @@ See `docs/architecture/m3u-playlist-module.md` for complete documentation.
- Xtream Codes: `/workspace/xtreams/:id` (children: `live`, `vod`, `series`, `search`, `actor/:personId`, `recently-added`, `favorites`, `recent`, `downloads`) — `libs/portal/xtream/feature/src/lib/xtream-feature.routes.ts`
- Stalker portal: `/workspace/stalker/:id` (children: `itv`, `vod`, `radio`, `series`, `favorites`, `recent`, `search`, `actor/:personId`, `downloads`) — `libs/portal/stalker/feature/src/lib/stalker-feature.routes.ts`
- Global collections: `/workspace/global-favorites`, `/workspace/global-recent`
- Multiview grid: `/workspace/multiview` (Electron and PWA) — `libs/workspace/multiview/feature`
- Global search: `/workspace/search` (Electron-only; a guard redirects the PWA to `/workspace/sources`)
- Downloads: `/workspace/downloads`
- Settings: `/workspace/settings` (`/settings` redirects there)
Expand Down Expand Up @@ -790,6 +792,17 @@ engine` (restart required) or
- Keyboard shortcuts: ArrowUp/ArrowDown (volume), M (mute)
- Component: `libs/ui/playback/src/lib/audio-player/audio-player.component.ts`

**Multiview** (`/workspace/multiview`, Electron and PWA):

- Grid that plays several live TV channels simultaneously; live-only (radio, VOD, and series are excluded)
- Layout presets switchable at runtime: 1×2, 2×2, 1 large + 3 small, 3×3 (`multiview-layouts.ts`)
- Channels come from global favorites and recently viewed across all three sources (M3U, Xtream, Stalker) via `UnifiedFavoritesDataService`/`UnifiedRecentDataService`; playback URLs resolve through `StreamResolverService.resolvePlayback`
- Exactly one tile has audio focus (highlighted border); click focuses a tile, double-click opens the channel in the regular full player via the global-collection handoff (`buildLiveCollectionNavigationTarget`)
- Each tile runs a minimal standalone media engine (`MultiviewTileEngine`: mpegts.js for raw TS, hls.js with small buffers, native `<video>` fallback) — deliberately not the full `WebPlayerViewComponent` stack (which is a per-app singleton with global shortcuts/volume)
- Per-tile error + retry (retry re-resolves the URL, important for expiring Xtream/Stalker tokens); a dismissible hint warns when two tiles share the same portal account (provider connection limits)
- Layout, slots, and audio focus persist in `localStorage` (`multiview-state-v1`)
- Library: `libs/workspace/multiview/feature` (`MultiviewPageComponent` provided the route; component-provided `MultiviewStateService`)

**EPG (Electronic Program Guide)**:

- XMLTV format support
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ export const routes: Routes = [
(c) => c.GlobalCollectionRouteComponent
),
},
{
path: 'multiview',
loadComponent: () =>
import('@iptvnator/workspace/multiview/feature').then(
(c) => c.MultiviewPageComponent
),
},
{
path: 'search',
canActivate: [electronOnlyGlobalSearchGuard],
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "التحكم عن بعد في IPTVNator",
"FOOTER": "التحكم عن بعد في IPTVNator"
},
"MULTIVIEW": {
"TITLE": "العرض المتعدد",
"ADD_CHANNEL": "إضافة قناة",
"REMOVE_TILE": "إزالة المربع",
"LAYOUT": "التخطيط",
"LAYOUT_1X2": "شبكة 1×2",
"LAYOUT_2X2": "شبكة 2×2",
"LAYOUT_1_PLUS_3": "1 كبير + 3 صغيرة",
"LAYOUT_3X3": "شبكة 3×3",
"EMPTY_HINT": "أضف قنوات مباشرة لمشاهدتها في وقت واحد.",
"TILE_ERROR": "فشل تشغيل هذه القناة.",
"RETRY": "إعادة المحاولة",
"PICKER_TITLE": "اختر قناة",
"PICKER_FAVORITES": "المفضلة",
"PICKER_RECENT": "تمت مشاهدتها مؤخرًا",
"PICKER_SEARCH": "البحث عن قنوات...",
"PICKER_EMPTY": "لم يتم العثور على قنوات مباشرة.",
"CONNECTION_LIMIT_HINT": "تستخدم عدة مربعات نفس المزود. يحدّ معظم المزودين من عدد التدفقات المتزامنة، لذا قد تفشل بعض المربعات."
},
"WORKSPACE": {
"FAVORITES_ONLY": "المفضلة فقط",
"SORT_CUSTOM": "ترتيب مخصص",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "فتح المصادر",
"RAIL_GLOBAL_FAVORITES": "المفضلة",
"RAIL_GLOBAL_RECENT": "تمت مشاهدتها مؤخرًا",
"RAIL_MULTIVIEW": "العرض المتعدد",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "الإعدادات",
"OPEN_SETTINGS": "فتح الإعدادات",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/ary.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "التحكم عن بعد IPTVNator",
"FOOTER": "التحكم عن بعد IPTVNator"
},
"MULTIVIEW": {
"TITLE": "العرض المتعدد",
"ADD_CHANNEL": "زيد قناة",
"REMOVE_TILE": "حيد المربع",
"LAYOUT": "التخطيط",
"LAYOUT_1X2": "شبكة 1×2",
"LAYOUT_2X2": "شبكة 2×2",
"LAYOUT_1_PLUS_3": "1 كبير + 3 صغار",
"LAYOUT_3X3": "شبكة 3×3",
"EMPTY_HINT": "زيد قنوات مباشرة باش تشوفهم فنفس الوقت.",
"TILE_ERROR": "ماقدرناش نشغلو هاد القناة.",
"RETRY": "عاود حاول",
"PICKER_TITLE": "ختار قناة",
"PICKER_FAVORITES": "المفضلة",
"PICKER_RECENT": "متشافين مؤخراً",
"PICKER_SEARCH": "قلب على القنوات...",
"PICKER_EMPTY": "ماكاينش قنوات مباشرة.",
"CONNECTION_LIMIT_HINT": "بزاف ديال المربعات كيستعملو نفس المزود. أغلب المزودين كيحدّو عدد البثوث المتزامنة، لهذا ممكن شي مربعات ما يخدموش."
},
"WORKSPACE": {
"FAVORITES_ONLY": "المفضلة فقط",
"SORT_CUSTOM": "ترتيب مخصص",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "حل المصادر",
"RAIL_GLOBAL_FAVORITES": "المفضلة",
"RAIL_GLOBAL_RECENT": "متشافين مؤخراً",
"RAIL_MULTIVIEW": "العرض المتعدد",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "الإعدادات",
"OPEN_SETTINGS": "حل الإعدادات",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/by.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "Аддаленае кіраванне IPTVNator",
"FOOTER": "Аддаленае кіраванне IPTVNator"
},
"MULTIVIEW": {
"TITLE": "Мультыэкран",
"ADD_CHANNEL": "Дадаць канал",
"REMOVE_TILE": "Выдаліць плітку",
"LAYOUT": "Раскладка",
"LAYOUT_1X2": "Сетка 1×2",
"LAYOUT_2X2": "Сетка 2×2",
"LAYOUT_1_PLUS_3": "1 вялікая + 3 малыя",
"LAYOUT_3X3": "Сетка 3×3",
"EMPTY_HINT": "Дадайце жывыя каналы, каб глядзець некалькі трансляцый адначасова.",
"TILE_ERROR": "Не атрымалася прайграць гэты канал.",
"RETRY": "Паўтарыць",
"PICKER_TITLE": "Выберыце канал",
"PICKER_FAVORITES": "Абранае",
"PICKER_RECENT": "Нядаўна праглянутае",
"PICKER_SEARCH": "Пошук каналаў...",
"PICKER_EMPTY": "Жывыя каналы не знойдзены.",
"CONNECTION_LIMIT_HINT": "Некалькі плітак выкарыстоўваюць аднаго правайдара. Большасць правайдараў абмяжоўваюць адначасовыя патокі, таму некаторыя пліткі могуць не працаваць."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Толькі выбранае",
"SORT_CUSTOM": "Свой парадак",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Адкрыць крыніцы",
"RAIL_GLOBAL_FAVORITES": "Выбранае",
"RAIL_GLOBAL_RECENT": "Нядаўна праглянутае",
"RAIL_MULTIVIEW": "Мультыэкран",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Налады",
"OPEN_SETTINGS": "Адкрыць налады",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "IPTVNator Fernbedienung",
"FOOTER": "IPTVNator Fernbedienung"
},
"MULTIVIEW": {
"TITLE": "Multiview",
"ADD_CHANNEL": "Sender hinzufügen",
"REMOVE_TILE": "Kachel entfernen",
"LAYOUT": "Layout",
"LAYOUT_1X2": "1×2-Raster",
"LAYOUT_2X2": "2×2-Raster",
"LAYOUT_1_PLUS_3": "1 groß + 3 klein",
"LAYOUT_3X3": "3×3-Raster",
"EMPTY_HINT": "Füge Live-Sender hinzu, um mehrere Streams gleichzeitig zu sehen.",
"TILE_ERROR": "Wiedergabe für diesen Sender fehlgeschlagen.",
"RETRY": "Erneut versuchen",
"PICKER_TITLE": "Sender auswählen",
"PICKER_FAVORITES": "Favoriten",
"PICKER_RECENT": "Zuletzt angesehen",
"PICKER_SEARCH": "Sender suchen...",
"PICKER_EMPTY": "Keine Live-Sender gefunden.",
"CONNECTION_LIMIT_HINT": "Mehrere Kacheln nutzen denselben Anbieter. Die meisten Anbieter begrenzen gleichzeitige Streams, daher können einzelne Kacheln fehlschlagen."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Nur Favoriten",
"SORT_CUSTOM": "Eigene Reihenfolge",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Quellen öffnen",
"RAIL_GLOBAL_FAVORITES": "Favoriten",
"RAIL_GLOBAL_RECENT": "Zuletzt angesehen",
"RAIL_MULTIVIEW": "Multiview",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Einstellungen",
"OPEN_SETTINGS": "Einstellungen öffnen",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "IPTVNator Τηλεχειριστήριο",
"FOOTER": "IPTVNator Τηλεχειριστήριο"
},
"MULTIVIEW": {
"TITLE": "Πολλαπλή προβολή",
"ADD_CHANNEL": "Προσθήκη καναλιού",
"REMOVE_TILE": "Αφαίρεση πλακιδίου",
"LAYOUT": "Διάταξη",
"LAYOUT_1X2": "Πλέγμα 1×2",
"LAYOUT_2X2": "Πλέγμα 2×2",
"LAYOUT_1_PLUS_3": "1 μεγάλο + 3 μικρά",
"LAYOUT_3X3": "Πλέγμα 3×3",
"EMPTY_HINT": "Προσθέστε ζωντανά κανάλια για να παρακολουθείτε πολλές ροές ταυτόχρονα.",
"TILE_ERROR": "Η αναπαραγωγή απέτυχε για αυτό το κανάλι.",
"RETRY": "Επανάληψη",
"PICKER_TITLE": "Επιλέξτε κανάλι",
"PICKER_FAVORITES": "Αγαπημένα",
"PICKER_RECENT": "Πρόσφατα προβληθέντα",
"PICKER_SEARCH": "Αναζήτηση καναλιών...",
"PICKER_EMPTY": "Δεν βρέθηκαν ζωντανά κανάλια.",
"CONNECTION_LIMIT_HINT": "Πολλά πλακίδια χρησιμοποιούν τον ίδιο πάροχο. Οι περισσότεροι πάροχοι περιορίζουν τις ταυτόχρονες ροές, οπότε ορισμένα πλακίδια ενδέχεται να αποτύχουν."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Μόνο αγαπημένα",
"SORT_CUSTOM": "Προσαρμοσμένη σειρά",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Άνοιγμα πηγών",
"RAIL_GLOBAL_FAVORITES": "Αγαπημένα",
"RAIL_GLOBAL_RECENT": "Πρόσφατα προβληθέντα",
"RAIL_MULTIVIEW": "Πολλαπλή προβολή",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Ρυθμίσεις",
"OPEN_SETTINGS": "Άνοιγμα ρυθμίσεων",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "IPTVNator Remote Control",
"FOOTER": "IPTVNator Remote Control"
},
"MULTIVIEW": {
"TITLE": "Multiview",
"ADD_CHANNEL": "Add channel",
"REMOVE_TILE": "Remove tile",
"LAYOUT": "Layout",
"LAYOUT_1X2": "1×2 grid",
"LAYOUT_2X2": "2×2 grid",
"LAYOUT_1_PLUS_3": "1 large + 3 small",
"LAYOUT_3X3": "3×3 grid",
"EMPTY_HINT": "Add live channels to watch multiple streams at once.",
"TILE_ERROR": "Playback failed for this channel.",
"RETRY": "Retry",
"PICKER_TITLE": "Select a channel",
"PICKER_FAVORITES": "Favorites",
"PICKER_RECENT": "Recently viewed",
"PICKER_SEARCH": "Search channels...",
"PICKER_EMPTY": "No live channels found.",
"CONNECTION_LIMIT_HINT": "Multiple tiles use the same provider. Most providers limit concurrent streams, so some tiles may fail."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Favorites only",
"SORT_CUSTOM": "Custom order",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Open sources",
"RAIL_GLOBAL_FAVORITES": "Favorites",
"RAIL_GLOBAL_RECENT": "Recently viewed",
"RAIL_MULTIVIEW": "Multiview",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Settings",
"OPEN_SETTINGS": "Open settings",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "Control remoto de IPTVnator",
"FOOTER": "Control remoto de IPTVnator"
},
"MULTIVIEW": {
"TITLE": "Vista múltiple",
"ADD_CHANNEL": "Añadir canal",
"REMOVE_TILE": "Quitar mosaico",
"LAYOUT": "Diseño",
"LAYOUT_1X2": "Cuadrícula 1×2",
"LAYOUT_2X2": "Cuadrícula 2×2",
"LAYOUT_1_PLUS_3": "1 grande + 3 pequeños",
"LAYOUT_3X3": "Cuadrícula 3×3",
"EMPTY_HINT": "Añade canales en directo para ver varias transmisiones a la vez.",
"TILE_ERROR": "Error de reproducción en este canal.",
"RETRY": "Reintentar",
"PICKER_TITLE": "Seleccionar un canal",
"PICKER_FAVORITES": "Favoritos",
"PICKER_RECENT": "Vistos recientemente",
"PICKER_SEARCH": "Buscar canales...",
"PICKER_EMPTY": "No se encontraron canales en directo.",
"CONNECTION_LIMIT_HINT": "Varios mosaicos usan el mismo proveedor. La mayoría de los proveedores limitan las transmisiones simultáneas, por lo que algunos mosaicos pueden fallar."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Solo favoritos",
"SORT_CUSTOM": "Orden personalizado",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Abrir fuentes",
"RAIL_GLOBAL_FAVORITES": "Favoritos",
"RAIL_GLOBAL_RECENT": "Vistos recientemente",
"RAIL_MULTIVIEW": "Vista múltiple",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Ajustes",
"OPEN_SETTINGS": "Abrir ajustes",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "Télécommande IPTVNator",
"FOOTER": "Télécommande IPTVNator"
},
"MULTIVIEW": {
"TITLE": "Multivue",
"ADD_CHANNEL": "Ajouter une chaîne",
"REMOVE_TILE": "Supprimer la tuile",
"LAYOUT": "Disposition",
"LAYOUT_1X2": "Grille 1×2",
"LAYOUT_2X2": "Grille 2×2",
"LAYOUT_1_PLUS_3": "1 grande + 3 petites",
"LAYOUT_3X3": "Grille 3×3",
"EMPTY_HINT": "Ajoutez des chaînes en direct pour regarder plusieurs flux à la fois.",
"TILE_ERROR": "Échec de la lecture pour cette chaîne.",
"RETRY": "Réessayer",
"PICKER_TITLE": "Sélectionner une chaîne",
"PICKER_FAVORITES": "Favoris",
"PICKER_RECENT": "Vus récemment",
"PICKER_SEARCH": "Rechercher des chaînes...",
"PICKER_EMPTY": "Aucune chaîne en direct trouvée.",
"CONNECTION_LIMIT_HINT": "Plusieurs tuiles utilisent le même fournisseur. La plupart des fournisseurs limitent les flux simultanés, certaines tuiles peuvent donc échouer."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Favoris uniquement",
"SORT_CUSTOM": "Ordre personnalisé",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Ouvrir les sources",
"RAIL_GLOBAL_FAVORITES": "Favoris",
"RAIL_GLOBAL_RECENT": "Récemment consultés",
"RAIL_MULTIVIEW": "Multivue",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Paramètres",
"OPEN_SETTINGS": "Ouvrir les paramètres",
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/assets/i18n/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,25 @@
"HEADER": "Controllo remoto IPTVNator",
"FOOTER": "Controllo remoto IPTVNator"
},
"MULTIVIEW": {
"TITLE": "Vista multipla",
"ADD_CHANNEL": "Aggiungi canale",
"REMOVE_TILE": "Rimuovi riquadro",
"LAYOUT": "Layout",
"LAYOUT_1X2": "Griglia 1×2",
"LAYOUT_2X2": "Griglia 2×2",
"LAYOUT_1_PLUS_3": "1 grande + 3 piccoli",
"LAYOUT_3X3": "Griglia 3×3",
"EMPTY_HINT": "Aggiungi canali live per guardare più stream contemporaneamente.",
"TILE_ERROR": "Riproduzione non riuscita per questo canale.",
"RETRY": "Riprova",
"PICKER_TITLE": "Seleziona un canale",
"PICKER_FAVORITES": "Preferiti",
"PICKER_RECENT": "Visti di recente",
"PICKER_SEARCH": "Cerca canali...",
"PICKER_EMPTY": "Nessun canale live trovato.",
"CONNECTION_LIMIT_HINT": "Più riquadri usano lo stesso provider. La maggior parte dei provider limita gli stream simultanei, quindi alcuni riquadri potrebbero non funzionare."
},
"WORKSPACE": {
"FAVORITES_ONLY": "Solo preferiti",
"SORT_CUSTOM": "Ordine personalizzato",
Expand Down Expand Up @@ -1063,6 +1082,7 @@
"OPEN_SOURCES": "Apri sorgenti",
"RAIL_GLOBAL_FAVORITES": "Preferiti",
"RAIL_GLOBAL_RECENT": "Visti di recente",
"RAIL_MULTIVIEW": "Vista multipla",
"RAIL_GLOBAL_SEARCH": "Global search",
"RAIL_SETTINGS": "Impostazioni",
"OPEN_SETTINGS": "Apri impostazioni",
Expand Down
Loading
Loading