×

📺 IPTV

B2C · Xtream · Reseller

IPTV Manage Users

Archive Storage Tiers

Three-tier archive system for 500+ concurrent clients. Configure in Config > Storage > Storage Tiers. Each tier has independent lifecycle, capacity calc and fallback behaviour.

Encoder

Write Buffer

2h hot window (NVMe or tmpfs)

→ mirror (continuous)

HDD RAID 5

50 TB, 14 days retention

→ prefetch on demand

Playback Cache

1 TB warm cache, 500+ clients

Clients

Mirror, not move

Segments older than mirror_min_age_minutes are COPIED to RAID continuously. The write_buffer holds a rolling 2h hot window so rewind-to-recent playback stays on fast storage. Only after evict_age_minutes (and only when verified-mirrored) are segments deleted from write_buffer. RAID always has the long-tail copy — no hourly burst transfers.

Tier

Purpose

Settings

Write Buffer

Receives segments 24/7, holds 2h hot window for fast recent-rewind playback, protects RAID from random IO. Device can be a physical NVMe partition (production) or tmpfs (dev/test box without physical NVMe — see tmpfs section below).

device_path (block device or 'tmpfs'), tmpfs_size_pct (10-50% MemAvailable when tmpfs), mirror_min_age_minutes, evict_age_minutes, flush_threshold_percent (force flush trigger)

HDD RAID Archive

Long-tail storage for full retention (default 14 days). Receives continuous mirror trickle from write_buffer — sequential write, no hourly bursts. Survives reboot. RAID survives single disk failure.

retention_days, raid_device, nginx_location, min_free_percent

Playback Cache (optional)

Read-side cache for popular files older than 2h (e.g. EPG catch-up). Populated from archive on demand after popularity threshold (3 hits / 5 min). Device can be NVMe or tmpfs.

prefetch_hours, max_sessions, eviction_policy (session/lru/fifo)

Capacity calc

live writes × 2h = write_buffer hot window size. Example: 50 streams × 8 Mbps × 2h ≈ 36 GB. Add ~30% headroom for mirror trickle backlog → ~50 GB write_buffer is enough for 50 streams. RAID sized for full retention: 50 × 8 Mbps × 14 days ≈ 6 TB.

Reads (tier-aware lookup)

Stream.dvr_file_path/2 probes write_buffer → playback_cache → archive in order. A segment in the 2h hot window is served from write_buffer at native speeds even though it also exists on RAID. Nginx X-Accel-Redirect serves files directly without going through Phoenix.

tmpfs Virtual Disk (dev/test)

Each tier can use a RAM-backed tmpfs virtual disk instead of a physical block device. Useful for dev boxes without spare NVMe, or for ephemeral test environments. Storage Tier card device dropdown contains a 'tmpfs (virtual disk from RAM)' option that replaces the device path selector with a percentage slider.

Size in % free RAM

Allocation is specified as a percentage of MemAvailable (from /proc/meminfo) — step 10, valid values 10/20/30/40/50. Hard floor 10% (validation rejects below). Hard ceiling 50%. Default 30%. On a 100 GB MemAvailable host: 10% = 10 GB, 30% = 30 GB, 50% = 50 GB. Size is recomputed and remounted live when the slider is moved and saved.

OOM safety guard

If computed target would exceed 60% of current MemAvailable, the mount is refused and the tier remains offline (Stream.archive_path falls through to the next healthy tier). Prevents an aggressive setting from killing the BEAM via OOM. The guard runs both at boot and on every Save in the UI.

Reboot persistence

tmpfs lives in RAM — any in-flight buffer content is LOST when the host reboots. The fstab entry written at provision time re-mounts the empty tmpfs automatically on next boot, but old segments do not return. Mirror to archive continues normally for everything that was already mirrored before the reboot — only the last unmirrored window (typically < mirror_min_age_minutes worth) is lost. For production, use a physical NVMe device; tmpfs is intended as a dev/test substitute.

