Files
KiraTV---App-Linux/docs/architecture/xtream-mock-server.md
T
2026-05-03 23:43:09 +02:00

11 KiB

Xtream Mock Server — Architecture

Purpose

apps/xtream-mock-server is a self-contained Express server that emulates the Xtream Codes API protocol. It is used for:

  • Local development — run a full portal without a real Xtream subscription
  • E2E testing — Playwright spins it up alongside the Angular dev server

Data Pipeline

credentials (username + password)
        │
        ▼
credentialsToSeed(u, p)   ←── deterministic polynomial hash
        │
        ▼
faker.seed(seed)           ←── all faker calls use same seed per credentials
        │
        ▼
  generateCategories()     ←── live / vod / series categories
        │
  generateLiveStreams()     ←── live TV stream list
  scenario EPG fixture?     ←── optional deterministic per-stream EPG override
  generateVodStreams()      ←── VOD movie list
  generateSeriesItems()     ←── series list
  generateSeriesInfo()      ←── nested seasons + episodes (pre-populated)
        │
        ▼
  PortalData (cached)       ←── Map<"username:password", PortalData>

Re-requesting with the same credentials returns the exact same data until POST /reset clears all caches.


File Structure

apps/xtream-mock-server/
├── project.json                  ← Nx targets: serve (port 3211), serve-with-watch
├── public/
│   └── marketing/                ← committed fictional release artwork PNGs
├── tsconfig.json
└── src/
    ├── main.ts                   ← Express app bootstrap, all routes wired up
    └── app/
        ├── scenarios.ts          ← Credential → ScenarioConfig mapping
        ├── data-store.ts         ← Lazy cache, per-credentials generation
        ├── generators/
        │   ├── categories.generator.ts
        │   ├── live.generator.ts   ← Live streams + EPG listings
        │   ├── marketing.generator.ts ← Fictional release screenshot fixture
        │   ├── vod.generator.ts    ← VOD streams + VodDetails
        │   └── series.generator.ts ← Series items + SeriesInfo
        ├── handlers/
        │   ├── get-account-info.handler.ts
        │   ├── get-categories.handler.ts   ← live/vod/series categories
        │   ├── get-full-epg.handler.ts     ← full EPG + legacy typo alias
        │   ├── get-streams.handler.ts      ← live/vod/series stream lists
        │   ├── get-vod-info.handler.ts
        │   ├── get-series-info.handler.ts
        │   └── get-short-epg.handler.ts
        └── routes/
            └── dispatch.ts               ← Action → handler routing

API Protocol

Direct Xtream endpoint

GET /player_api.php?action=<action>&username=<u>&password=<p>[&...]

Response: raw JSON (no envelope). Matches the real Xtream Codes API format.

PWA proxy endpoint

IPTVnator's PWA routes Xtream calls through:

GET /xtream?url=<serverUrl>&action=<action>&username=<u>&password=<p>

Response: { payload: <data>, action: <action> }

This mirrors the backend proxy in apps/electron-backend so the same Angular service code works in both environments.

Stream stub endpoints

GET /live/<username>/<password>/<streamId>.m3u8
GET /movie/<username>/<password>/<streamId>.<ext>
GET /series/<username>/<password>/<streamId>.<ext>

All redirect to a publicly available HLS test stream (https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8).


Key Response Shapes

get_account_info

{
    "user_info": {
        "username": "user1",
        "password": "pass1",
        "status": "active",
        "exp_date": "4102444799",
        "is_trial": "0",
        "active_cons": "1",
        "max_connections": "2",
        "allowed_output_formats": ["m3u8", "ts", "rtmp"]
    },
    "server_info": {
        "url": "http://localhost:3211",
        "port": "3211",
        "timezone": "UTC",
        "timestamp_now": 1234567890
    }
}

get_live_categories / get_vod_categories / get_series_categories

[
  { "category_id": "101", "category_name": "News", "parent_id": 0 },
  ...
]

get_live_streams (sample item)

{
    "num": 1,
    "name": "Acme Corp TV",
    "stream_type": "live",
    "stream_id": 10000,
    "stream_icon": "https://picsum.photos/seed/live-10000/100/100",
    "epg_channel_id": "channel-10000.mock",
    "category_id": "101",
    "tv_archive": 0,
    "tv_archive_duration": 0
}

get_short_epg (sample item)

{
    "epg_listings": [
        {
            "id": "1000000",
            "epg_id": "channel-10000.mock",
            "title": "base64encodedTitle",
            "description": "base64encodedDescription",
            "start": "2024-01-01 12:00:00",
            "end": "2024-01-01 12:30:00",
            "start_timestamp": "1704110400",
            "stop_timestamp": "1704112200"
        }
    ]
}

Note: title and description are base64-encoded, matching the real Xtream API.

get_simple_data_table / get_simple_date_table

