Skip to content

Architecture Overview

System Components

┌──────────────────────────────────────────────────────────────┐
│                     DAZN Cloud APIs                          │
│  startup.core.indazn.com  ·  epg.discovery.indazn.com       │
│  api.playback.indazn.com  ·  authentication-prod.ar.indazn  │
│  drm.playback.indazn.com  ·  image.discovery.indazn.com     │
└─────────────┬────────────────────────────┬───────────────────┘
              │  HTTPS                     │  Widevine License
              ▼                            ▼
┌─────────────────────────┐   ┌────────────────────────────────┐
│   PHP Panel (nginx)     │   │   playback_request.py          │
│   dazn.xctv.stream      │   │   request_keys.py (pywidevine) │
│                         │   │   curl_cffi Chrome TLS imperson │
│  ┌───────────────────┐  │   └────────────────────────────────┘
│  ┌───────────────────┐  │              │
│  │ function.php      │  │              │ decryption keys
│  │ get.php           │  │◄─────────────┘
│  │ cron_schedule.php │  │
│  │ discover.php      │  │
│  └───────┬───────────┘  │
│          │              │
│  ┌───────▼───────────┐  │
│  │ MariaDB (dazn)    │  │
│  │ 8 tables          │  │
│  └───────────────────┘  │
└─────────────┬───────────┘
              │  xaccel-codec API (start/stop/update)
┌─────────────────────────┐
│   xaccel-codec Server   │
│   MPEG-DASH → HLS/TS    │
│   DRM decrypt + transcode│
└─────────────────────────┘

Data Flow

1. Endpoint Discovery (Startup)

Panel → startup.core.indazn.com/v1/main/web
     ← ServiceDictionary { Rail, Rails, EpgWithDateRange, Playback, SignIn, ... }
     → Probe each version (newest first) to find working URLs
     → Cache to api_endpoints.json (6h TTL)

2. Schedule Build (Nightly Cron — 23:58)

cron_schedule.php
  → initApiEndpoints(true)        # refresh cached URLs
  → MakeSchedule("yes")
    → DAZN EPG API (epgWithDatesRange)
    ← Tiles[] with Sport, Competition, AssetId, Start, VerifyAge, etc.
    → Filter by active sports/competitions in DB
    → INSERT into schedule table (one row per event)
    → makeCron() generates /etc/cron.d/cron_dazn entries
      (±10 min window around each event start, every 2 min)
    → MakeSchedule() fetches DAZN EPG day-by-day for the full schedule_days range
    → BuildEPG() generates EPG XML covering the full schedule_days range
      (per-competition stop_hour duration for live events, 4-hour filler
      blocks, per-competition templates from epg_templates table,
      deduplicates schedule rows, filename from epg_filename setting)
      Template engine: centralized `resolveTemplateVars()` builds 90+
      variables across 13 categories (Teamarr-style architecture):
        IDENTITY — {team1}, {team2}, {team1_short}, {team1_abbrev},
          {team1_pascal}, {team1_normalize}, {league}, {sport}, etc.
        HOME_AWAY — {home_team}, {away_team}, {home_team_short},
          {home_team_abbrev}, {vs_at}, {vs_@}, etc.
        MATCHUP — {matchup}, {matchup_short}, {matchup_abbrev}
        DATETIME — {start_time}, {date}, {game_date}, {game_date_short},
          {game_date_iso}, {game_day}, {game_time}, {game_time_24h},
          {relative_day}, {today_tonight}, {days_until}, etc.
        EVENT — {description}, {season}, {asset_id}, {image}, {image_url},
          {sport_id}, {competition_id}, {channel_name}
        VENUE/SCORES/BROADCAST — populated by TheSportsDB enrichment
          (venue, scores, broadcast, badges, jerseys, banners)
        RECORDS — {team1_record}, {team2_record} (W-D-L from TSDB standings)
        STREAKS — {team1_streak}, {team2_streak} (form-derived from TSDB)
        STANDINGS — {team1_position}, {team2_position}, {team1_points},
          {team2_points}, {playoff_seed}, {games_back} (TSDB league table)
        ODDS — placeholder category (requires external betting API)
        PLAYOFFS — {season_type}
      `applyTemplate()` replaces placeholders and cleans empty-var artifacts.
      TheSportsDB enrichment: `tsdbEnrichEvent()` called at end of
      `resolveTemplateVars()` — fuzzy team matching, venue lookup,
      event search, league standings, TV schedule. Results cached in
      `tsdb_cache` table.
      EPG XML enrichment: `<category>` (per-template), `<video>`,
      `<audio>`, `<icon>` (poster URL), `<date>` (always),
      `<live />` and `<new />` (main events only, always)
    → SendScheduleViaTelegram() (optional)