When to use tmpfs

Use tmpfs for write_buffer when: (a) dev/test box has no spare NVMe but you still want the full DVR pipeline running for integration testing, (b) operator wants to validate mirror + archive behaviour without buying NVMe up-front, (c) ephemeral testing where loss-on-reboot is acceptable. Do NOT use tmpfs in production — RAM is volatile and orders of magnitude more expensive than NVMe per GB of retention.

Faster flush for tmpfs

When the write tier is tmpfs, StorageTierFlush uses tighter defaults — mirror_min_age 2 min, evict_age 15 min (vs 5 min / 120 min for physical NVMe). RAM is more expensive than disk so the rolling hot window is kept smaller. Override via MIRROR_MIN_AGE_MINUTES / EVICT_AGE_MINUTES env vars if needed.

Tier Auto-Provisioning & Safety

Tiers are configured once via Config > Storage > Storage Tiers and provisioned automatically — no manual mkfs/mount/fstab editing on the host. StorageTier.ensure_all_provisioned/0 runs at every boot from SystemInitializer and is idempotent: already-mounted tiers are skipped, missing devices are no-ops, tmpfs tiers re-mount cleanly after reboot.

Default boot seed

On first boot (no write tier in DB), the system auto-creates a tmpfs-backed write tier with 30% MemAvailable allocation, mount point /mnt/nvme_write. This gives a working DVR pipeline out-of-the-box on any host with enough RAM. Operator can later switch device to a physical NVMe via UI without losing other tier settings.

Health & failover

Stream.archive_path/1 returns the first 'healthy' tier — write_buffer if mounted+writable+free_pct above safety floor, otherwise falls back to archive (degraded mode). Safety floor 20% for tmpfs (RAM-strict), 2% for block devices. tier_health endpoint exposes per-tier mounted/writable/free_pct for monitoring.

Live size change

Changing tmpfs_size_pct in UI triggers umount + remount with the new size on Save. Active DVR writes are briefly paused (mount transition < 1 s). Recommendation: change tmpfs size only when DVR write rate is low, and consider restarting streams after to ensure they pick up the new mount cleanly.

Base URL — OSTV Player

Package Fields

Packages API

Password Policy

Reseller API

Settings

User Fields

User Management

IPTV Manage Users

Base URL — OSTV Player

The OSTV Player application allows users to change the server base URL (API address). This feature is available only for users with provider set to root.

Reason: Changing the base URL is intended only for administrators/root users so they can switch between servers (e.g. production vs. testing). Regular users have a fixed base URL and cannot change it.

User Fields

Packages API

Returns a list of all active packages with prices and billing periods.

List of management users (resellers / providers).

Returns credit balance for a specific management user.

Package Fields

Reseller API

API for resellers/providers. Authorization: Authorization: Bearer mtk_... (token from Manage Users). Reseller sees only their own clients (owner_id).

Password Policy

Password is required when creating a subscriber. Rules:

Minimum 8 characters

At least 1 uppercase letter (A-Z)