Both actions return the same full per-channel schedule shape:

{
    "epg_listings": [
        {
            "id": "10000-current",
            "epg_id": "channel-10000.mock",
            "title": "base64encodedTitle",
            "description": "base64encodedDescription",
            "start": "2026-04-05 04:30:00",
            "end": "2026-04-05 05:00:00",
            "start_timestamp": "1775363400",
            "stop_timestamp": "1775365200",
            "channel_id": "channel-10000.mock"
        }
    ]
}

The legacy get_simple_date_table typo alias exists because real Xtream panels sometimes only respond to that misspelled action.

get_series_info (structure)

{
  "seasons": [
    {
      "id": 3000100, "name": "Season 1", "season_number": 1,
      "episode_count": 8, "air_date": "2022-05-14",
      "cover": "https://picsum.photos/seed/season-30001-1/300/450"
    }
  ],
  "info": { "name": "...", "cover": "...", "plot": "...", "cast": "...", ... },
  "episodes": {
    "1": [
      {
        "id": "80001", "episode_num": 1, "title": "Series Name S1E1",
        "season": 1, "container_extension": "mkv",
        "info": { "duration_secs": 2400, "rating": 8.3, ... }
      }
    ]
  }
}

Scenarios

Key (username:password) Seed Categories Items/cat Account status
user1:pass1 1001 8 each 40 active
large:large 9999 20 each 200 active
stress:stress 7777 16 each 120 active
series:series 2002 live:3, vod:4, series:15 30 active
minimal:minimal 3003 2 each 5 active
epg:epg 6006 live:2, vod:1, series:1 3 active
emptyvod:emptyvod 7007 2 each 5 active
marketing:marketing 8020 live:4, vod:4, series:4 curated active
expired:expired 4004 4 each 10 Expired
inactive:inactive 5005 4 each 10 Disabled
<any other> hash 6 each 30 active

epg:epg fixture details

This scenario is reserved for Xtream EPG tests:

  • live category EPG Focus contains deterministic channels such as Timezone News
  • Timezone News serves a fixed get_short_epg window and a full get_simple_data_table schedule
  • the full schedule includes a program that spans a UTC midnight boundary and another program after midnight
  • raw start / end strings are intentionally offset from start_timestamp / stop_timestamp

That deliberate mismatch lets Electron tests verify the renderer uses timestamp fields for sorting, current-program selection, progress bars, and local clock labels instead of trusting provider-local strings.

marketing:marketing fixture details

This scenario is reserved for release screenshots and marketing materials:

  • VOD and series data use 30 curated fictional titles instead of faker-generated titles or real media metadata
  • posters and backdrops are served from committed PNG files in apps/xtream-mock-server/public/marketing/{poster,backdrop}/
  • tools/release/generate-marketing-artwork.ts generates the PNGs with gpt-image-2 only when OPENAI_API_KEY is present; it also writes the prompt/asset manifest at apps/xtream-mock-server/public/marketing/manifest.json
  • the manifest assigns distinct genre and visual-medium profiles per title, including documentary, noir, animated family, retro rescue drama, cyberpunk, anime-inspired space school, and workplace dramedy styles
  • screenshot capture remains deterministic and offline because tools/release/capture-v020-screenshots.ts consumes the local mock server assets and never calls OpenAI
  • the SVG renderer in marketing.generator.ts remains the fallback for missing assets, live logos, season covers, and episode thumbnails

Playwright Integration

Configuration (apps/web-e2e/playwright.config.ts)

The mock server is listed as a third webServer entry:

{
  command: 'pnpm nx run xtream-mock-server:serve',
  url: 'http://localhost:3211/health',
  reuseExistingServer: !process.env['CI'],
  cwd: workspaceRoot,
}

Request Interception

The Angular PWA calls localhost:3000/xtream?.... Playwright intercepts these:

await page.route('**/localhost:3000/xtream**', async (route) => {
    const originalUrl = new URL(route.request().url());
    const mockUrl = new URL('http://localhost:3211/xtream');
    originalUrl.searchParams.forEach((v, k) => mockUrl.searchParams.set(k, v));
    await route.continue({ url: mockUrl.toString() });
});

Extension Points

  • Add new actions: Implement a handler function and add a case in routes/dispatch.ts
  • Add new scenarios: Add an entry to SCENARIOS in scenarios.ts
  • Add deterministic EPG fixtures: Extend ScenarioConfig.epgFixture and populate epgListingsByStreamId in data-store.ts
  • Refresh release artwork: Run pnpm release:artwork:dry-run, then OPENAI_API_KEY=... pnpm release:artwork:generate, inspect the generated PNGs, and finish with pnpm release:artwork:validate
  • Adjust data volume: Change itemsPerCategory, seasonsPerSeries, or episodesPerSeason per scenario
  • Custom stream URLs: Edit the HLS stub redirect in main.ts