3. Stream Acquisition (Per-Event Cron)

get.php <asset_id> <channel_name>
  → load_token() (get_token or refresh_token)
  → Check schedule status (skip if already streaming)
  → PIN verification (if verify_age=1)
  → DAZN Playback API (dynamic endpoint)
    ← PlaybackDetails[] with ManifestUrl, LaUrl, CdnName, CdnToken
  → Select CDN from configured list (cdn_takes)
  → Append CdnToken to ManifestUrl (if present)
  → pssh_kid_grabber() fetches MPD, extracts PSSH/KID
  → request_keys.py (curl_cffi) → pywidevine CDM → Widevine license server
    ← decryption_key (KID:KEY,KID:KEY)
  → Build xaccel-codec URL: ManifestUrl + decryption_key + segment_with_manifest_params=1 + http_persistent=0
  → update() via /dynamic-url API (parses decryption_key from URL)
  → update_decryption_keys() via /decryption-key-update API
  → update_stream_user_agent() sets Chrome UA on xaccel-codec stream config
  → stop() + start() each resolution channel on xaccel-codec
  → update_status() marks event as active

4. Stream Teardown (reset_status.php via cron)

reset_status.php
  → SELECT active events past stop_hour (interpreted as minutes)
  → stop() each resolution channel on xaccel-codec
  → UPDATE schedule status = 0

Database Schema

Table Purpose Key Columns
settings Global config (1 row) credentials, device IDs, xaccel-codec config, Apprise, epg_filename
epg_templates Named EPG template sets name, live_title, live_desc, upcoming_title, upcoming_desc, fallback_title, fallback_desc, poster_url, include_date, include_live, include_new, category
sports Sport categories sport_id (DAZN ID), name, status
competition Leagues/tournaments competition_id, sport_id (FK), resolutions, channel config, stop_hour (minutes), xaccell_category, channel_logo, video_codec, video_bitrate, video_maxrate, epg_template_id (FK → epg_templates), group_id (FK → competition_groups, nullable)
competition_groups Merged competition groups name, channel_name, num_ch, stop_hour, channel_logo, epg_template_id
schedule Daily event schedule asset_id, channel_name, start_time, status, verify_age
log Operation log channel_name, value, datetime
proxy HTTP proxy servers name, country, url
users Panel authentication username (unique), password_hash (bcrypt), totp_secret, created_at
user_2fa_remember 2FA trusted-device tokens (7-day) user_id, token_hash (SHA-256), expires_at
user_api_keys Panel API keys (per-user) user_id, label, token_hash (SHA-256), token_prefix, last_used_at
tsdb_cache TheSportsDB response cache cache_key (unique), cache_value (JSON), expires_at (Unix timestamp)

File Map

File Role
function.php Core library: auth, EPG (per-competition/group templates), scheduling (group-aware counters), xaccel-codec (AddChannel/UpdateChannel with per-competition encoder settings, group channel registration, and category assignment), logging, discovery, migrations
get.php Per-event stream acquisition (CLI, called by cron)
index.php Dashboard — stats row (events, streams, competitions, sports) + schedule card grid
config_dazn.php Global settings UI + API endpoints panel
discover.php EPG scanner for sport/competition ID discovery
competition_events.php Full event listing for a single competition (linked from Discover)
sports.php Sport selection page — card grid with enable/disable toggles and competition counts
edit_competitions.php Per-sport competition editing — expandable cards with all channel/encoder fields, EPG template dropdown, and group badge; xaccel-codec channel actions
competition_groups.php Competition group CRUD — create, edit, delete groups; assign/unassign competitions
epg_templates.php EPG template CRUD — create, edit, duplicate, delete named template sets
schedule.php Schedule viewer, manual trigger, download
cron_schedule.php Nightly cron entry point
reset_status.php Stream teardown cron
make_schedule.php UI schedule trigger — thin wrapper around MakeSchedule() (multi-day v6 endpoint)
card.php Visual schedule card (standalone black background for external consumers)
auth.php Session authentication, CSRF, rate limiting, 2FA remember-me helpers
login.php Login page with first-run setup
logout.php Session destruction and redirect
header.php Shared layout header with sidebar navigation
footer.php Shared layout footer
db.inc.php Database connection
tsdb.php TheSportsDB integration client — team search with fuzzy matching, venue/event/broadcast lookup, league standings/records/streaks, DB caching
playback_request.py Playback API request via curl_cffi (Chrome TLS impersonation)
request_keys.py Widevine license acquisition via curl_cffi (Python)
pywidevine/ CDM library for DRM decryption
add_sport.php Add sport form
add_competition.php Add competition form
add_proxy.php Add proxy form
delete_sports.php Delete sport (cascades competitions)
delete_competition.php Delete competition
proxy.php Proxy list management
log.php Log viewer
api.php JSON REST API — token-authenticated headless access to status, schedule, EPG, streams, log, and all mutating operations