At least 1 special character (!@#$%^&* etc.)

Applies to UI and API. On update, password is optional, but if provided must meet the rules.

Settings

Tier Auto-Provisioning &amp; Safety

Capacity + filesystem auto-detection: when a free NVMe is selected from the dropdown, capacity (GB) is read from lsblk -bn -o SIZE and current FS from blkid (with whole-disk fallback). The form shows the values read-only — no manual entry, no mistakes.

Tier Flush Mechanism — Continuous Mirror

StorageTierFlush GenServer runs every 60s. Instead of waiting for an hour-dir to "age out" and moving it as a 5GB burst, it trickles segments to RAID continuously while keeping a 2h hot window on NVMe for fast "recent rewind" playback. The two phases are decoupled — copy and delete are separate, verified operations.

Why continuous mirror beats "wait 60min, batch-move": (1) RAID load is even — no 5GB bursts at hour boundaries, just trickle of 7MB segments; (2) Most hot data is already on RAID — disaster recovery only loses the last few minutes; (3) Eviction is verified per-file — a partial mirror failure is detected before any NVMe data is lost.

In-progress segment safety: the mirror only picks files older than MIRROR_MIN_AGE_MINUTES via find -mmin +5. A segment file still being appended by FFmpeg won't appear in the list — no chance of partial-write transfer.

Per-deployment Tuning (.env)

500–1000

Sizing rule:

DVR Preview Calendar

The DVR Preview tab shows a server-rendered calendar that highlights only the days for which recordings exist on disk. The scan runs across every tier (write_buffer + read_cache + archive + legacy) so a segment that was just flushed from NVMe to RAID stays visible without delay.

Days with recordings are clickable (blue) — clicking pre-fills the datetime input with that day at 00:00; the user then picks the hour. Days with no recording are dimmed and inert.

Implemented in Stream.available_dvr_days/1 via find -mindepth 4 -maxdepth 4 -type d on each tier mount; deduplicated across tiers (a day is shown once even if it has segments in multiple tiers during a flush window).

B2C Player API

Overview

The B2C (Business-to-Consumer) API provides authentication and stream access for end-user player applications. Users authenticate with username/password and receive a time-limited auth token for subsequent requests.

Authentication

Authenticate a user and obtain an auth_token for subsequent API calls.

Method

Endpoint

Request Body

Parameter

Type

Required

Description

Yes

User login name

User password

Example Request

Response Fields

Success Response (200)

Encrypted authentication token for subsequent API calls

Token expiration date/time in ISO 8601 format

Example Success Response

Error Response (403)

Error description message

HTTP status code

Example Error Response

User Info

Retrieve information about the authenticated user using the auth token.

Response Fields

Username of the authenticated user

Server message (empty if none)

Authentication status (1 = authenticated)

Account status: Active, Banned, Disabled, Trial

Account expiration date (null if unlimited)

Number of current active connections

Account creation date in ISO 8601 format

Maximum allowed simultaneous connections

List of allowed output stream formats

Provider name of the user's owner/reseller (null if none)

URL of the provider logo image (null if none)

URL of the provider collapse logo image 208x208 (null if none)

Current server time in ISO 8601 format

Example Request

Categories

Retrieve categories filtered by type. Available types: live (streams), vod (movies), series.

Category type: live, vod, or series

Category ID as string

Category display name

Parent category ID (0 if root)

MD5 hash of content for cache invalidation

Example Request

VOD Detail

Retrieve detailed information for a specific VOD item, including metadata and stream links.

VOD stream ID (UUID)

Info Object Fields

Cover image URL

TMDB identifier

Backdrop image URL

Genre string

Plot / description

Cast members

Rating value (0-10)

Director name

Release date

Duration in seconds

Duration as formatted string

Array of category IDs

1 if adult content, 0 otherwise

VOD Object Fields

VOD stream ID

VOD title

Date added (ISO 8601)

Stream link URLs

Video/audio codec metadata

Series Detail

Retrieve series information with full season and episode listings, grouped by season number.

Series ID (UUID)

Seasons Array Fields

Season number

Season display name

Number of episodes in season

Air date of the season

Season overview / description

Season cover image URL

Episodes Object

Map keyed by season number string. Each value is an array of episode objects.

Episode Object Fields

Episode ID

TMDB episode ID

Episode number within season

Episode title

Date added (ISO 8601)

Season number

Episode info object (same structure as VOD info)

Stream link URLs

Video/audio codec metadata

Notes

Token expiration depends on user role: admin = 365 days, other = 30 days|Disabled accounts cannot authenticate (returns 403)|Each successful login updates the user's last_login timestamp|Tokens are encrypted and contain user_id, username, role and expiration

Success Response (200)

Auth token obtained from /auth endpoint