Files
iptvnator/apps/xtream-mock-server/README.md
T
4grayandClaude Opus 5 004cb65fe6 feat(portals): find the same movie in your other playlists
A movie that exists in several imported Xtream playlists now shows a
"Sources N" chip on its detail page and in the player. Switching playlist
mid-film keeps the timecode, a preferred source can be pinned per movie, and
a failed stream offers the alternatives instead of a dead end.

The governing rule is that a guess is never presented as a fact. Every
metadata value carries where it came from — `api` (the provider said so),
`parsed` (inferred from the title) or `probe` (we contacted the stream).
Facts render as plain tags, guesses are prefixed `~` in a warning colour, and
an unknown value renders no tag at all plus a "check" affordance. Ranking and
failover read through `factualOnly()`, so a filename claiming 4K is
structurally unable to outrank a source that was actually reached. A probe
that could not complete reports "unknown", never "unavailable".

Scope is deliberately narrow: Xtream to Xtream, movies only, Electron only.
Stalker never reaches the `content` table and M3U is a JSON blob whose search
forces live content; both are additive later, since the candidate type
already carries all three portal kinds. In the PWA every entry point is gated
off and the chip renders nothing.

Auto-failover is opt-in and off by default. Each source is tried at most once
per session, so it terminates structurally, and the switch is never silent —
the toast names the new playlist, offers an undo, and warns that the dub may
differ only when both sides state an audio track as fact.

Notable details:
- Playlist names are routinely the pasted URL, credentials included. They are
  never rendered raw; a short host-only label is derived instead.
- Quality is derived from pixel width, not height: a 2.39:1 1080p master is
  1920x800, and bucketing that by height would publish "720p" as a fact.
- Switching is a single `inlinePlayback.set()` so the player and engine
  survive and re-seek; the carried position is read before the 15s
  persistence throttle so it does not rewind.
- Sources from one playlist collapse into a group, since the same film often
  appears there several times under different stream ids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 08:34:56 +02:00

194 lines
8.3 KiB
Markdown

# Xtream Codes Mock Server
A lightweight Express server that simulates the Xtream Codes API for local
development and end-to-end testing. Uses `@faker-js/faker` with deterministic
seeding so every credential pair always produces the same data.
---
## Quick Start
```bash
# Start on port 3211
pnpm nx run xtream-mock-server:serve
# Start with file-watch (auto-restart on code changes)
pnpm nx run xtream-mock-server:serve-with-watch
# Start the mock server plus the Electron app
pnpm run serve:marketing-demo
# Start the mock server plus the browser web app
pnpm run serve:marketing-demo:web
```
---
## Available Scenarios (credential pairs)
| Username | Password | Scenario | Live cats | VOD cats | Series cats | Items/cat | Status |
| ----------- | ----------- | ---------------------- | --------- | -------- | ----------- | --------- | -------- |
| `user1` | `pass1` | default | 8 | 8 | 8 | 40 | active |
| `large` | `large` | large catalog | 20 | 20 | 20 | 200 | active |
| `stress` | `stress` | stress catalog | 16 | 16 | 16 | 120 | active |
| `series` | `series` | series-heavy | 3 | 4 | 15 | 30 | active |
| `minimal` | `minimal` | minimal (edge cases) | 2 | 2 | 2 | 5 | active |
| `epg` | `epg` | EPG fixture | 2 | 1 | 1 | 3 | active |
| `emptyvod` | `emptyvod` | empty VOD metadata | 2 | 2 | 2 | 5 | active |
| `marketing` | `marketing` | fictional release demo | 4 | 4 | 4 | curated | active |
| `multisrc1` | `multisrc1` | multi-source portal A | 1 | 2 | 1 | 5 | active |
| `multisrc2` | `multisrc2` | multi-source portal B | 1 | 2 | 1 | 5 | active |
| `expired` | `expired` | expired account | 4 | 4 | 4 | 10 | Expired |
| `inactive` | `inactive` | disabled account | 4 | 4 | 4 | 10 | Disabled |
Any other credential pair is auto-generated using a hash of `username:password` as the faker seed (6 categories, 30 items each, active account).
`multisrc1` and `multisrc2` deliberately share one faker seed, so both portals
generate an identical catalog. That overlap is what the VOD multi-source E2E
needs — the same movie present in two different playlists.
---
## API Endpoints
### Direct Xtream Protocol
`GET /player_api.php?action=<action>&username=<u>&password=<p>[&...]`
| Action | Description |
| ---------------------------------------- | --------------------------------------------------- |
| (none) / `get_account_info` | User info + server info |
| `get_live_categories` | Live TV categories |
| `get_vod_categories` | VOD (movie) categories |
| `get_series_categories` | Series categories |
| `get_live_streams` | Live streams (optionally filtered by `category_id`) |
| `get_vod_streams` | VOD streams (optionally filtered by `category_id`) |
| `get_series` | Series list (optionally filtered by `category_id`) |
| `get_vod_info?vod_id=<id>` | Full movie details |
| `get_series_info?series_id=<id>` | Full series info (seasons + episodes) |
| `get_short_epg?stream_id=<id>[&limit=N]` | EPG listings for a live channel |
| `get_simple_data_table?stream_id=<id>` | Full per-channel EPG schedule |
| `get_simple_date_table?stream_id=<id>` | Legacy typo alias for full per-channel EPG schedule |
### PWA CORS Proxy Endpoint
IPTVnator's PWA routes Xtream calls through a backend proxy:
```
GET /xtream?url=<serverUrl>&action=<action>&username=<u>&password=<p>
```
Response: `{ payload: <data>, action: <action> }`
### Stream URLs (stub redirects)
```
GET /live/<username>/<password>/<streamId>.m3u8 → HLS test stream
GET /movie/<username>/<password>/<streamId>.<ext> → HLS test stream
GET /series/<username>/<password>/<streamId>.<ext> → HLS test stream
```
### Utility Endpoints
```
GET /health → { status: "ok", server: "xtream-mock-server", port: 3211 }
POST /reset → clears all in-memory caches; data regenerates on next request
```
---
## Example Requests
```bash
# Account info (direct)
curl "http://localhost:3211/player_api.php?username=user1&password=pass1"
# Live categories (direct)
curl "http://localhost:3211/player_api.php?username=user1&password=pass1&action=get_live_categories"
# VOD details (direct)
curl "http://localhost:3211/player_api.php?username=user1&password=pass1&action=get_vod_info&vod_id=20000"
# Series info (direct)
curl "http://localhost:3211/player_api.php?username=user1&password=pass1&action=get_series_info&series_id=30000"
# EPG for stream (direct)
curl "http://localhost:3211/player_api.php?username=user1&password=pass1&action=get_short_epg&stream_id=10000"
# Full EPG schedule (direct)
curl "http://localhost:3211/player_api.php?username=epg&password=epg&action=get_simple_data_table&stream_id=10000"
# Via PWA proxy
curl "http://localhost:3211/xtream?url=http://localhost:3211&username=user1&password=pass1&action=get_live_categories"
```
---
## Playwright Integration
The mock server starts automatically with `nx e2e web-e2e`. Run only Xtream
tests using the `@xtream` tag:
```bash
nx e2e web-e2e --grep "@xtream"
```
Test files: `apps/web-e2e/src/xtream.e2e.ts`
Electron Xtream EPG coverage lives in
`apps/electron-backend-e2e/src/xtream-epg.e2e.ts`.
The Playwright tests use `page.route()` to redirect the app's backend proxy
calls (`localhost:3000/xtream**`) to the mock server without modifying any
application code.
---
## Data Characteristics
- **Deterministic**: Same credentials → same data every time (seeded faker)
- **Cached per session**: Data generated once on first request, reused until `/reset`
- **EPG**: Titles and descriptions are base64-encoded (matches real Xtream API)
- **Dedicated EPG fixture**: `epg:epg` returns stable live channels plus deterministic `get_short_epg` and `get_simple_data_table` payloads for timezone-focused tests
- **Release screenshot fixture**: `marketing:marketing` returns fictional live, VOD, and series data with local generated artwork under `apps/xtream-mock-server/public/marketing`
- **Timestamp precedence coverage**: The `epg:epg` scenario intentionally shifts raw `start` / `end` strings away from `start_timestamp` / `stop_timestamp` so UI tests can prove timestamps drive rendering
- **Stream IDs**: Live 10,000+, VOD 20,000+, Series 30,000+
- **Category IDs**: Live 101+, VOD 201+, Series 301+
---
## Release Demo Artwork
The `marketing:marketing` fixture uses 65 original fictional titles for release
screenshots. Its core 30-title artwork pack includes matched posters and
backdrops; 35 additional movie-poster showcase titles use approved local poster
PNGs and the deterministic SVG fallback for missing backdrops. The newest 20
showcase movies are ordered first so they appear immediately in screenshot
catalog grids. Assets are served from:
```text
apps/xtream-mock-server/public/marketing/{poster,backdrop}/
```
Generate or validate those assets with:
```bash
pnpm release:artwork:dry-run
pnpm release:artwork:generate
pnpm release:artwork:validate
```
`release:artwork:generate` uses `gpt-image-2` through the OpenAI Image API and
requires `OPENAI_API_KEY`. The screenshot capture workflow only reads committed
local assets; it does not call OpenAI. If a local PNG is missing, the mock
server falls back to its deterministic SVG renderer for development continuity.
The prompt manifest deliberately varies genres and visual media across titles
so the catalog does not collapse into one superhero/poster style.
---
## Architecture
See `docs/architecture/xtream-mock-server.md` for a full description of the
data pipeline, response shapes, and extension points.