JSON REST API (api.php)

Bearer-token authentication using user-generated API keys (Profile → API Keys) or the legacy xaccel-codec token from settings. API key validation runs inline (no session_start() or migration overhead) with output buffering to guarantee clean JSON responses.

Read Endpoints (GET)

Action Parameters Description
health System health check, dashboard stats, schedule_days
status Active streams, event counts, xaccel-codec stats
schedule ?date=YYYY-MM-DD (optional) Full schedule listing
log ?limit=N (default 100) Last N log entries
streams xaccel-codec stream stats and categories
card Schedule card data grouped by day
epg EPG file metadata, channel list, programme count
competition_groups Competition groups with member competitions

Write Endpoints (POST, JSON body)

Action Body Description
make_schedule Rebuild schedule from DAZN EPG
build_epg Regenerate EPG XML file
start_channel {asset_id, channel_name} Start a channel via get.php
stop_channel {channel_name} Stop a channel on xaccel-codec
reset_status {channel_name} Reset channel status to inactive
refresh Full refresh: endpoints + schedule + EPG + notification
truncate_schedule Clear schedule table
truncate_log Clear log table

Example: curl -H "Authorization: Bearer <token>" https://dazn.xctv.stream/api?action=status

For full request/response documentation with field-level details, see Panel API Reference.


EPG Templates & Enrichment

The panel generates XMLTV-format EPG files consumed by IPTV players, Plex/Jellyfin DVR, Dispatcharr, Threadfin, and similar middleware. Every aspect of the generated XML is driven by named EPG templates assigned per-competition or per-competition-group.

Template Assignment

Each competition (or competition group) has an epg_template_id column pointing to a row in the epg_templates table. Assign templates in Edit Competitions (per-competition dropdown) or Competition Groups (per-group dropdown). If unassigned, built-in defaults apply.

Template Fields

Field Where Used Description
name UI only Human label (e.g. "Football", "MMA", "Default")
live_title Main events Title template for currently-airing events
live_desc Main events Description template for currently-airing events
upcoming_title Filler blocks Title for upcoming-event filler slots
upcoming_desc Filler blocks Description for upcoming-event filler slots
fallback_title Filler blocks Title when no future event exists on channel
fallback_desc Filler blocks Description when no future event exists
category All programmes Sport/genre label (e.g. Soccer, MMA)
poster_url Main events URL pattern for programme artwork / thumbnail
include_date All programmes Checkbox — add <date> tag (YYYYMMDD)
include_live Main events Checkbox — add <live /> tag
include_new Main events Checkbox — add <new /> tag

Placeholders

All title, description, and category fields support these placeholders (replaced at EPG build time):

