- Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
- holders can request source removal — DMCA policy.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Our Architecture
-
How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs.
-
-
The Vyla stack
-
Vyla separates local application management, provider resolution, and client-facing delivery. That separation
- lets each layer be used on its own while keeping the integration points clear.
-
Vyla Home
-
Home is responsible for a desktop session: local configuration, encrypted local credentials, update
- coordination, managed processes, and the embedded interface. It is the right layer for launching and
- supervising a local experience.
-
Vyla SDK
-
The SDK contains the provider-facing work: source configuration, stream resolution, subtitle retrieval,
- download lookup, and health probes. It is the right layer when your Node.js application needs to decide how
- requests are authorized, cached, selected, and returned.
-
Stream API
-
Stream API wraps the SDK in a self-hosted HTTP service. Its movie and TV endpoints use Server-Sent Events so
- metadata can arrive first and usable sources can be delivered progressively. It also provides subtitle,
- download, health, test, and optional debug routes.
-
Common deployment patterns
-
-
-
-
Pattern
-
Best for
-
Key decision
-
-
-
-
-
Home only
-
a managed local desktop experience
-
Home owns the lifecycle of bundled local services
-
-
-
App + SDK
-
a Node.js backend with a custom product API
-
your app owns authentication, policy, and response design
-
-
-
Player + Stream API
-
browser or mobile clients that need a stable HTTP contract
-
deploy Stream API and place it behind TLS when exposed remotely
-
-
-
Home + Stream API
-
a local player backed by a local service
-
keep the service bound and access-controlled for the local environment
-
-
-
-
Data flow for playback
-
-
A client provides a TMDB media ID (plus season and episode for TV).
-
Stream API, or your own SDK-based backend, asks active providers for candidates.
-
Candidates are checked and successful sources are returned progressively.
-
The player begins with the first compatible candidate and keeps later candidates as fallbacks.
-
Subtitle and download lookups are separate operations; neither should block first playback.
-
-
Provider availability varies by source and network environment. Build the player experience around fallbacks
- and empty-result states, as described in Error handling.
How Vyla SDK sources are configured, selected, and verified.
+
+Vyla SDK aggregates multiple independent providers. Each has different internal mechanisms, reliability profiles, and timeout characteristics. This page explains the source layer that powers both direct SDK use and the optional Stream API.
+
+:::callout-note
+Provider availability and performance can vary based on your network environment. When self-hosting, providers may work differently than on residential connections due to IP-based blocking from upstream sources.
+:::
+
+---
+
+## How Providers Are Used
+
+When your app calls `getStream()`—directly or through Stream API:
+
+1. All active (non-disabled) providers are queried **simultaneously** with per-source random jitter to stagger upstream requests
+2. Each provider has its own **timeout**, **retry count**, and **backoff delay**
+3. Every returned URL is **verified** — first via a HEAD request to the raw upstream URL, then via a proxied HLS playability check
+4. Only URLs that pass both verification steps appear in the response as `source` events
+
+Because the endpoint is SSE, **verified sources are emitted immediately as each provider resolves** — your player receives them progressively. The stream closes once every provider has either resolved or timed out.
+
+:::callout-info
+Call `sdk.getSources(true)` to inspect enabled providers and `sdk.probeAllSources()` for a live availability signal. If you use Stream API, its `/health` endpoint exposes probes over HTTP.
+:::
+
+---
+
+## Provider Configuration
+
+Each provider is defined with the following properties:
+
+| Field | Description |
+| --- | --- |
+| `key` | Internal identifier used in API responses and the `source` field |
+| `label` | Human-readable name shown in the `label` field |
+| `proxyParam` | Short query parameter used to route proxied requests |
+| `timeout` | Maximum time (ms) to wait for a stream URL |
+| `sourcesTimeout` | Optional secondary timeout (ms) for an internal source-listing step |
+| `jitter` | Max random delay (ms) before the request fires |
+| `retries` | Number of retry attempts on failure |
+| `skipProxy` | If `true`, stream URLs are returned as-is without proxy wrapping |
+| `disabled` | If `true`, the provider is excluded from all requests |
+
+---
+
+## Response Time
+
+The first `source` event typically arrives within **3–8 seconds**. Subsequent sources stream in as each provider resolves. The stream closes once the **slowest configured provider** times out.
+
+**Best practice:** always show a loading state, begin playback on the first `source` event, and implement a client-side timeout for the overall stream. See the [Error Handling](/guides/error-handling) guide.
+
+---
+
+## Caching
+
+Results are cached in-memory with a **5-minute TTL**. The per-source stream cache is keyed by `source_key + tmdb_id + season + episode`.
+
+```
+CACHE_TTL = 5 minutes
+```
+
+---
+
+## The HLS Proxy
+
+All provider stream URLs are wrapped in the server proxy (unless `skipProxy: true`):
+
+```
+http://localhost:7860/api?url=&=1
+```
+
+When your player fetches this path from your self-hosted instance, the server:
+
+1. Fetches the upstream M3U8 with the correct `Referer`, `Origin`, and any provider-specific headers
+2. Rewrites all segment URLs and encryption key URIs in the playlist to route through the same proxy
+3. Returns the rewritten playlist with `Access-Control-Allow-Origin: *`
+
+---
+
+## Stream Verification
+
+Before a URL is emitted as a `source` event, it passes two verification steps:
+
+1. **`verifyStream`** — a HEAD request to the raw upstream URL. Must return HTTP `< 400`.
+2. **`verifyPlayable`** — fetches the proxied URL, validates `#EXTM3U`, checks for `#EXTINF` or `#EXT-X-STREAM-INF`, then fetches the first segment.
\ No newline at end of file
diff --git a/concepts/providers/index.html b/concepts/providers/index.html
deleted file mode 100644
index fae5cc3..0000000
--- a/concepts/providers/index.html
+++ /dev/null
@@ -1,293 +0,0 @@
-
-
-
-
-
-
- Providers — Vyla
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
- holders can request source removal — DMCA policy.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Providers
-
How Vyla SDK sources are configured, selected, and verified.
-
Vyla SDK aggregates multiple independent providers. Each has different internal mechanisms, reliability
- profiles, and timeout characteristics. This page explains the source layer that powers both direct SDK use and
- the optional Stream API.
-
-
-
Provider availability and performance can vary based on your network environment. When self-hosting,
- providers may work differently than on residential connections due to IP-based blocking from upstream
- sources.
-
-
-
-
How Providers Are Used
-
When your app calls getStream()—directly or through Stream API:
-
-
All active (non-disabled) providers are queried simultaneously with per-source random
- jitter to stagger upstream requests
-
Each provider has its own timeout, retry count, and backoff
- delay
-
Every returned URL is verified — first via a HEAD request to the raw upstream URL, then
- via a proxied HLS playability check
-
Only URLs that pass both verification steps appear in the response as source events
-
-
Because the endpoint is SSE, verified sources are emitted immediately as each provider
- resolves — your player receives them progressively. The stream closes once every provider has
- either resolved or timed out.
-
-
-
Call sdk.getSources(true) to inspect enabled providers and
- sdk.probeAllSources() for a live availability signal. If you use Stream API, its /health endpoint exposes probes over HTTP.
-
-
-
-
-
Provider Configuration
-
Each provider is defined with the following properties:
-
-
-
-
Field
-
Description
-
-
-
-
-
key
-
Internal identifier used in API responses and the source field
-
-
-
label
-
Human-readable name shown in the label field
-
-
-
proxyParam
-
Short query parameter used to route proxied requests
-
-
-
timeout
-
Maximum time (ms) to wait for a stream URL
-
-
-
sourcesTimeout
-
Optional secondary timeout (ms) for an internal source-listing step, used by some providers
-
-
-
jitter
-
Max random delay (ms) before the request fires, to stagger upstream hits
-
-
-
retries
-
Number of retry attempts on failure
-
-
-
skipProxy
-
If true, stream URLs are returned as-is without proxy wrapping
-
-
-
disabled
-
If true, the provider is excluded from all requests
-
-
-
-
Providers with disabled: true are completely skipped — they won't appear in health checks or
- fanout responses.
-
-
Response Time
-
The first source event typically arrives within 3–8 seconds. Subsequent sources
- stream in as each provider resolves. The stream closes once the slowest configured provider
- times out — in the worst case this can be 60+ seconds (e.g. cinezo has a 60 s timeout), though
- most sources emit well before then.
-
Best practice: always show a loading state, begin playback on the first source
- event, and implement a client-side timeout for the overall stream. See the Error Handling guide.
-
-
Caching
-
Results are cached in-memory with a 5-minute TTL. The per-source stream cache is keyed by
- source_key + tmdb_id + season + episode; the aggregate source-results cache is keyed by
- tmdb_id + season + episode + base. Cache hits skip the provider fetch entirely and return
- near-instantly.
-
-
CACHE_TTL = 5 minutes
-
-
The cache resets on server restart. Cache size is reported in the /api/health response under the
- cache field.
-
-
-
The HLS Proxy
-
All provider stream URLs are wrapped in the server proxy (unless skipProxy: true):
When your player fetches this path from your self-hosted instance, the server:
-
-
Fetches the upstream M3U8 with the correct Referer, Origin, and any
- provider-specific headers
-
Rewrites all segment URLs and encryption key URIs in the playlist to route through the same proxy
-
Returns the rewritten playlist with Access-Control-Allow-Origin: *
-
-
This means your player never talks to the provider directly — all HLS traffic flows through
- the proxy, keeping CORS and rate-limiting issues fully contained.
-
-
Stream Verification
-
Before a URL is emitted as a source event, it passes two verification steps:
-
-
verifyStream — a HEAD request to the raw upstream URL (skipped for
- SKIP_VERIFY sources). Must return HTTP < 400.
-
-
verifyPlayable — fetches the proxied URL, validates #EXTM3U,
- checks for #EXTINF or #EXT-X-STREAM-INF, then fetches the first segment with a
- range request.
-
-
Sources that fail either step — HTTP errors, timeouts, non-HLS/non-MP4 content — are silently dropped. MP4
- sources skip the #EXTM3U check and are verified via HEAD + content-type inspection instead. HTTP
- 429 responses and timeout/abort errors are treated as passing (stream exists but is
- rate-limited).
-
-
-
/test runs the same two-step verification pipeline as /movie and
- /tv. Check the ok field in the response body — HTTP status is always
- 200 regardless of result.
-
-
-
-
-
Disabled Providers
-
Providers marked disabled: true in config are completely excluded from:
-
-
/api/movie and /api/tv fanout
-
/api/health probes
-
GET /api?sources_meta listings
-
-
If a provider you expect to see is missing from responses, check whether it's been disabled.
-
-
Multi-URL Sources
-
Some providers return multiple candidate stream URLs (via allUrls). For these, verification runs
- through each candidate in order and returns the first one that passes. If none pass, the source is dropped.
-
-
For providers with SKIP_VERIFY, all candidate URLs are returned as separate source events
- without verification. These are not distinguished by numbered labels in the response.
- Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
- holders can request source removal — DMCA policy.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Vyla Go
-
The Android control center for running the Vyla media stack locally.
-
-
Vyla Go is the Android application in the Vyla ecosystem. It prepares and runs the local
- services that power your media experience, then opens the interface in a secure embedded window. It is
- designed for people who want Vyla on their Android device without managing a separate server process or
- service lifecycle by hand.
-
Downloads
-
Vyla Go is available for Android. Get the latest version below:
- Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
- holders can request source removal — DMCA policy.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Stream API error handling
-
Handle SSE failures, source fallbacks, and edge cases in an optional Stream API
- client.
-
The /movie and /tv endpoints stream via SSE — error handling looks different from a
- standard JSON API. This page covers every failure mode and the recommended pattern for each.
-
-
-
These patterns apply when you choose the optional Stream API boundary. SDK integrations should apply the
- same fallback principles inside their own server-side request flow.
For /movie and /tv, HTTP 200 is returned immediately when the SSE
- stream opens. Failures surface as zero source events followed by a
- done event with total: 0 — not as HTTP error codes.
-
-
-
Handling No Sources
-
The most common failure — all providers failed to return a playable stream. Detect it by checking
- total in the done event.
-
-
constBASE = 'http://localhost:7860';
-
-const res = awaitfetch(`${BASE}/movie?id=550`);
-const reader = res.body!.getReader();
-const decoder = newTextDecoder();
-let buffer = '';
-let started = false;
-
-while (true) {
- const { done, value } = await reader.read();
- if (done) break;
-
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split('\n');
- buffer = lines.pop()!;
-
- for (const line of lines) {
- if (!line.startsWith('data: ')) continue;
- const event = JSON.parse(line.slice(6));
-
- if (event.type === 'source') { started = true; playSource(event.source.url); }
-
- if (event.type === 'done' && !started) {
- showError('This title is currently unavailable. Try again later.');
- }
- }
-}
-
-
-
Source Fallback Queue
-
Sources stream in as providers resolve. Queue every source event URL and fall back to the next
- one if playback fails mid-stream:
- Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
- holders can request source removal — DMCA policy.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Stream API client integration
-
Build an optional Stream API player client with HLS/MP4 handling, SSE sources,
- subtitles, and fallbacks.
-
Every source URL from /movie and /tv is either an HLS stream
- (.m3u8) or an MP4 file — your player code must handle both. HLS goes through
- HLS.js; MP4 sets video.src directly. The detection logic is the same everywhere.
-
-
-
These examples are for clients that use the optional self-hosted Stream API. If your product already has
- a Node.js backend, prefer Vyla SDK directly and expose only the API your product
- needs.
-
-
-
-
Source Type Detection
-
This is the single utility everything below depends on. A URL is MP4 if the proxy path ends in
- .mp4 or the upstream served a video content-type that isn't HLS.
-
HLS.js accesses window and will crash during SSR. Either mark the component
- 'use client' (already done above) or use a dynamic import to exclude it from the server bundle
- entirely:
-
ExoPlayer resolves HLS and MP4 automatically from the Content-Type returned by the proxy. No
- manual type detection needed on Android.
-
-
-
-
iOS (Swift + AVPlayer)
-
AVPlayer handles both HLS and MP4 natively. Same SSE parsing, different player attachment.
-
import UIKit
-import AVKit
-
-classStreamViewController: UIViewController {
-
- letBASE="https://api.vyla.cc"
- var player: AVPlayer?
- var playerLayer: AVPlayerLayer?
- var streamTask: URLSessionDataTask?
- var started =false
-
- overridefuncviewDidLoad() {
- super.viewDidLoad()
- loadStream(tmdbId: 550)
- }
-
- funcloadStream(tmdbId: Int, season: Int? =nil, episode: Int? =nil) {
- let path: String
- iflet s = season, let e = episode {
- path ="\(self.BASE)/tv?id=\(tmdbId)&season=\(s)&episode=\(e)"
- } else {
- path ="\(self.BASE)/movie?id=\(tmdbId)"
- }
-
- guardlet url =URL(string: path) else { return }
- var request =URLRequest(url: url)
-
- classSSEDelegate: NSObject, URLSessionDataDelegate {
- var buffer =""
- var onSource: (String) -> Void
-
- init(_onSource: @escaping (String) -> Void) { self.onSource = onSource }
-
- funcurlSession(_session: URLSession, dataTask: URLSessionDataTask, didReceivedata: Data) {
- guardlet text =String(data: data, encoding: .utf8) else { return }
- buffer += text
- whilelet range = buffer.range(of: "\n") {
- let line =String(buffer[..<range.lowerBound])
- buffer.removeSubrange(..<range.upperBound)
- guard line.hasPrefix("data: "),
- let jsonData = line.dropFirst(6).data(using: .utf8),
- let json =try?JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
- let type = json["type"] as?Stringelse { continue }
- if type =="source",
- let source = json["source"] as? [String: Any],
- let streamUrl = source["url"] as?String {
- onSource(streamUrl)
- }
- }
- }
- }
-
- let delegate =SSEDelegate { [weakself] streamUrl in
- guardletself, !self.started else { return }
- self.started =true
- DispatchQueue.main.async {
- guardlet url =URL(string: streamUrl) else { return }
- let player =AVPlayer(url: url)
- self.player = player
- let layer =AVPlayerLayer(player: player)
- layer.frame =self.view.bounds
- layer.videoGravity = .resizeAspect
- self.view.layer.addSublayer(layer)
- self.playerLayer = layer
- player.play()
- }
- }
-
- let session =URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
- self.streamTask = session.dataTask(with: request)
- self.streamTask?.resume()
- }
- }
-
- overridefuncviewWillDisappear(_animated: Bool) {
- super.viewWillDisappear(animated)
- streamTask?.cancel()
- player?.pause()
- }
-}
-
-
-
-
AVPlayer resolves HLS via application/vnd.apple.mpegurl and MP4 via video/mp4
- content-type returned by the proxy — no manual detection needed on iOS either.
-
-
-
-
Python (mpv)
-
For desktop apps or scripts. mpv handles HLS and MP4 natively via its own demuxer.
-
import requests
-import json
-import subprocess
-
-BASE = 'https://api.vyla.cc'
-
-defstream(tmdb_id: int, season: int = None, episode: int = None):
- url = (
- f'{BASE}/tv?id={tmdb_id}&season={season}&episode={episode}'
- if season and episode else
- f'{BASE}/movie?id={tmdb_id}'
- )
-
- with requests.get(url, stream=True) as res:
- for line in res.iter_lines():
- ifnot line ornot line.startswith(b'data: '):
- continue
-
- event = json.loads(line[6:])
-
- if event['type'] == 'source':
- stream_url = event['source']['url']
- label = event['source']['label']
- print(f'Playing via {label}: {stream_url}')
- subprocess.run(['mpv', stream_url])
- return
-
- if event['type'] == 'done':
- print('No sources available.')
-
-stream(550)
-
- Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control.
- Rights
- holders can request source removal — DMCA policy.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Vyla Home
-
The desktop control center for running the Vyla media stack locally.
-
-
Vyla Home
-
Vyla Home is the desktop application in the Vyla ecosystem. It prepares and runs the
- local services that power your media experience, then opens the interface in a secure embedded
- window. It is designed for people who want Vyla without managing a separate server process, reverse
- proxy, or service lifecycle by hand.
-
Downloads
-
Vyla Home is available for Windows, macOS, and Linux. Get the latest version below: