687 lines
30 KiB
Markdown
687 lines
30 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Plan Mode
|
||
|
||
- When Claude Code is in Plan Mode and produces a final `<proposed_plan>`, it must also save that finalized plan as a Markdown file in the repo-root `.plans/` directory.
|
||
- Save only finalized plans. Do not write interim exploration, question turns, or draft revisions to `.plans/`.
|
||
- Use the filename pattern `YYYY-MM-DD-short-topic.md` such as `.plans/2026-03-12-channel-filtering.md`.
|
||
- If the intended filename already exists, append a numeric suffix such as `-2`, `-3`, and so on.
|
||
|
||
## Documentation After Changes
|
||
|
||
- After implementing a meaningful change, Claude Code must assess whether canonical repo docs need updates before considering the task complete.
|
||
- Meaningful changes include new or changed user-visible behavior, architecture or data-flow changes, non-obvious maintenance workflows, new setup/debugging steps, and new subsystem contracts or boundaries.
|
||
- Skip doc updates for trivial refactors with unchanged behavior, formatting-only edits, and isolated test-only changes.
|
||
- Prefer updating an existing authoritative doc before creating a new one:
|
||
1. `README.md` for top-level developer or user workflows
|
||
2. `docs/architecture/` for architecture, ownership, and behavior contracts
|
||
3. the nearest module `README.md` for local usage or behavior
|
||
- Repo docs are canonical even when they were originally drafted by an LLM. External wiki pages are derivative or synthesis content unless explicitly promoted back into the repo.
|
||
- The external wiki sync is one-way by default: repo docs -> external wiki `_repo-context/`.
|
||
- If repo docs changed and `IPTVNATOR_WIKI_VAULT` is configured, run `pnpm wiki:export --mode changed` after the doc update.
|
||
- The wiki exporter only owns `_repo-context/` in the external vault. It must never overwrite repo docs or maintained wiki pages outside that folder.
|
||
- Final task summaries should state whether docs were updated, which doc changed, and whether wiki export ran, was skipped, or failed.
|
||
|
||
## Project Overview
|
||
|
||
IPTVnator is a cross-platform IPTV player application built with Angular and Electron, supporting M3U/M3U8 playlists, Xtream Codes API, and Stalker portals.
|
||
|
||
**Dual Environment Support**: The application is designed to work in both Electron and as a Progressive Web App (PWA). The architecture uses a factory pattern to inject environment-specific services at runtime, ensuring the same codebase works in both contexts.
|
||
|
||
## Development Commands
|
||
|
||
### Building and Serving
|
||
|
||
```bash
|
||
# Serve the Angular web app only (development mode, baseHref="/")
|
||
pnpm run serve:frontend
|
||
# or
|
||
nx serve web
|
||
|
||
# Serve with PWA configuration (optimized, baseHref="/")
|
||
pnpm run serve:frontend:pwa
|
||
# or
|
||
nx serve web --configuration=pwa
|
||
|
||
# Serve the Electron app (starts both frontend and backend)
|
||
pnpm run serve:backend
|
||
# or
|
||
nx serve electron-backend
|
||
|
||
# Build frontend for Electron (baseHref="./")
|
||
pnpm run build:frontend
|
||
# or
|
||
nx build web
|
||
|
||
# Build frontend for PWA deployment (baseHref="/")
|
||
pnpm run build:frontend:pwa
|
||
# or
|
||
nx build web --configuration=pwa
|
||
|
||
# Build backend (Electron)
|
||
pnpm run build:backend
|
||
# or
|
||
nx build electron-backend
|
||
|
||
# Package the app (creates distributable without installers)
|
||
pnpm run package:app
|
||
# or
|
||
nx run electron-backend:package
|
||
|
||
# Create installers/executables
|
||
pnpm run make:app
|
||
# or
|
||
nx run electron-backend:make
|
||
```
|
||
|
||
### Electron CDP Debugging
|
||
|
||
- Start Electron in dev mode with: `nx serve electron-backend`
|
||
- Package-script equivalent: `pnpm run serve:backend`
|
||
- The workspace is configured to always launch Electron with: `--remote-debugging-port=9222`
|
||
- Use CDP clients (Chrome DevTools Protocol tools) against: `127.0.0.1:9222`
|
||
- When the task is Electron automation/debugging, use the `electron` skill
|
||
|
||
For startup tracing or white-screen debugging:
|
||
|
||
```bash
|
||
IPTVNATOR_TRACE_STARTUP=1 nx serve electron-backend
|
||
```
|
||
|
||
Useful narrower flags:
|
||
|
||
- `IPTVNATOR_TRACE_IPC=1` traces renderer `window.electron.*` bridge calls
|
||
- `IPTVNATOR_TRACE_DB=1` traces DB worker requests and DB progress events
|
||
- `IPTVNATOR_TRACE_SQL=1` traces SQLite statements in both main and worker connections
|
||
- `IPTVNATOR_TRACE_WINDOW=1` traces BrowserWindow navigation/load lifecycle
|
||
- `IPTVNATOR_TRACE_RENDERER_CONSOLE=1` mirrors renderer console logs into the Electron terminal
|
||
|
||
For GPU/compositor debugging:
|
||
|
||
```bash
|
||
IPTVNATOR_DISABLE_HARDWARE_ACCELERATION=1 nx serve electron-backend
|
||
```
|
||
|
||
If the Nx daemon gets into a bad state before rerunning Electron:
|
||
|
||
```bash
|
||
pnpm nx reset
|
||
```
|
||
|
||
Use global `agent-browser` (preferred):
|
||
|
||
```bash
|
||
# Verify CDP targets
|
||
agent-browser --cdp 9222 tab list
|
||
|
||
# Switch to the app tab and inspect interactive elements
|
||
agent-browser --cdp 9222 tab 1
|
||
agent-browser --cdp 9222 snapshot -i -c -d 4
|
||
|
||
# Capture debug artifacts
|
||
agent-browser --cdp 9222 screenshot /tmp/iptvnator-cdp.png
|
||
agent-browser --cdp 9222 trace start /tmp/iptvnator.trace.zip
|
||
agent-browser --cdp 9222 wait 1500
|
||
agent-browser --cdp 9222 trace stop /tmp/iptvnator.trace.zip
|
||
```
|
||
|
||
If `agent-browser` is not in PATH, use:
|
||
|
||
```bash
|
||
npx --yes agent-browser --cdp 9222 tab list
|
||
```
|
||
|
||
### Testing
|
||
|
||
```bash
|
||
# Run frontend tests
|
||
pnpm run test:frontend
|
||
# or
|
||
nx test web
|
||
|
||
# Run backend tests
|
||
pnpm run test:backend
|
||
# or
|
||
nx test electron-backend
|
||
|
||
# Run e2e tests (Playwright)
|
||
nx e2e web-e2e
|
||
|
||
# Run tests with coverage
|
||
nx test web --configuration=ci
|
||
```
|
||
|
||
### Linting
|
||
|
||
```bash
|
||
# Lint frontend
|
||
nx lint web
|
||
|
||
# Lint backend
|
||
nx lint electron-backend
|
||
```
|
||
|
||
### Documentation And Wiki Export
|
||
|
||
```bash
|
||
# Export a full canonical-doc snapshot into the external Obsidian vault
|
||
pnpm wiki:export --mode full
|
||
|
||
# Export only the latest repo context into the external Obsidian vault
|
||
pnpm wiki:export --mode changed
|
||
```
|
||
|
||
## Architecture
|
||
|
||
### Monorepo Structure (Nx Workspace)
|
||
|
||
This is an Nx monorepo with the following structure:
|
||
|
||
- **apps/web** - Angular application (frontend)
|
||
- **apps/electron-backend** - Electron main process
|
||
- **apps/web-e2e** - Playwright end-to-end tests
|
||
- **libs/** - Shared libraries:
|
||
- **m3u-state** - NgRx state management for playlists
|
||
- **services** - Abstract DataService and implementations
|
||
- **shared/interfaces** - TypeScript interfaces and types
|
||
- **shared/m3u-utils** - M3U playlist utilities
|
||
- **ui/components** - Reusable UI components
|
||
- **ui/pipes** - Angular pipes
|
||
- **ui/shared-portals** - Portal-related UI components
|
||
|
||
### Frontend Architecture (Angular)
|
||
|
||
**State Management**: Uses NgRx for playlist state management:
|
||
|
||
- Store configuration in `apps/web/src/app/app.config.ts`
|
||
- Playlist state, actions, effects, and reducers in `libs/m3u-state/`
|
||
- Entity adapter pattern for managing playlists collection
|
||
- Router store integration for route-based state
|
||
|
||
**XtreamStore Architecture** (Signal Store with Feature Composition):
|
||
|
||
The Xtream Codes module uses NgRx Signal Store with a layered architecture:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ PRESENTATION LAYER │
|
||
│ Components use XtreamStore (facade) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ FACADE LAYER │
|
||
│ XtreamStore │
|
||
│ (Composes feature stores, unified API) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌────────────┬────────────┼────────────┬────────────┐
|
||
▼ ▼ ▼ ▼ ▼
|
||
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
|
||
│ withPortal│ │withContent │ │withSelection│ │ withSearch │ │ withPlayer │
|
||
└────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘
|
||
│ │ │
|
||
└───────────────────────────┼──────────────┘
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ DATA SOURCE LAYER │
|
||
│ IXtreamDataSource │
|
||
│ ┌───────────────────┬───────────────────┐ │
|
||
│ ▼ ▼ │
|
||
│ ElectronDataSource PwaDataSource │
|
||
│ (DB-first + API) (API-only) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
File structure:
|
||
|
||
```
|
||
apps/web/src/app/xtream-electron/
|
||
├── stores/
|
||
│ ├── features/
|
||
│ │ ├── with-portal.feature.ts # Playlist & portal status
|
||
│ │ ├── with-content.feature.ts # Categories & streams
|
||
│ │ ├── with-selection.feature.ts # UI selection & pagination
|
||
│ │ ├── with-search.feature.ts # Search functionality
|
||
│ │ ├── with-epg.feature.ts # EPG data
|
||
│ │ ├── with-player.feature.ts # Stream URLs & player
|
||
│ │ └── index.ts
|
||
│ ├── xtream.store.ts # Facade composing all features
|
||
│ └── index.ts
|
||
├── services/
|
||
│ ├── xtream-api.service.ts # Xtream Codes API calls
|
||
│ ├── xtream-url.service.ts # Stream URL construction
|
||
│ └── index.ts
|
||
├── data-sources/
|
||
│ ├── xtream-data-source.interface.ts # Abstract interface + types
|
||
│ ├── electron-xtream-data-source.ts # DB-first implementation
|
||
│ ├── pwa-xtream-data-source.ts # API-only implementation
|
||
│ └── index.ts # Factory provider
|
||
└── with-favorites.feature.ts # Favorites (existing)
|
||
└── with-recent-items.ts # Recently viewed (existing)
|
||
```
|
||
|
||
Key patterns:
|
||
|
||
- **Feature stores**: Each `with*.feature.ts` uses `signalStoreFeature()` for focused functionality
|
||
- **Facade pattern**: `XtreamStore` composes all features, maintaining backward compatibility
|
||
- **Data source abstraction**: `IXtreamDataSource` interface with environment-specific implementations
|
||
- **Factory injection**: `provideXtreamDataSource()` selects Electron or PWA implementation at runtime
|
||
|
||
Data strategies by environment:
|
||
| Environment | Strategy |
|
||
|-------------|----------|
|
||
| **Electron** | DB-first: Check DB → fetch API if missing → cache to DB |
|
||
| **PWA** | API-only: Always fetch from API, store in memory |
|
||
|
||
**M3U Playlist Module Architecture**:
|
||
|
||
The M3U playlist module handles traditional M3U/M3U8 playlists with support for 90,000+ channels.
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ VIDEO PLAYER PAGE │
|
||
│ apps/web/src/app/home/video-player/ │
|
||
├─────────────────────────────────────────────────────────────────────┤
|
||
│ ┌─────────────┐ ┌──────────────────────┐ ┌────────────────────┐ │
|
||
│ │ Sidebar │ │ Video Player │ │ EPG List │ │
|
||
│ │ │ │ (ArtPlayer/Video.js)│ │ (Right drawer) │ │
|
||
│ │ ┌─────────┐ │ │ │ │ │ │
|
||
│ │ │Channel │ │ │ │ │ │ │
|
||
│ │ │List │ │ │ │ │ │ │
|
||
│ │ │Container│ │ │ │ │ │ │
|
||
│ │ └─────────┘ │ │ │ │ │ │
|
||
│ └─────────────┘ └──────────────────────┘ └────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Radio Channel Layout** (when `channel.radio === 'true'`):
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ ┌─────────────┐ ┌────────────────────────────────────────────────┐│
|
||
│ │ Sidebar │ │ Blurred backdrop (station logo) ││
|
||
│ │ │ │ ┌──────────┐ ││
|
||
│ │ │ │ │ Artwork │ ← cinematic hero layout ││
|
||
│ │ │ │ └──────────┘ ││
|
||
│ │ │ │ Station Name ││
|
||
│ │ │ │ [LIVE] badge ││
|
||
│ │ │ │ ⏮ ▶/⏸ ⏭ ← transport controls ││
|
||
│ │ │ │ 🔊 ━━━━━━━━━ ← volume slider ││
|
||
│ │ │ │ (no EPG panel) ││
|
||
│ └─────────────┘ └────────────────────────────────────────────────┘│
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
Key radio behavior:
|
||
|
||
- Detection: `channel.radio === 'true'` (string from M3U `radio` attribute)
|
||
- The audio player always renders inline — `shouldShowInlinePlayer` is bypassed for radio
|
||
- EPG panel is conditionally hidden in the template when radio is active
|
||
- Volume is shared with video player via `localStorage` key `'volume'`
|
||
- Keyboard: ArrowUp/Down adjusts volume by 5%, M toggles mute
|
||
- Component: `libs/ui/playback/src/lib/audio-player/audio-player.component.ts`
|
||
|
||
Channel List Component Structure (parent coordinator pattern):
|
||
|
||
```
|
||
libs/ui/components/src/lib/channel-list-container/
|
||
├── channel-list-container.component.ts # Parent - shared state coordinator
|
||
├── all-channels-tab/ # Virtual scroll + debounced search
|
||
├── groups-tab/ # Expansion panels + infinite scroll
|
||
├── favorites-tab/ # CDK drag-drop reordering
|
||
└── channel-list-item/ # Individual channel display
|
||
```
|
||
|
||
Key patterns:
|
||
|
||
- **EnrichedChannel**: Pre-computed EPG data attached to channels for performance
|
||
- **Parent coordinator**: Manages shared signals (`channelEpgMap`, `progressTick`, `favoriteIds`)
|
||
- **Virtual scrolling**: CDK virtual scroll for 90,000+ channel lists
|
||
- **Infinite scroll**: IntersectionObserver in groups tab loads 50 items at a time
|
||
- **Global progress tick**: Single 30s interval instead of per-item intervals
|
||
|
||
State management via NgRx (`libs/m3u-state/`):
|
||
|
||
- `PlaylistActions`: loadPlaylists, addPlaylist, removePlaylist, parsePlaylist
|
||
- `ChannelActions`: setChannels, setActiveChannel, setAdjacentChannelAsActive
|
||
- `EpgActions`: setActiveEpgProgram, setCurrentEpgProgram, setEpgAvailableFlag
|
||
- `FavoritesActions`: updateFavorites, setFavorites
|
||
|
||
See `docs/architecture/m3u-playlist-module.md` for complete documentation.
|
||
|
||
**Routing**: Lazy-loaded routes in `apps/web/src/app/app.routes.ts`
|
||
|
||
- Home/playlists overview: `/`
|
||
- Video player: `/playlists/:id` or `/iptv`
|
||
- Xtream Codes: `/xtreams/:id` (different routes for Electron vs web)
|
||
- Stalker portal: `/portals/:id`
|
||
- Settings: `/settings`
|
||
|
||
**Service Architecture** (Factory Pattern):
|
||
|
||
- Abstract `DataService` class in `libs/services/src/lib/data.service.ts` defines the contract
|
||
- Two environment-specific implementations:
|
||
- `ElectronService` (`apps/web/src/app/services/electron.service.ts`) - Uses IPC to communicate with Electron backend
|
||
- `PwaService` (`apps/web/src/app/services/pwa.service.ts`) - Uses HTTP API and IndexedDB for standalone web version
|
||
- Factory function `DataFactory()` in `apps/web/src/app/app.config.ts` determines which implementation to inject:
|
||
```typescript
|
||
if (window.electron) {
|
||
return new ElectronService();
|
||
}
|
||
return new PwaService();
|
||
```
|
||
|
||
**Data Storage (Environment-Specific)**:
|
||
|
||
- **Electron**: libSQL/SQLite database via Drizzle ORM
|
||
- Location: `~/.iptvnator/databases/iptvnator.db`
|
||
- Full-featured relational database with foreign keys and indexes
|
||
- Supports local file or remote Turso instance via env vars
|
||
- **PWA (Web)**: IndexedDB via `ngx-indexed-db`
|
||
- Browser-based NoSQL storage
|
||
- Same schema structure but implemented in IndexedDB
|
||
- Limited by browser storage quotas
|
||
|
||
**TypeScript File Size Rule**:
|
||
|
||
Keep TypeScript files under **300 lines**. Hard maximum is **350–400 lines**.
|
||
|
||
- When creating new files, design them to stay within this limit from the start.
|
||
- When adding a feature to an existing file that would push it past 350 lines, **refactor first**: extract helpers, sub-services, or feature modules before adding the new code.
|
||
- When you notice a file already exceeds 350 lines, **proactively suggest a refactoring** (or perform it if the change is straightforward) — even if the immediate task is small.
|
||
|
||
Typical split strategies:
|
||
|
||
- Angular components: extract child components, move logic to a dedicated service or store feature
|
||
- Signal store features: split into smaller `with*` feature functions in separate files
|
||
- Services: split by responsibility (e.g. separate API, transformation, and state concerns)
|
||
- Utility files: group by domain and export from a barrel `index.ts`
|
||
|
||
This rule exists to keep the codebase navigable and reviewable. A 150-line file is always preferable to a 500-line file.
|
||
|
||
---
|
||
|
||
**Angular Coding Standards**:
|
||
|
||
This project uses modern Angular signal-based APIs and patterns. **ALWAYS** use the following:
|
||
|
||
- **Component Queries**: Use `viewChild()`, `viewChildren()`, `contentChild()`, `contentChildren()` instead of `@ViewChild`, `@ViewChildren`, `@ContentChild`, `@ContentChildren` decorators
|
||
|
||
```typescript
|
||
// ✅ Correct - Signal-based
|
||
readonly menu = viewChild.required<MatMenu>('menuRef');
|
||
readonly items = viewChildren<ElementRef>('item');
|
||
|
||
// ❌ Incorrect - Old decorator syntax
|
||
@ViewChild('menuRef') menu!: MatMenu;
|
||
@ViewChildren('item') items!: QueryList<ElementRef>;
|
||
```
|
||
|
||
**Important**: When using signals in templates with properties that expect non-signal values, unwrap the signal by calling it:
|
||
|
||
```html
|
||
<!-- ✅ Correct - Unwrap the signal -->
|
||
<button [matMenuTriggerFor]="menu()">Open Menu</button>
|
||
|
||
<!-- ❌ Incorrect - Signal not unwrapped -->
|
||
<button [matMenuTriggerFor]="menu">Open Menu</button>
|
||
```
|
||
|
||
- **Component Inputs/Outputs**: Use `input()` and `output()` functions instead of `@Input()` and `@Output()` decorators
|
||
|
||
```typescript
|
||
// ✅ Correct - Signal-based
|
||
readonly title = input.required<string>();
|
||
readonly size = input<number>(10); // with default value
|
||
readonly clicked = output<string>();
|
||
|
||
// ❌ Incorrect - Old decorator syntax
|
||
@Input({ required: true }) title!: string;
|
||
@Input() size = 10;
|
||
@Output() clicked = new EventEmitter<string>();
|
||
```
|
||
|
||
- **Reactive State**: Use signal primitives for reactive state management
|
||
|
||
```typescript
|
||
// ✅ Use signal(), computed(), effect(), linkedSignal()
|
||
readonly count = signal(0);
|
||
readonly doubled = computed(() => this.count() * 2);
|
||
|
||
constructor() {
|
||
effect(() => {
|
||
console.log('Count changed:', this.count());
|
||
});
|
||
}
|
||
```
|
||
|
||
- **Host Bindings**: Use `@HostBinding()` and `@HostListener()` decorators (these don't have signal equivalents yet)
|
||
|
||
```typescript
|
||
@HostBinding('class.active') get isActive() { return this.active(); }
|
||
@HostListener('click') onClick() { /* ... */ }
|
||
```
|
||
|
||
- **Control Flow**: Use `@if`, `@for`, `@switch` instead of `*ngIf`, `*ngFor`, `*ngSwitch`
|
||
|
||
```typescript
|
||
// ✅ Correct - Modern syntax
|
||
@if (isLoggedIn()) {
|
||
<p>Welcome!</p>
|
||
}
|
||
|
||
@for (item of items(); track item.id) {
|
||
<li>{{ item.name }}</li>
|
||
}
|
||
|
||
// ❌ Incorrect - Old syntax
|
||
<p *ngIf="isLoggedIn">Welcome!</p>
|
||
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
|
||
```
|
||
|
||
### Backend Architecture (Electron)
|
||
|
||
**Main Entry**: `apps/electron-backend/src/main.ts`
|
||
|
||
- Bootstraps Electron app and initializes database
|
||
- Registers event handlers for IPC communication
|
||
|
||
**Database**:
|
||
|
||
- **ORM**: Drizzle ORM with libSQL (local SQLite file or remote Turso)
|
||
- **Location**: `~/.iptvnator/databases/iptvnator.db` (avoids spaces in path)
|
||
- **Schema** (`apps/electron-backend/src/app/database/schema.ts`):
|
||
- `playlists` - Playlist metadata (M3U, Xtream, Stalker)
|
||
- `categories` - Content categories (live, movies, series)
|
||
- `content` - Streams/VOD/series items
|
||
- `favorites` - User favorites
|
||
- `recentlyViewed` - Watch history
|
||
- **Connection**: `apps/electron-backend/src/app/database/connection.ts`
|
||
- Auto-creates tables on init
|
||
- Supports local file or remote via env vars (`LIBSQL_URL`, `LIBSQL_AUTH_TOKEN`)
|
||
|
||
**IPC Communication**:
|
||
|
||
- **Preload script**: `apps/electron-backend/src/app/api/main.preload.ts`
|
||
- Exposes `window.electron` API via `contextBridge`
|
||
- All IPC channels defined here (playlist operations, EPG, database CRUD, external players, etc.)
|
||
- **Event handlers**: `apps/electron-backend/src/app/events/`
|
||
- `database.events.ts` - Database CRUD operations
|
||
- `playlist.events.ts` - Playlist import/update
|
||
- `epg.events.ts` - EPG fetch and parsing (uses worker)
|
||
- `xtream.events.ts` - Xtream Codes API
|
||
- `stalker.events.ts` - Stalker portal API
|
||
- `player.events.ts` - External player (MPV, VLC) integration
|
||
- `settings.events.ts` - App settings
|
||
- `electron.events.ts` - App version, etc.
|
||
|
||
**Workers**:
|
||
|
||
- EPG parsing runs in worker thread: `apps/electron-backend/src/app/workers/epg-parser.worker.ts`
|
||
|
||
### Key Features
|
||
|
||
**Playlist Support**:
|
||
|
||
- M3U/M3U8 files (local or URL)
|
||
- Xtream Codes API (`username`, `password`, `serverUrl`)
|
||
- Stalker portal (`macAddress`, `url`)
|
||
|
||
**Video Players**:
|
||
|
||
- Built-in HTML5 player with HLS.js or Video.js
|
||
- External players: MPV, VLC (via IPC to Electron backend)
|
||
|
||
**Radio Player**:
|
||
|
||
- Dedicated audio player for channels with `radio="true"` M3U attribute
|
||
- Cinematic layout: blurred station logo as backdrop, floating artwork card, transport controls
|
||
- Always uses the built-in inline player — external player settings (MPV/VLC) are ignored for radio
|
||
- EPG panel is hidden for radio channels (radio streams have no EPG data)
|
||
- Volume synced with video player via shared `localStorage` key `'volume'`
|
||
- Keyboard shortcuts: ArrowUp/ArrowDown (volume), M (mute)
|
||
- Component: `libs/ui/playback/src/lib/audio-player/audio-player.component.ts`
|
||
|
||
**EPG (Electronic Program Guide)**:
|
||
|
||
- XMLTV format support
|
||
- Background parsing in worker thread
|
||
- Stored in database for quick lookup
|
||
|
||
**Favorites and Recently Viewed**:
|
||
|
||
- Per-playlist favorites and global favorites
|
||
- Recently viewed tracks watch history
|
||
|
||
**Internationalization**:
|
||
|
||
- Uses `@ngx-translate` with 16 language files in `apps/web/src/assets/i18n/`
|
||
|
||
## Development Notes
|
||
|
||
### Environment Detection and Dual-Mode Architecture
|
||
|
||
The app determines whether it's running in Electron or as a PWA by checking:
|
||
|
||
```typescript
|
||
window.electron; // truthy in Electron, undefined in browser
|
||
```
|
||
|
||
**Why Dual Mode?**
|
||
IPTVnator supports both Electron (desktop app) and PWA (web browser) to provide flexibility:
|
||
|
||
- **Electron**: Full-featured desktop experience with local database, external player support (MPV/VLC), and native file system access
|
||
- **PWA**: Lightweight web version that runs in any browser without installation
|
||
|
||
**Environment-Specific Behavior**:
|
||
|
||
- `app.config.ts` - `DataFactory()` selects DataService implementation based on environment
|
||
- `app.routes.ts` - Different routes for Xtream portals (Electron uses Tauri-based routes, PWA uses standard routes)
|
||
- Storage layer switches automatically:
|
||
- Electron → libSQL/Drizzle ORM → `~/.iptvnator/databases/iptvnator.db`
|
||
- PWA → IndexedDB → Browser storage
|
||
- External player support (MPV/VLC) only available in Electron
|
||
- File system operations only available in Electron (uploading playlists from disk)
|
||
|
||
**Base Href Configuration**:
|
||
The app uses different base href values depending on the build target:
|
||
|
||
- **Development & PWA**: `baseHref="/"` (from `index.html`)
|
||
- Used by: `pnpm run serve:frontend`, `pnpm run build:frontend:pwa`
|
||
- For web servers with proper routing
|
||
- **Electron Production**: `baseHref="./"` (overridden in build config)
|
||
- Used by: `pnpm run build:backend`, `pnpm run make:app`
|
||
- Required for `file://` protocol in Electron
|
||
|
||
Build configurations in `apps/web/project.json`:
|
||
|
||
- `production`: Electron build with `baseHref="./"`
|
||
- `pwa`: Web deployment with `baseHref="/"`
|
||
- `development`: Dev mode with `baseHref="/"` from index.html
|
||
|
||
**Factory Pattern Implementation**:
|
||
The factory pattern ensures a single codebase works in both environments without conditional checks scattered throughout the application. All environment-specific logic is encapsulated in the service implementations.
|
||
|
||
### Testing Strategy
|
||
|
||
- **Unit tests**: Jest with `jest-preset-angular` and `ng-mocks`
|
||
- **E2E tests**: Playwright testing the web app
|
||
- Backend tests use standard Jest
|
||
|
||
### Nx Commands
|
||
|
||
Use `nx` CLI for better performance:
|
||
|
||
```bash
|
||
nx run <project>:<target>
|
||
# Example: nx run web:build
|
||
# Example: nx run electron-backend:serve
|
||
```
|
||
|
||
To run multiple projects:
|
||
|
||
```bash
|
||
nx run-many --target=test --all
|
||
```
|
||
|
||
### Electron Build Process
|
||
|
||
The Electron backend depends on the web app being built first:
|
||
|
||
- `electron-backend:build` depends on `web:build`
|
||
- Output goes to `dist/apps/electron-backend` (backend) and `dist/apps/web` (frontend)
|
||
- Packaging combines both into distributable
|
||
|
||
### Database Migrations
|
||
|
||
No formal migration system yet. Schema changes are applied via raw SQL in `connection.ts` `createTables()` function using `CREATE TABLE IF NOT EXISTS`.
|
||
|
||
### Common Patterns
|
||
|
||
**IPC Communication**:
|
||
|
||
1. Define handler in appropriate events file (e.g., `database.events.ts`)
|
||
2. Register with `ipcMain.handle()` in the event bootstrap function
|
||
3. Expose in preload script via `contextBridge.exposeInMainWorld()`
|
||
4. Call from Angular via `window.electron.<methodName>()`
|
||
|
||
**Adding New Playlist Source**:
|
||
|
||
1. Add type to `libs/shared/interfaces/src/lib/playlist.interface.ts`
|
||
2. Create event handler in `apps/electron-backend/src/app/events/`
|
||
3. Add UI in `apps/web/src/app/home/`
|
||
4. Update database schema if needed
|
||
|
||
**State Management**:
|
||
|
||
- Use NgRx for global application state (playlists)
|
||
- Use component stores (`@ngrx/component-store`) for feature-specific state
|
||
- Use NgRx signals for reactive data streams
|
||
|
||
<!-- nx configuration start-->
|
||
<!-- Leave the start & end comments to automatically receive updates. -->
|
||
|
||
## General Guidelines for working with Nx
|
||
|
||
- For navigating/exploring the workspace, invoke the `nx-workspace` skill first - it has patterns for querying projects, targets, and dependencies
|
||
- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly
|
||
- Prefix nx commands with the workspace's package manager (e.g., `pnpm nx build`, `npm exec nx test`) - avoids using globally installed CLI
|
||
- You have access to the Nx MCP server and its tools, use them to help the user
|
||
- For Nx plugin best practices, check `node_modules/@nx/<plugin>/PLUGIN.md`. Not all plugins have this file - proceed without it if unavailable.
|
||
- NEVER guess CLI flags - always check nx_docs or `--help` first when unsure
|
||
|
||
## Scaffolding & Generators
|
||
|
||
- For scaffolding tasks (creating apps, libs, project structure, setup), ALWAYS invoke the `nx-generate` skill FIRST before exploring or calling MCP tools
|
||
|
||
## When to use nx_docs
|
||
|
||
- USE for: advanced config options, unfamiliar flags, migration guides, plugin configuration, edge cases
|
||
- DON'T USE for: basic generator syntax (`nx g @nx/react:app`), standard commands, things you already know
|
||
- The `nx-generate` skill handles generator discovery internally - don't call nx_docs just to look up generator syntax
|
||
|
||
<!-- nx configuration end-->
|