Placeholder Resolves To Example
{title} Full DAZN event title Essen vs. Bayern Munich
{competition} Competition channel_name from the DB (for grouped events, the child competition's channel_name) UEFA_EUROPA_LEAGUE
{league} DAZN competition name Uefa Europa League
{team1} First team (split from title on v / vs / vs.) Essen
{team2} Second team Bayern Munich
{start_time} Formatted start time 7:30 PM
{date} Formatted date March 23, 2026
{description} DAZN event description (varies)
{season} Current season string 25/26

Poster URL additionally supports three normalized placeholders. These strip all non-alphanumeric characters and lowercase the result, making them safe for URL paths:

Placeholder Resolves To Example
{team1_normalize} Normalized team 1 essen
{team2_normalize} Normalized team 2 bayernmunich
{league_normalize} Normalized DAZN competition name uefaeuropaleague
{competition_normalize} Normalized DB channel_name uefaeuropaleague

EPG XML Tags Generated

Every <programme> element includes these tags:

XML Tag Applies To Description
<title> All Rendered from template (live_title, upcoming_title, or fallback_title)
<desc> All Rendered from template (live_desc, upcoming_desc, or fallback_desc)
<category> All (if set) From template category field, with placeholders resolved
<icon src="..."> Main events (if set) From template poster_url, with all placeholders (including normalized) resolved
<date> All (if enabled) Event date in YYYYMMDD format (per-template toggle)
<video> All <video><quality>HDTV</quality></video>
<audio> All <audio><stereo>stereo</stereo></audio>
<live /> Main events (if enabled) Indicates a live broadcast (per-template toggle, not on fillers)
<new /> Main events (if enabled) Indicates a new/first-airing programme (per-template toggle, not on fillers)

Game Thumbs Integration

Game Thumbs is a self-hosted matchup thumbnail service. Set the Program Poster URL to generate <icon> tags that pull artwork from your Game Thumbs instance.

Poster URL pattern:

http://game-thumbs:3000/{league_normalize}/{team1_normalize}/{team2_normalize}/thumb

Replace game-thumbs:3000 with your actual Game Thumbs host and port.

Image types — Game Thumbs supports three output formats in the URL path suffix:

Suffix Resolution Orientation
/thumb 1440×1080 Landscape
/cover 1080×1440 Portrait
/logo Original / 1024×1024 Square (transparent for matchups)

League codes — the {league_normalize} placeholder produces a lowercase alphanumeric string from the competition's channel_name. Game Thumbs uses its own short league codes. For best results, set competition channel_name values to match Game Thumbs codes. Common mappings:

DAZN Competition Recommended channel_name Game Thumbs Code
Uefa Europa League europa europa
Uefa Europa Conference League conference conference
UEFA Champions League uefa uefa
English Premier League epl epl
La Liga laliga laliga
Serie A seriea seriea
Bundesliga bundesliga bundesliga
Ligue 1 ligue1 ligue1
MLS mls mls
NFL nfl nfl
UFC ufc ufc
NBA nba nba

See the Game Thumbs Supported Leagues page for the full list of 100+ supported league codes.

Team name matching — Game Thumbs performs fuzzy matching on team names, so {team1_normalize} values like bayernmunich or manchesterunited will resolve correctly. City names, abbreviations, and partial names are also supported.

Example EPG Output

A live event with category and poster URL configured:

<programme start="20260323193000 +0000" stop="20260323220000 +0000" channel="EUROPA01">
  <title lang="en">LIVE europa Action: Essen vs. Bayern Munich</title>
  <desc lang="en">Watch as Essen take on Bayern Munich in live europa action.</desc>
  <category lang="en">Soccer</category>
  <icon src="http://game-thumbs:3000/europa/essen/bayernmunich/thumb" />
  <date>20260323</date>
  <video><quality>HDTV</quality></video>
  <audio><stereo>stereo</stereo></audio>
  <live />
  <new />
</programme>

A filler block (no <live />, <new />, or <icon>):

<programme start="20260323150000 +0000" stop="20260323190000 +0000" channel="EUROPA01">
  <title lang="en">UPCOMING europa Action: Essen vs. Bayern Munich</title>
  <desc lang="en">Upcoming at 7:30 PM Essen vs. Bayern Munich.</desc>
  <category lang="en">Soccer</category>
  <date>20260323</date>
  <video><quality>HDTV</quality></video>
  <audio><stereo>stereo</stereo></audio>
</programme>

Quick Setup

  1. Create a template — navigate to EPG Templates → New Template.
  2. Set titles/descriptions — use placeholders like {league}, {team1}, {team2} to generate dynamic text.
  3. Set Category — enter the sport name (e.g. Soccer). This populates the <category> tag in all programmes using this template.
  4. Set Poster URL — paste your Game Thumbs URL pattern: http://your-host:3000/{league_normalize}/{team1_normalize}/{team2_normalize}/thumb
  5. Enable enrichment tags — check Include Date Tag, Include Live Tag, and Include New Tag as desired.
  6. Assign template — in Edit Competitions, select the template from the EPG Template dropdown for each competition or group.
  7. Rebuild EPG — click Make Schedule or call the API: POST /api?action=build_epg
  8. Point your player — configure your IPTV player / Dispatcharr / Threadfin to pull the EPG XML from https://dazn.xctv.stream/<epg_filename> (default: xctv-dazn-epg.xml).