Initial documentation
177
ai-catalog.json
Normal file
@@ -0,0 +1,177 @@
|
||||
{
|
||||
"name": "Vyla Documentation",
|
||||
"description": "Comprehensive documentation for Vyla - a community-built, self-hosted media project focused on freedom, privacy, and user control. Includes SDK reference, Stream API documentation, and guides for building custom media players.",
|
||||
"url": "https://vyla.cc",
|
||||
"type": "documentation",
|
||||
"category": "software",
|
||||
"tags": [
|
||||
"media streaming",
|
||||
"self-hosted",
|
||||
"privacy",
|
||||
"open source",
|
||||
"SDK",
|
||||
"API",
|
||||
"Node.js",
|
||||
"TypeScript",
|
||||
"Android",
|
||||
"Android TV",
|
||||
"desktop",
|
||||
"streaming",
|
||||
"video player",
|
||||
"HLS",
|
||||
"SSE"
|
||||
],
|
||||
"language": "en",
|
||||
"version": "1.0.0",
|
||||
"lastUpdated": "2026-10-06",
|
||||
"maintainer": {
|
||||
"name": "Vyla Entertainment",
|
||||
"url": "https://gitlab.com/vyla-entertainment/"
|
||||
},
|
||||
"documentation": {
|
||||
"main": "https://vyla.cc",
|
||||
"llms": "https://vyla.cc/llms.txt",
|
||||
"repository": "https://gitlab.com/vyla-entertainment/"
|
||||
},
|
||||
"topics": [
|
||||
{
|
||||
"name": "Getting Started",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Introduction",
|
||||
"url": "/"
|
||||
},
|
||||
{
|
||||
"title": "Architecture",
|
||||
"url": "/architecture"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Products",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Vyla Home",
|
||||
"url": "/home"
|
||||
},
|
||||
{
|
||||
"title": "Vyla Go",
|
||||
"url": "/go"
|
||||
},
|
||||
{
|
||||
"title": "Vyla TV",
|
||||
"url": "/tv"
|
||||
},
|
||||
{
|
||||
"title": "Build a Player",
|
||||
"url": "/player"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SDK",
|
||||
"pages": [
|
||||
{
|
||||
"title": "SDK Reference",
|
||||
"url": "/sdk"
|
||||
},
|
||||
{
|
||||
"title": "SDK Quickstart",
|
||||
"url": "/sdk-quickstart"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Stream API",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Overview",
|
||||
"url": "/stream-api/overview"
|
||||
},
|
||||
{
|
||||
"title": "Paths and Events",
|
||||
"url": "/stream-api/paths"
|
||||
},
|
||||
{
|
||||
"title": "Subtitles and Downloads",
|
||||
"url": "/stream-api/media-data"
|
||||
},
|
||||
{
|
||||
"title": "Health and Operations",
|
||||
"url": "/stream-api/operations"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Deployment",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Self-hosting",
|
||||
"url": "/self-hosting"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Concepts",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Providers",
|
||||
"url": "/concepts/providers"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Guides",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Client Integration",
|
||||
"url": "/guides/integrations"
|
||||
},
|
||||
{
|
||||
"title": "Error Handling",
|
||||
"url": "/guides/error-handling"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Miscellaneous",
|
||||
"pages": [
|
||||
{
|
||||
"title": "Credits",
|
||||
"url": "/misc/credit"
|
||||
},
|
||||
{
|
||||
"title": "DMCA Policy",
|
||||
"url": "/misc/dmca"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"apis": [
|
||||
{
|
||||
"name": "Vyla SDK",
|
||||
"type": "npm",
|
||||
"package": "@vyla-entertainment/sdk",
|
||||
"description": "Node.js/TypeScript SDK for direct provider resolution",
|
||||
"language": "TypeScript"
|
||||
},
|
||||
{
|
||||
"name": "Stream API",
|
||||
"type": "http",
|
||||
"description": "Self-hosted HTTP/SSE API for browser and mobile clients",
|
||||
"protocol": "SSE"
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"Windows",
|
||||
"macOS",
|
||||
"Linux",
|
||||
"Android",
|
||||
"Android TV"
|
||||
],
|
||||
"license": "ISC",
|
||||
"community": {
|
||||
"discord": "https://discord.gg/5eqRhKeywG",
|
||||
"gitlab": "https://gitlab.com/vyla-entertainment/"
|
||||
}
|
||||
}
|
||||
190
architecture/index.html
Normal file
@@ -0,0 +1,190 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Our Architecture — Vyla</title>
|
||||
<meta name="description"
|
||||
content="How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs." />
|
||||
<meta name="keywords"
|
||||
content="Vyla architecture, Vyla Home, Vyla SDK, Stream API, system design, media stack, deployment patterns, self-hosted media" />
|
||||
<link rel="canonical" href="https://vyla.cc/architecture.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/architecture.html" />
|
||||
<meta property="og:title" content="Our Architecture — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/architecture.html" />
|
||||
<meta property="twitter:title" content="Our Architecture — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Our Architecture",
|
||||
"description": "How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs.",
|
||||
"url": "https://vyla.cc/architecture.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Our Architecture</h1>
|
||||
<p class="page-description">How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs.
|
||||
</p>
|
||||
<h2 id="the-vyla-stack">The Vyla stack</h2>
|
||||
<p>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.</p>
|
||||
<h3 id="vyla-home">Vyla Home</h3>
|
||||
<p>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.</p>
|
||||
<h3 id="vyla-sdk">Vyla SDK</h3>
|
||||
<p>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.</p>
|
||||
<h3 id="stream-api">Stream API</h3>
|
||||
<p>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.</p>
|
||||
<h2 id="common-deployment-patterns">Common deployment patterns</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pattern</th>
|
||||
<th>Best for</th>
|
||||
<th>Key decision</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Home only</td>
|
||||
<td>a managed local desktop experience</td>
|
||||
<td>Home owns the lifecycle of bundled local services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>App + SDK</td>
|
||||
<td>a Node.js backend with a custom product API</td>
|
||||
<td>your app owns authentication, policy, and response design</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Player + Stream API</td>
|
||||
<td>browser or mobile clients that need a stable HTTP contract</td>
|
||||
<td>deploy Stream API and place it behind TLS when exposed remotely</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Home + Stream API</td>
|
||||
<td>a local player backed by a local service</td>
|
||||
<td>keep the service bound and access-controlled for the local environment</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 id="data-flow-for-playback">Data flow for playback</h2>
|
||||
<ol>
|
||||
<li>A client provides a TMDB media ID (plus season and episode for TV).</li>
|
||||
<li>Stream API, or your own SDK-based backend, asks active providers for candidates.</li>
|
||||
<li>Candidates are checked and successful sources are returned progressively.</li>
|
||||
<li>The player begins with the first compatible candidate and keeps later candidates as fallbacks.</li>
|
||||
<li>Subtitle and download lookups are separate operations; neither should block first playback.</li>
|
||||
</ol>
|
||||
<p>Provider availability varies by source and network environment. Build the player experience around fallbacks
|
||||
and empty-result states, as described in <a href="/guides/error-handling">Error handling</a>.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#the-vyla-stack">The Vyla stack</a></li>
|
||||
<li><a href="#common-deployment-patterns">Common deployment patterns</a></li>
|
||||
<li><a href="#data-flow-for-playback">Data flow for playback</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
177
architecture/index.html.backup
Normal file
@@ -0,0 +1,177 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Our Architecture — Vyla</title>
|
||||
<meta name="description"
|
||||
<meta name="keywords"
|
||||
content="Vyla architecture, Vyla Home, Vyla SDK, Stream API, system design, media stack, deployment patterns, self-hosted media" />
|
||||
<link rel="canonical" href="https://vyla.cc/architecture.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/architecture.html" />
|
||||
<meta property="og:title" content="Our Architecture — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/architecture.html" />
|
||||
<meta property="twitter:title" content="Our Architecture — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
content="How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs." />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
<script>
|
||||
(function () {
|
||||
var saved = localStorage.getItem('vyla-theme');
|
||||
var isDark = saved ? saved === 'dark' : true;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li></li>
|
||||
<li><a class="active" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Our Architecture</h1>
|
||||
<p class="page-description">How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs.
|
||||
</p>
|
||||
<h2 id="the-vyla-stack">The Vyla stack</h2>
|
||||
<p>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.</p>
|
||||
<h3 id="vyla-home">Vyla Home</h3>
|
||||
<p>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.</p>
|
||||
<h3 id="vyla-sdk">Vyla SDK</h3>
|
||||
<p>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.</p>
|
||||
<h3 id="stream-api">Stream API</h3>
|
||||
<p>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.</p>
|
||||
<h2 id="common-deployment-patterns">Common deployment patterns</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pattern</th>
|
||||
<th>Best for</th>
|
||||
<th>Key decision</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Home only</td>
|
||||
<td>a managed local desktop experience</td>
|
||||
<td>Home owns the lifecycle of bundled local services</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>App + SDK</td>
|
||||
<td>a Node.js backend with a custom product API</td>
|
||||
<td>your app owns authentication, policy, and response design</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Player + Stream API</td>
|
||||
<td>browser or mobile clients that need a stable HTTP contract</td>
|
||||
<td>deploy Stream API and place it behind TLS when exposed remotely</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Home + Stream API</td>
|
||||
<td>a local player backed by a local service</td>
|
||||
<td>keep the service bound and access-controlled for the local environment</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 id="data-flow-for-playback">Data flow for playback</h2>
|
||||
<ol>
|
||||
<li>A client provides a TMDB media ID (plus season and episode for TV).</li>
|
||||
<li>Stream API, or your own SDK-based backend, asks active providers for candidates.</li>
|
||||
<li>Candidates are checked and successful sources are returned progressively.</li>
|
||||
<li>The player begins with the first compatible candidate and keeps later candidates as fallbacks.</li>
|
||||
<li>Subtitle and download lookups are separate operations; neither should block first playback.</li>
|
||||
</ol>
|
||||
<p>Provider availability varies by source and network environment. Build the player experience around fallbacks
|
||||
and empty-result states, as described in <a href="/guides/error-handling">Error handling</a>.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#the-vyla-stack">The Vyla stack</a></li>
|
||||
<li><a href="#common-deployment-patterns">Common deployment patterns</a></li>
|
||||
<li><a href="#data-flow-for-playback">Data flow for playback</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
653
assets/app.js
Normal file
@@ -0,0 +1,653 @@
|
||||
var THEME_KEY = "vyla-theme";
|
||||
var ICONS = {
|
||||
system: '<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>',
|
||||
light: '<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4"/>',
|
||||
dark: '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
|
||||
search: '<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>',
|
||||
menu: '<path d="M4 6h16M4 12h10M4 18h16"/>',
|
||||
doc: '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5M9 13h6M9 17h6"/>',
|
||||
hash: '<path d="M4 9h16M4 15h16M10 3 8 21M16 3l-2 18"/>',
|
||||
copy: '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h9"/>',
|
||||
check: '<path d="m5 12 5 5 9-10"/>',
|
||||
chevron: '<path d="m6 9 6 6 6-6"/>'
|
||||
};
|
||||
var searchData = null;
|
||||
var searchResults = [];
|
||||
var searchActive = 0;
|
||||
|
||||
function icon(name) {
|
||||
return '<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + ICONS[name] + "</svg>";
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s).replace(/[&<>"]/g, function (c) {
|
||||
return { "&": "&", "<": "<", ">": ">", '"': """ }[c];
|
||||
});
|
||||
}
|
||||
|
||||
function getThemeMode() {
|
||||
var v = localStorage.getItem(THEME_KEY);
|
||||
return v === "light" || v === "dark" ? v : "system";
|
||||
}
|
||||
|
||||
function applyTheme() {
|
||||
var mode = getThemeMode();
|
||||
var dark = mode === "dark" || (mode === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
document.documentElement.classList.toggle("dark", dark);
|
||||
var logo = document.getElementById("brand-logo");
|
||||
if (logo) logo.src = dark ? "/assets/images/logo.png" : "/assets/images/dark_logo.png";
|
||||
}
|
||||
|
||||
function setTheme(mode) {
|
||||
if (mode === "system") localStorage.removeItem(THEME_KEY);
|
||||
else localStorage.setItem(THEME_KEY, mode);
|
||||
applyTheme();
|
||||
refreshThemeUI();
|
||||
var menu = document.getElementById("theme-menu");
|
||||
if (menu) menu.classList.remove("open");
|
||||
}
|
||||
|
||||
function toggleThemeMenu(e) {
|
||||
e.stopPropagation();
|
||||
document.getElementById("theme-menu").classList.toggle("open");
|
||||
}
|
||||
|
||||
function refreshThemeUI() {
|
||||
var mode = getThemeMode();
|
||||
var btn = document.getElementById("theme-toggle");
|
||||
if (btn) btn.innerHTML = icon(mode);
|
||||
document.querySelectorAll("#theme-menu button, #theme-seg button").forEach(function (b) {
|
||||
b.classList.toggle("on", b.getAttribute("data-mode") === mode);
|
||||
});
|
||||
}
|
||||
|
||||
function buildThemeControls() {
|
||||
var btn = document.getElementById("theme-toggle");
|
||||
var links = document.querySelector("#navbar .links");
|
||||
if (!btn || !links) return;
|
||||
var wrap = document.createElement("div");
|
||||
wrap.className = "theme-wrap";
|
||||
btn.parentNode.insertBefore(wrap, btn);
|
||||
wrap.appendChild(btn);
|
||||
var modes = ["system", "light", "dark"];
|
||||
var menu = document.createElement("div");
|
||||
menu.id = "theme-menu";
|
||||
menu.innerHTML = modes.map(function (m) {
|
||||
return '<button type="button" data-mode="' + m + '">' + icon(m) + "<span>" + m.charAt(0).toUpperCase() + m.slice(1) + '</span><i class="check">✓</i></button>';
|
||||
}).join("");
|
||||
wrap.appendChild(menu);
|
||||
var seg = document.createElement("div");
|
||||
seg.id = "theme-seg";
|
||||
seg.innerHTML = modes.map(function (m) {
|
||||
return '<button type="button" data-mode="' + m + '" aria-label="' + m + '">' + icon(m) + "</button>";
|
||||
}).join("");
|
||||
links.appendChild(seg);
|
||||
[menu, seg].forEach(function (el) {
|
||||
el.addEventListener("click", function (e) {
|
||||
var b = e.target.closest("button");
|
||||
if (b) setTheme(b.getAttribute("data-mode"));
|
||||
});
|
||||
});
|
||||
refreshThemeUI();
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var nav = document.getElementById("navbar");
|
||||
var top = Math.max(0, Math.round(nav.getBoundingClientRect().bottom));
|
||||
sidebar.style.setProperty("--sidebar-top", top + "px");
|
||||
var open = sidebar.classList.toggle("open");
|
||||
document.body.classList.toggle("nav-open", open);
|
||||
}
|
||||
|
||||
function findActiveTab(root) {
|
||||
var hrefs = {};
|
||||
root.querySelectorAll("#sidebar .group li a").forEach(function (a) {
|
||||
hrefs[a.getAttribute("href")] = true;
|
||||
});
|
||||
var tabs = root.querySelectorAll("#navbar .tabs a");
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
if (hrefs[tabs[i].getAttribute("href")]) return tabs[i];
|
||||
}
|
||||
return root.querySelector("#navbar .tabs a.active");
|
||||
}
|
||||
|
||||
function fixActiveTab() {
|
||||
var current = findActiveTab(document);
|
||||
if (!current) return;
|
||||
document.querySelectorAll("#navbar .tabs a").forEach(function (a) {
|
||||
a.classList.toggle("active", a === current);
|
||||
});
|
||||
}
|
||||
|
||||
function activeLink() {
|
||||
return document.querySelector("#sidebar .group:not(.mobile-tabs) li a.active");
|
||||
}
|
||||
|
||||
function activeGroupTitle() {
|
||||
var a = activeLink();
|
||||
var g = a && a.closest(".group");
|
||||
var t = g && g.querySelector(".group-title");
|
||||
return t ? t.textContent.trim() : "";
|
||||
}
|
||||
|
||||
function buildMobileBar() {
|
||||
var nav = document.getElementById("navbar");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
if (!nav) return;
|
||||
var a = activeLink();
|
||||
var group = activeGroupTitle();
|
||||
var bar = document.createElement("div");
|
||||
bar.id = "mobile-bar";
|
||||
bar.innerHTML =
|
||||
'<button id="mobile-toggle" type="button" aria-label="Toggle navigation" onclick="toggleSidebar()">' + icon("menu") + "</button>" +
|
||||
(group ? "<span>" + esc(group) + '</span><span class="sep">›</span>' : "") +
|
||||
'<span class="crumb-current">' + esc(a ? a.textContent.trim() : document.title) + "</span>";
|
||||
nav.parentNode.insertBefore(bar, nav.nextSibling);
|
||||
var tabs = document.querySelectorAll("#navbar .tabs a");
|
||||
if (sidebar && tabs.length) {
|
||||
var currentTab = document.querySelector("#navbar .tabs a.active") || tabs[0];
|
||||
var g = document.createElement("div");
|
||||
g.className = "mobile-tabs";
|
||||
g.innerHTML =
|
||||
'<button type="button" class="mt-btn" aria-expanded="false"><span>' + esc(currentTab.textContent.trim()) + "</span>" + icon("chevron") + "</button>" +
|
||||
'<div class="mt-menu">' + Array.prototype.map.call(tabs, function (t) {
|
||||
return '<a href="' + t.getAttribute("href") + '"' + (t === currentTab ? ' class="on"' : "") + ">" + esc(t.textContent.trim()) + icon("check") + "</a>";
|
||||
}).join("") + "</div>";
|
||||
sidebar.insertBefore(g, sidebar.firstChild);
|
||||
g.querySelector(".mt-btn").addEventListener("click", function () {
|
||||
g.classList.toggle("open");
|
||||
});
|
||||
document.addEventListener("click", function (e) {
|
||||
if (!g.contains(e.target)) g.classList.remove("open");
|
||||
});
|
||||
g.querySelectorAll(".mt-menu a").forEach(function (a) {
|
||||
a.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var href = a.getAttribute("href");
|
||||
g.querySelectorAll(".mt-menu a").forEach(function (link) {
|
||||
link.classList.remove("on");
|
||||
});
|
||||
a.classList.add("on");
|
||||
g.querySelector(".mt-btn span").textContent = a.textContent.trim();
|
||||
g.classList.remove("open");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
if (sidebar) {
|
||||
var mobileTabs = sidebar.querySelector(".mobile-tabs");
|
||||
var nav = document.getElementById("navbar");
|
||||
var top = Math.max(0, Math.round(nav.getBoundingClientRect().bottom));
|
||||
sidebar.style.setProperty("--sidebar-top", top + "px");
|
||||
fetch(href)
|
||||
.then(function (r) { return r.text(); })
|
||||
.then(function (html) {
|
||||
var doc = new DOMParser().parseFromString(html, "text/html");
|
||||
var newSidebar = doc.querySelector("#sidebar");
|
||||
if (newSidebar) {
|
||||
var groups = newSidebar.querySelectorAll(".group");
|
||||
sidebar.innerHTML = "";
|
||||
if (mobileTabs) sidebar.appendChild(mobileTabs);
|
||||
groups.forEach(function (group) {
|
||||
sidebar.appendChild(group.cloneNode(true));
|
||||
});
|
||||
addEyebrow();
|
||||
}
|
||||
})
|
||||
.catch(function () { });
|
||||
if (!sidebar.classList.contains("open")) {
|
||||
sidebar.classList.add("open");
|
||||
document.body.classList.add("nav-open");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addEyebrow() {
|
||||
var h1 = document.querySelector("main.prose h1");
|
||||
var group = activeGroupTitle();
|
||||
if (!h1 || !group) return;
|
||||
var el = document.createElement("div");
|
||||
el.className = "eyebrow";
|
||||
el.textContent = group;
|
||||
h1.parentNode.insertBefore(el, h1);
|
||||
}
|
||||
|
||||
function addCopyButtons() {
|
||||
document.querySelectorAll(".code-group").forEach(function (cg) {
|
||||
if (cg.querySelector(".cg-copy-btn")) return;
|
||||
var b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "cg-copy-btn";
|
||||
b.setAttribute("aria-label", "Copy");
|
||||
b.innerHTML = icon("copy");
|
||||
b.onclick = function () {
|
||||
var activePre = cg.querySelector(".code-group-panel.active pre");
|
||||
if (!activePre) return;
|
||||
navigator.clipboard.writeText(activePre.innerText).then(function () {
|
||||
b.innerHTML = icon("check");
|
||||
setTimeout(function () { b.innerHTML = icon("copy"); }, 1500);
|
||||
});
|
||||
};
|
||||
cg.appendChild(b);
|
||||
});
|
||||
|
||||
document.querySelectorAll("main.prose pre").forEach(function (pre) {
|
||||
if (pre.closest(".code-group")) return;
|
||||
if (pre.parentNode.classList.contains("code-wrap")) return;
|
||||
var wrap = document.createElement("div");
|
||||
wrap.className = "code-wrap";
|
||||
pre.parentNode.insertBefore(wrap, pre);
|
||||
wrap.appendChild(pre);
|
||||
var b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "copy-btn";
|
||||
b.setAttribute("aria-label", "Copy");
|
||||
b.innerHTML = icon("copy");
|
||||
b.onclick = function () {
|
||||
navigator.clipboard.writeText(pre.innerText).then(function () {
|
||||
b.innerHTML = icon("check");
|
||||
setTimeout(function () { b.innerHTML = icon("copy"); }, 1500);
|
||||
});
|
||||
};
|
||||
wrap.appendChild(b);
|
||||
});
|
||||
}
|
||||
|
||||
function bindScrollFade(box, getScroller) {
|
||||
function update() {
|
||||
var el = getScroller();
|
||||
var max = el.scrollWidth - el.clientWidth;
|
||||
var left = 0;
|
||||
var right = 0;
|
||||
if (max > 1) {
|
||||
left = Math.min(el.scrollLeft, 56);
|
||||
right = Math.min(Math.max(Math.round(max - el.scrollLeft), 0), 56);
|
||||
}
|
||||
box.style.setProperty("--fade-l", left + "px");
|
||||
box.style.setProperty("--fade-r", right + "px");
|
||||
}
|
||||
box.addEventListener("scroll", update, true);
|
||||
window.addEventListener("resize", update);
|
||||
window.addEventListener("load", update);
|
||||
update();
|
||||
}
|
||||
|
||||
function initCodeFades() {
|
||||
document.querySelectorAll("main.prose pre").forEach(function (pre) {
|
||||
var code = pre.querySelector("code");
|
||||
bindScrollFade(pre, function () {
|
||||
return code && code.scrollWidth > code.clientWidth + 1 ? code : pre;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function wrapTables() {
|
||||
document.querySelectorAll("main.prose table").forEach(function (table) {
|
||||
if (table.parentNode.classList.contains("table-wrap")) return;
|
||||
var wrap = document.createElement("div");
|
||||
wrap.className = "table-wrap";
|
||||
table.parentNode.insertBefore(wrap, table);
|
||||
wrap.appendChild(table);
|
||||
bindScrollFade(wrap, function () {
|
||||
return wrap;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initTocSpy() {
|
||||
var links = Array.prototype.slice.call(document.querySelectorAll("aside#toc a[href^='#']"));
|
||||
if (!links.length) return;
|
||||
var heads = links.map(function (a) {
|
||||
return document.getElementById(decodeURIComponent(a.getAttribute("href").slice(1)));
|
||||
});
|
||||
function update() {
|
||||
var idx = 0;
|
||||
heads.forEach(function (h, i) {
|
||||
if (h && h.getBoundingClientRect().top < 140) idx = i;
|
||||
});
|
||||
links.forEach(function (a, i) { a.classList.toggle("active", i === idx); });
|
||||
}
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
update();
|
||||
}
|
||||
|
||||
var SEARCH_KEY = "vyla-search-v1";
|
||||
var searchPromise = null;
|
||||
|
||||
function getRecent() {
|
||||
try { return JSON.parse(localStorage.getItem("vyla-recent-v2")) || []; } catch (e) { return []; }
|
||||
}
|
||||
|
||||
function saveRecent(item) {
|
||||
if (!item) return;
|
||||
var slim = { title: item.title, crumbs: item.crumbs, href: item.href, type: item.type, text: "" };
|
||||
var list = getRecent().filter(function (r) { return r.href !== slim.href; });
|
||||
list.unshift(slim);
|
||||
localStorage.setItem("vyla-recent-v2", JSON.stringify(list.slice(0, 5)));
|
||||
}
|
||||
|
||||
function cleanText(s) {
|
||||
return s.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function fetchDoc(href) {
|
||||
return fetch(href)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error(href);
|
||||
return r.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
return { href: href, doc: new DOMParser().parseFromString(html, "text/html") };
|
||||
})
|
||||
.catch(function () {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function indexDoc(res) {
|
||||
var doc = res.doc;
|
||||
var href = res.href.split("#")[0];
|
||||
var prose = doc.querySelector("main.prose");
|
||||
if (!prose) return [];
|
||||
var tabEl = findActiveTab(doc);
|
||||
var activeEl = doc.querySelector("#sidebar li a.active");
|
||||
var groupEl = activeEl && activeEl.closest(".group");
|
||||
var groupTitleEl = groupEl && groupEl.querySelector(".group-title");
|
||||
var h1 = prose.querySelector("h1");
|
||||
var pageTitle = h1 ? cleanText(h1.textContent) : activeEl ? cleanText(activeEl.textContent) : href;
|
||||
var base = [];
|
||||
if (tabEl) base.push(cleanText(tabEl.textContent));
|
||||
if (groupTitleEl) base.push(cleanText(groupTitleEl.textContent));
|
||||
var current = { title: pageTitle, id: "", type: "page", crumbs: base.slice(), text: [] };
|
||||
var entries = [current];
|
||||
Array.prototype.forEach.call(prose.children, function (el) {
|
||||
if (el.tagName === "H1") return;
|
||||
if (el.tagName === "H2" || el.tagName === "H3") {
|
||||
var title = cleanText(el.textContent);
|
||||
current = { title: title, id: el.id, type: "heading", crumbs: base.concat([pageTitle, title]), text: [] };
|
||||
entries.push(current);
|
||||
return;
|
||||
}
|
||||
current.text.push(cleanText(el.textContent));
|
||||
});
|
||||
return entries.map(function (e) {
|
||||
return {
|
||||
title: e.title,
|
||||
crumbs: e.crumbs,
|
||||
href: e.id ? href + "#" + e.id : href,
|
||||
type: e.type,
|
||||
text: e.text.join(" ").slice(0, 4000)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function crawlSite() {
|
||||
var seen = {};
|
||||
var all = [];
|
||||
function visit(hrefs) {
|
||||
var fresh = hrefs.map(function (h) {
|
||||
return new URL(h, window.location.href);
|
||||
}).filter(function (u) {
|
||||
if (u.origin !== window.location.origin || seen[u.pathname]) return false;
|
||||
seen[u.pathname] = true;
|
||||
return true;
|
||||
}).map(function (u) {
|
||||
return u.pathname;
|
||||
});
|
||||
return Promise.all(fresh.map(fetchDoc)).then(function (results) {
|
||||
var next = [];
|
||||
results.forEach(function (res) {
|
||||
if (!res) return;
|
||||
all = all.concat(indexDoc(res));
|
||||
res.doc.querySelectorAll("#sidebar li a").forEach(function (a) {
|
||||
var h = a.getAttribute("href");
|
||||
if (h && h.charAt(0) !== "#") next.push(h);
|
||||
});
|
||||
});
|
||||
return next.length ? visit(next) : all;
|
||||
});
|
||||
}
|
||||
var start = Array.prototype.map.call(document.querySelectorAll("#navbar .tabs a"), function (a) {
|
||||
return a.getAttribute("href");
|
||||
});
|
||||
start.push(window.location.pathname);
|
||||
return visit(start).then(function () {
|
||||
return all;
|
||||
});
|
||||
}
|
||||
|
||||
function loadSearchData() {
|
||||
if (searchData) return Promise.resolve(searchData);
|
||||
if (searchPromise) return searchPromise;
|
||||
try {
|
||||
var cached = JSON.parse(sessionStorage.getItem(SEARCH_KEY));
|
||||
if (cached && cached.data && cached.data.length && Date.now() - cached.time < 600000) {
|
||||
searchData = cached.data;
|
||||
return Promise.resolve(searchData);
|
||||
}
|
||||
} catch (e) { }
|
||||
searchPromise = crawlSite().then(function (all) {
|
||||
searchData = all;
|
||||
try { sessionStorage.setItem(SEARCH_KEY, JSON.stringify({ time: Date.now(), data: all })); } catch (e) { }
|
||||
return all;
|
||||
});
|
||||
return searchPromise;
|
||||
}
|
||||
|
||||
function escRe(s) {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function hl(text, tokens) {
|
||||
if (!tokens.length) return esc(text);
|
||||
var re = new RegExp("(" + tokens.map(escRe).join("|") + ")", "ig");
|
||||
return text.split(re).map(function (part, i) {
|
||||
return i % 2 ? '<b class="hit">' + esc(part) + "</b>" : esc(part);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function lower(it) {
|
||||
if (!it._l) it._l = { t: it.title.toLowerCase(), c: it.crumbs.join(" ").toLowerCase(), x: (it.text || "").toLowerCase() };
|
||||
return it._l;
|
||||
}
|
||||
|
||||
function scoreEntry(it, tokens, q) {
|
||||
var low = lower(it);
|
||||
var score = 0;
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var t = tokens[i];
|
||||
var inTitle = low.t.indexOf(t) !== -1;
|
||||
var inCrumbs = low.c.indexOf(t) !== -1;
|
||||
var inText = low.x.indexOf(t) !== -1;
|
||||
if (!inTitle && !inCrumbs && !inText) return 0;
|
||||
score += (inTitle ? 20 : 0) + (inCrumbs ? 4 : 0) + (inText ? 1 : 0);
|
||||
}
|
||||
if (low.t === q) score += 100;
|
||||
else if (low.t.indexOf(q) === 0) score += 50;
|
||||
else if (low.t.indexOf(q) !== -1) score += 25;
|
||||
if (it.type === "page") score += 5;
|
||||
return score;
|
||||
}
|
||||
|
||||
function makeSnippet(it, tokens) {
|
||||
var low = lower(it);
|
||||
var inTitle = tokens.every(function (t) { return low.t.indexOf(t) !== -1; });
|
||||
if (inTitle || !it.text) return "";
|
||||
var hit = -1;
|
||||
tokens.forEach(function (t) {
|
||||
var k = low.x.indexOf(t);
|
||||
if (k !== -1 && (hit === -1 || k < hit)) hit = k;
|
||||
});
|
||||
if (hit === -1) return "";
|
||||
var start = Math.max(0, hit - 50);
|
||||
var end = hit + 120;
|
||||
return (start > 0 ? "…" : "") + it.text.slice(start, end) + (end < it.text.length ? "…" : "");
|
||||
}
|
||||
|
||||
function renderSearch(keepActive) {
|
||||
var q = document.getElementById("search-input").value.trim().toLowerCase();
|
||||
var tokens = q.split(/\s+/).filter(Boolean);
|
||||
var box = document.getElementById("search-results");
|
||||
if (!keepActive) searchActive = 0;
|
||||
if (tokens.length) {
|
||||
if (!searchData) {
|
||||
searchResults = [];
|
||||
box.innerHTML = '<div class="search-label">Indexing pages...</div>';
|
||||
return;
|
||||
}
|
||||
searchResults = searchData
|
||||
.map(function (it) { return { it: it, s: scoreEntry(it, tokens, q) }; })
|
||||
.filter(function (r) { return r.s > 0; })
|
||||
.sort(function (a, b) { return b.s - a.s; })
|
||||
.slice(0, 12)
|
||||
.map(function (r) { return r.it; });
|
||||
} else {
|
||||
searchResults = getRecent();
|
||||
}
|
||||
if (!searchResults.length) {
|
||||
box.innerHTML = '<div class="search-label">' + (tokens.length ? "No results found" : "No recent searches") + "</div>";
|
||||
return;
|
||||
}
|
||||
box.innerHTML = (tokens.length ? "" : '<div class="search-label">Recent searches</div>') + searchResults.map(function (it, i) {
|
||||
var snippet = tokens.length ? makeSnippet(it, tokens) : "";
|
||||
var crumbs = it.crumbs.map(function (c) { return hl(c, tokens); }).join('<span class="sep">›</span>');
|
||||
return '<a class="sr' + (i === searchActive ? " on" : "") + '" href="' + esc(it.href) + '" data-i="' + i + '">' +
|
||||
icon(it.type === "heading" ? "hash" : "doc") +
|
||||
'<div class="sr-body"><div class="t">' + esc(it.title) + "</div>" +
|
||||
(snippet ? '<div class="s">' + hl(snippet, tokens) + "</div>" : "") +
|
||||
'<div class="p">' + crumbs + "</div></div></a>";
|
||||
}).join("");
|
||||
if (keepActive) {
|
||||
var on = box.querySelector(".sr.on");
|
||||
if (on) on.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
|
||||
function openSearch() {
|
||||
var input = document.getElementById("search-input");
|
||||
document.getElementById("search-modal").classList.add("open");
|
||||
document.body.style.overflow = "hidden";
|
||||
input.value = "";
|
||||
renderSearch(false);
|
||||
loadSearchData().then(function () { renderSearch(false); });
|
||||
input.focus();
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
document.getElementById("search-modal").classList.remove("open");
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
|
||||
function onSearchKey(e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "k") {
|
||||
e.preventDefault();
|
||||
openSearch();
|
||||
return;
|
||||
}
|
||||
var modal = document.getElementById("search-modal");
|
||||
if (!modal || !modal.classList.contains("open")) return;
|
||||
if (e.key === "Escape") {
|
||||
closeSearch();
|
||||
} else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
var n = searchResults.length || 1;
|
||||
searchActive = (searchActive + (e.key === "ArrowDown" ? 1 : -1) + n) % n;
|
||||
renderSearch(true);
|
||||
} else if (e.key === "Enter" && searchResults[searchActive]) {
|
||||
var item = searchResults[searchActive];
|
||||
saveRecent(item);
|
||||
closeSearch();
|
||||
window.location.href = item.href;
|
||||
}
|
||||
}
|
||||
|
||||
function buildSearch() {
|
||||
var trigger = document.getElementById("search-trigger");
|
||||
if (trigger) trigger.innerHTML = icon("search") + "<span>Search...</span><kbd>Ctrl K</kbd>";
|
||||
var modal = document.createElement("div");
|
||||
modal.id = "search-modal";
|
||||
modal.innerHTML =
|
||||
'<div class="search-backdrop"></div><div class="search-panel"><div class="search-input-row">' + icon("search") +
|
||||
'<input id="search-input" type="text" placeholder="Search..." autocomplete="off" spellcheck="false"></div>' +
|
||||
'<div id="search-results"></div><div class="search-footer"><span><kbd>↑</kbd><kbd>↓</kbd>Select</span><span><kbd>↵</kbd>Open</span><span class="close-hint"><kbd>esc</kbd>Close</span></div></div>';
|
||||
document.body.appendChild(modal);
|
||||
modal.querySelector(".search-backdrop").addEventListener("click", closeSearch);
|
||||
document.getElementById("search-input").addEventListener("input", function () { renderSearch(false); });
|
||||
document.getElementById("search-results").addEventListener("click", function (e) {
|
||||
var a = e.target.closest(".sr");
|
||||
if (a) {
|
||||
saveRecent(searchResults[Number(a.getAttribute("data-i"))]);
|
||||
closeSearch();
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", onSearchKey);
|
||||
}
|
||||
|
||||
applyTheme();
|
||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyTheme);
|
||||
|
||||
function dismissBanner() {
|
||||
var banner = document.getElementById("banner");
|
||||
if (banner) banner.setAttribute("data-dismissed", "true");
|
||||
document.body.classList.add("banner-dismissed");
|
||||
localStorage.setItem("vyla-banner-dismissed", "true");
|
||||
}
|
||||
|
||||
function cgSwitch(btn) {
|
||||
var group = btn.closest(".code-group");
|
||||
var index = btn.getAttribute("data-tab-index");
|
||||
group.querySelectorAll(".code-group-tab").forEach(function (t) {
|
||||
t.classList.toggle("active", t === btn);
|
||||
});
|
||||
group.querySelectorAll(".code-group-panel").forEach(function (p, i) {
|
||||
p.classList.toggle("active", String(i) === index);
|
||||
});
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (localStorage.getItem("vyla-banner-dismissed") === "true") {
|
||||
var banner = document.getElementById("banner");
|
||||
if (banner) banner.setAttribute("data-dismissed", "true");
|
||||
document.body.classList.add("banner-dismissed");
|
||||
}
|
||||
applyTheme();
|
||||
fixActiveTab();
|
||||
buildMobileBar();
|
||||
addEyebrow();
|
||||
addCopyButtons();
|
||||
initCodeFades();
|
||||
wrapTables();
|
||||
buildThemeControls();
|
||||
buildSearch();
|
||||
setTimeout(loadSearchData, 1500);
|
||||
initTocSpy();
|
||||
document.addEventListener("click", function (e) {
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var menu = document.getElementById("theme-menu");
|
||||
if (menu && !e.target.closest(".theme-wrap")) menu.classList.remove("open");
|
||||
if (
|
||||
sidebar &&
|
||||
sidebar.classList.contains("open") &&
|
||||
!sidebar.contains(e.target) &&
|
||||
!e.target.closest("#mobile-toggle") &&
|
||||
!e.target.closest(".theme-wrap") &&
|
||||
!e.target.closest("#theme-seg")
|
||||
) {
|
||||
sidebar.classList.remove("open");
|
||||
document.body.classList.remove("nav-open");
|
||||
}
|
||||
});
|
||||
var sidebarLinks = document.querySelectorAll("#sidebar .group:not(.mobile-tabs) li a");
|
||||
sidebarLinks.forEach(function (a) {
|
||||
a.addEventListener("click", function () {
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
if (sidebar && sidebar.classList.contains("open")) {
|
||||
sidebar.classList.remove("open");
|
||||
document.body.classList.remove("nav-open");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
1501
assets/base.css
Normal file
107
assets/highlight.css
Normal file
@@ -0,0 +1,107 @@
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 1em;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: #5e5e5e;
|
||||
background: #0d1117
|
||||
}
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-keyword,
|
||||
.hljs-meta .hljs-keyword,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable.language_ {
|
||||
color: #ff7b72
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-title.class_,
|
||||
.hljs-title.class_.inherited__,
|
||||
.hljs-title.function_ {
|
||||
color: #d2a8ff
|
||||
}
|
||||
|
||||
.hljs-attr,
|
||||
.hljs-attribute,
|
||||
.hljs-literal,
|
||||
.hljs-meta,
|
||||
.hljs-number,
|
||||
.hljs-operator,
|
||||
.hljs-variable,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-id {
|
||||
color: #79c0ff
|
||||
}
|
||||
|
||||
.hljs-regexp,
|
||||
.hljs-string,
|
||||
.hljs-meta .hljs-string {
|
||||
color: #a5d6ff
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-symbol {
|
||||
color: #ffa657
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-code,
|
||||
.hljs-formula {
|
||||
color: #8b949e
|
||||
}
|
||||
|
||||
.hljs-name,
|
||||
.hljs-quote,
|
||||
.hljs-selector-tag,
|
||||
.hljs-selector-pseudo {
|
||||
color: #7ee787
|
||||
}
|
||||
|
||||
.hljs-subst {
|
||||
color: #5e5e5e
|
||||
}
|
||||
|
||||
.hljs-section {
|
||||
color: #1f6feb;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.hljs-bullet {
|
||||
color: #f2cc60
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
color: #5e5e5e;
|
||||
font-style: italic
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
color: #5e5e5e;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #aff5b4;
|
||||
background-color: #033a16
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #ffdcd7;
|
||||
background-color: #67060c
|
||||
}
|
||||
BIN
assets/images/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
assets/images/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/images/android-tv/site-preview.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/images/android/site_preview.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
assets/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/images/dark_logo.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/desktop-preview.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
assets/images/desktop/site_preview.png
Normal file
|
After Width: | Height: | Size: 1016 KiB |
BIN
assets/images/devices.png
Normal file
|
After Width: | Height: | Size: 441 KiB |
BIN
assets/images/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 360 B |
BIN
assets/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 652 B |
BIN
assets/images/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/header_logo.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/partners/fmhy.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/images/partners/piracy.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
assets/images/rounded-banner.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
assets/images/thumbnail.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
121
assets/mintlify-overrides.css
Normal file
@@ -0,0 +1,121 @@
|
||||
:root {
|
||||
--table-header-text: #000000;
|
||||
--vyla-black: #000000;
|
||||
--vyla-white: #ffffff;
|
||||
--vyla-border-light: rgba(0, 0, 0, 0.12);
|
||||
--vyla-border-dark: rgba(255, 255, 255, 0.16);
|
||||
--banner-bg: #ffffff;
|
||||
--banner-text: #000000;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--table-header-text: #ffffff;
|
||||
--banner-bg: #000000;
|
||||
--banner-text: #ffffff;
|
||||
}
|
||||
|
||||
table th {
|
||||
color: var(--table-header-text) !important;
|
||||
}
|
||||
|
||||
#banner,
|
||||
#banner *,
|
||||
#banner p,
|
||||
#banner a {
|
||||
color: var(--banner-text) !important;
|
||||
}
|
||||
|
||||
.data-\[is-opaque\=true\]\:bg-background-light[data-is-opaque=true] {
|
||||
background-color: rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.data-\[is-opaque\=true\]\:dark\:bg-background-dark\/75:is(.dark *)[data-is-opaque=true] {
|
||||
background-color: rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
#banner a {
|
||||
text-decoration: underline !important;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
#banner button {
|
||||
color: var(--banner-text) !important;
|
||||
}
|
||||
|
||||
#navbar a[href*="gitlab"],
|
||||
#navbar a[href*="discord"],
|
||||
#navbar a[href="/"] {
|
||||
border-radius: 999px !important;
|
||||
}
|
||||
|
||||
#navbar a[href="/"] {
|
||||
padding-left: 16px !important;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
|
||||
.dark\:border-gray-300\/\[0\.06\]:is(.dark *) {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:ring-0:is(.dark *),
|
||||
.dark\:ring-1:is(.dark *),
|
||||
.ring-1,
|
||||
.ring-2 {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.dark\:hover\:\!border-primary-light:hover:is(.dark *) {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.hover\:\!border-primary:hover {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:border-white\/10:is(.dark *) {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:bg-white\/10:is(.dark *) {
|
||||
background-color: rgba(25, 25, 25, 0.1) !important;
|
||||
}
|
||||
|
||||
html.dark .code-block-background {
|
||||
background-color: rgba(5, 5, 5) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
html .code-block-background {
|
||||
background-color: rgba(250, 250, 250) !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
[data-fade-overlay] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.h-\[1\.5px\] {
|
||||
height: 0px !important;
|
||||
}
|
||||
|
||||
.prose :where(img):not(:where([class~=not-prose], [class~=not-prose] *)) {
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
html.dark [data-fade-overlay] {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.dark\:text-gray-500:is(.dark *) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
293
concepts/providers/index.html
Normal file
@@ -0,0 +1,293 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Providers — Vyla</title>
|
||||
<meta name="description" content="How Vyla SDK sources are configured, selected, and verified." />
|
||||
<meta name="keywords"
|
||||
content="providers, source configuration, stream sources, provider verification, SDK providers, source selection" />
|
||||
<link rel="canonical" href="https://vyla.cc/concepts/providers.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/concepts/providers.html" />
|
||||
<meta property="og:title" content="Providers — Vyla" />
|
||||
<meta property="og:description" content="How Vyla SDK sources are configured, selected, and verified." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/concepts/providers.html" />
|
||||
<meta property="twitter:title" content="Providers — Vyla" />
|
||||
<meta property="twitter:description" content="How Vyla SDK sources are configured, selected, and verified." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Providers",
|
||||
"description": "How Vyla SDK sources are configured, selected, and verified.",
|
||||
"url": "https://vyla.cc/concepts/providers.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
</li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Providers</h1>
|
||||
<p class="page-description">How Vyla SDK sources are configured, selected, and verified.</p>
|
||||
<p>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.</p>
|
||||
<div class="callout callout-note">
|
||||
<div class="callout-content">
|
||||
<p>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.</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="how-providers-are-used">How Providers Are Used</h2>
|
||||
<p>When your app calls <code>getStream()</code>—directly or through Stream API:</p>
|
||||
<ol>
|
||||
<li>All active (non-disabled) providers are queried <strong>simultaneously</strong> with per-source random
|
||||
jitter to stagger upstream requests</li>
|
||||
<li>Each provider has its own <strong>timeout</strong>, <strong>retry count</strong>, and <strong>backoff
|
||||
delay</strong></li>
|
||||
<li>Every returned URL is <strong>verified</strong> — first via a HEAD request to the raw upstream URL, then
|
||||
via a proxied HLS playability check</li>
|
||||
<li>Only URLs that pass both verification steps appear in the response as <code>source</code> events</li>
|
||||
</ol>
|
||||
<p>Because the endpoint is SSE, <strong>verified sources are emitted immediately as each provider
|
||||
resolves</strong> — your player receives them progressively. The stream closes once every provider has
|
||||
either resolved or timed out.</p>
|
||||
<div class="callout callout-tip">
|
||||
<div class="callout-content">
|
||||
<p>Call <code>sdk.getSources(true)</code> to inspect enabled providers and
|
||||
<code>sdk.probeAllSources()</code> for a live availability signal. If you use Stream API, its <a
|
||||
href="/stream-api/operations"><code>/health</code></a> endpoint exposes probes over HTTP.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="provider-configuration">Provider Configuration</h2>
|
||||
<p>Each provider is defined with the following properties:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>key</code></td>
|
||||
<td>Internal identifier used in API responses and the <code>source</code> field</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>label</code></td>
|
||||
<td>Human-readable name shown in the <code>label</code> field</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>proxyParam</code></td>
|
||||
<td>Short query parameter used to route proxied requests</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>timeout</code></td>
|
||||
<td>Maximum time (ms) to wait for a stream URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sourcesTimeout</code></td>
|
||||
<td>Optional secondary timeout (ms) for an internal source-listing step, used by some providers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>jitter</code></td>
|
||||
<td>Max random delay (ms) before the request fires, to stagger upstream hits</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>retries</code></td>
|
||||
<td>Number of retry attempts on failure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>skipProxy</code></td>
|
||||
<td>If <code>true</code>, stream URLs are returned as-is without proxy wrapping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>disabled</code></td>
|
||||
<td>If <code>true</code>, the provider is excluded from all requests</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Providers with <code>disabled: true</code> are completely skipped — they won't appear in health checks or
|
||||
fanout responses.</p>
|
||||
<hr>
|
||||
<h2 id="response-time">Response Time</h2>
|
||||
<p>The first <code>source</code> event typically arrives within <strong>3–8 seconds</strong>. Subsequent sources
|
||||
stream in as each provider resolves. The stream closes once the <strong>slowest configured provider</strong>
|
||||
times out — in the worst case this can be 60+ seconds (e.g. <code>cinezo</code> has a 60 s timeout), though
|
||||
most sources emit well before then.</p>
|
||||
<p><strong>Best practice:</strong> always show a loading state, begin playback on the first <code>source</code>
|
||||
event, and implement a client-side timeout for the overall stream. See the <a
|
||||
href="/guides/error-handling">Error Handling</a> guide.</p>
|
||||
<hr>
|
||||
<h2 id="caching">Caching</h2>
|
||||
<p>Results are cached in-memory with a <strong>5-minute TTL</strong>. The per-source stream cache is keyed by
|
||||
<code>source_key + tmdb_id + season + episode</code>; the aggregate source-results cache is keyed by
|
||||
<code>tmdb_id + season + episode + base</code>. Cache hits skip the provider fetch entirely and return
|
||||
near-instantly.
|
||||
</p>
|
||||
<pre><code>CACHE_TTL = 5 minutes
|
||||
</code></pre>
|
||||
<p>The cache resets on server restart. Cache size is reported in the <code>/api/health</code> response under the
|
||||
<code>cache</code> field.
|
||||
</p>
|
||||
<hr>
|
||||
<h2 id="the-hls-proxy">The HLS Proxy</h2>
|
||||
<p>All provider stream URLs are wrapped in the server proxy (unless <code>skipProxy: true</code>):</p>
|
||||
<pre><code>http://localhost:7860/api?url=<encoded_stream_url>&<proxyParam>=1
|
||||
</code></pre>
|
||||
<p>When your player fetches this path from your self-hosted instance, the server:</p>
|
||||
<ol>
|
||||
<li>Fetches the upstream M3U8 with the correct <code>Referer</code>, <code>Origin</code>, and any
|
||||
provider-specific headers</li>
|
||||
<li>Rewrites all segment URLs and encryption key URIs in the playlist to route through the same proxy</li>
|
||||
<li>Returns the rewritten playlist with <code>Access-Control-Allow-Origin: *</code></li>
|
||||
</ol>
|
||||
<p>This means <strong>your player never talks to the provider directly</strong> — all HLS traffic flows through
|
||||
the proxy, keeping CORS and rate-limiting issues fully contained.</p>
|
||||
<hr>
|
||||
<h2 id="stream-verification">Stream Verification</h2>
|
||||
<p>Before a URL is emitted as a <code>source</code> event, it passes two verification steps:</p>
|
||||
<ol>
|
||||
<li><strong><code>verifyStream</code></strong> — a HEAD request to the raw upstream URL (skipped for
|
||||
<code>SKIP_VERIFY</code> sources). Must return HTTP <code>< 400</code>.
|
||||
</li>
|
||||
<li><strong><code>verifyPlayable</code></strong> — fetches the proxied URL, validates <code>#EXTM3U</code>,
|
||||
checks for <code>#EXTINF</code> or <code>#EXT-X-STREAM-INF</code>, then fetches the first segment with a
|
||||
range request.</li>
|
||||
</ol>
|
||||
<p>Sources that fail either step — HTTP errors, timeouts, non-HLS/non-MP4 content — are silently dropped. MP4
|
||||
sources skip the <code>#EXTM3U</code> check and are verified via HEAD + content-type inspection instead. HTTP
|
||||
429 responses and timeout/abort errors are treated as <strong>passing</strong> (stream exists but is
|
||||
rate-limited).</p>
|
||||
<div class="callout callout-note">
|
||||
<div class="callout-content">
|
||||
<p><code>/test</code> runs the same two-step verification pipeline as <code>/movie</code> and
|
||||
<code>/tv</code>. Check the <code>ok</code> field in the response body — HTTP status is always
|
||||
<code>200</code> regardless of result.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="disabled-providers">Disabled Providers</h2>
|
||||
<p>Providers marked <code>disabled: true</code> in config are completely excluded from:</p>
|
||||
<ul>
|
||||
<li><code>/api/movie</code> and <code>/api/tv</code> fanout</li>
|
||||
<li><code>/api/health</code> probes</li>
|
||||
<li><code>GET /api?sources_meta</code> listings</li>
|
||||
</ul>
|
||||
<p>If a provider you expect to see is missing from responses, check whether it's been disabled.</p>
|
||||
<hr>
|
||||
<h2 id="multi-url-sources">Multi-URL Sources</h2>
|
||||
<p>Some providers return multiple candidate stream URLs (via <code>allUrls</code>). For these, verification runs
|
||||
through each candidate in order and returns the first one that passes. If none pass, the source is dropped.
|
||||
</p>
|
||||
<p>For providers with <code>SKIP_VERIFY</code>, all candidate URLs are returned as separate source events
|
||||
without verification. These are not distinguished by numbered labels in the response.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#how-providers-are-used">How Providers Are Used</a></li>
|
||||
<li><a href="#provider-configuration">Provider Configuration</a></li>
|
||||
<li><a href="#response-time">Response Time</a></li>
|
||||
<li><a href="#caching">Caching</a></li>
|
||||
<li><a href="#the-hls-proxy">The HLS Proxy</a></li>
|
||||
<li><a href="#stream-verification">Stream Verification</a></li>
|
||||
<li><a href="#disabled-providers">Disabled Providers</a></li>
|
||||
<li><a href="#multi-url-sources">Multi-URL Sources</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
favicon.ico
Normal file
|
After Width: | Height: | Size: 27 KiB |
152
go/index.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Vyla Go — Vyla</title>
|
||||
<meta name="description" content="The Android control center for running the Vyla media stack locally." />
|
||||
<meta name="keywords"
|
||||
content="Vyla Go, Android media player, mobile streaming, self-hosted media, Android app, media streaming" />
|
||||
<link rel="canonical" href="https://vyla.cc/go.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/go.html" />
|
||||
<meta property="og:title" content="Vyla Go — Vyla" />
|
||||
<meta property="og:description" content="The Android control center for running the Vyla media stack locally." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/android-preview.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/go.html" />
|
||||
<meta property="twitter:title" content="Vyla Go — Vyla" />
|
||||
<meta property="twitter:description" content="The Android control center for running the Vyla media stack locally." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/android-preview.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Vyla Go",
|
||||
"description": "The Android control center for running the Vyla media stack locally.",
|
||||
"url": "https://vyla.cc/go.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
</li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="active" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Vyla Go</h1>
|
||||
<p class="page-description">The Android control center for running the Vyla media stack locally.</p>
|
||||
<img class="vyla-banner" src="/assets/images/android/site_preview.png"
|
||||
alt="A collage of films and shows available through a Vyla experience" loading="lazy" fetchpriority="low">
|
||||
<p><strong>Vyla Go</strong> 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.</p>
|
||||
<h2 id="downloads">Downloads</h2>
|
||||
<p>Vyla Go is available for Android. Get the latest version below:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Android</td>
|
||||
<td><a href="https://gitlab.com/vyla-entertainment/go/-/releases">Download .apk</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Inside the releases page, you can find the latest version of Vyla Go for Android, find the <code>.apk</code>
|
||||
file, and install it on your device.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#downloads">Downloads</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
432
guides/error-handling/index.html
Normal file
@@ -0,0 +1,432 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Stream API error handling — Vyla</title>
|
||||
<meta name="description"
|
||||
content="Handle SSE failures, source fallbacks, and edge cases in an optional Stream API client." />
|
||||
<meta name="keywords"
|
||||
content="error handling, fallbacks, SSE errors, streaming errors, retry logic, error boundary, source fallback" />
|
||||
<link rel="canonical" href="https://vyla.cc/guides/error-handling.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/guides/error-handling.html" />
|
||||
<meta property="og:title" content="Stream API error handling — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="Handle SSE failures, source fallbacks, and edge cases in an optional Stream API client." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/guides/error-handling.html" />
|
||||
<meta property="twitter:title" content="Stream API error handling — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Handle SSE failures, source fallbacks, and edge cases in an optional Stream API client." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Stream API error handling",
|
||||
"description": "Handle SSE failures, source fallbacks, and edge cases in an optional Stream API client.",
|
||||
"url": "https://vyla.cc/guides/error-handling.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Guides</p>
|
||||
<ul>
|
||||
<li><a class="" href="/guides/integrations">Integrations</a></li>
|
||||
<li><a class="active" href="/guides/error-handling">Error Handling</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Stream API error handling</h1>
|
||||
<p class="page-description">Handle SSE failures, source fallbacks, and edge cases in an optional Stream API
|
||||
client.</p>
|
||||
<p>The <code>/movie</code> and <code>/tv</code> 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.</p>
|
||||
<div class="callout callout-note">
|
||||
<div class="callout-content">
|
||||
<p>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.</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="error-response-shape">Error Response Shape</h2>
|
||||
<p>Non-streaming endpoints (<code>/health</code>, <code>/subtitles</code>, <code>/downloads</code>,
|
||||
<code>/test</code>) return standard JSON errors:
|
||||
</p>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span> <span class="hljs-attr">"error"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"description of what went wrong"</span> <span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP Status</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>Missing or invalid request parameters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>No data found (subtitles, downloads)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>500</code></td>
|
||||
<td>Unexpected server error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>For <code>/movie</code> and <code>/tv</code>, HTTP <code>200</code> is returned immediately when the SSE
|
||||
stream opens. Failures surface as <strong>zero <code>source</code> events</strong> followed by a
|
||||
<code>done</code> event with <code>total: 0</code> — not as HTTP error codes.
|
||||
</p>
|
||||
<hr>
|
||||
<h2 id="handling-no-sources">Handling No Sources</h2>
|
||||
<p>The most common failure — all providers failed to return a playable stream. Detect it by checking
|
||||
<code>total</code> in the <code>done</code> event.
|
||||
</p>
|
||||
<pre><code class="hljs language-typescript"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">BASE</span> = <span class="hljs-string">'http://localhost:7860'</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">`<span class="hljs-subst">${BASE}</span>/movie?id=550`</span>);
|
||||
<span class="hljs-keyword">const</span> reader = res.<span class="hljs-property">body</span>!.<span class="hljs-title function_">getReader</span>();
|
||||
<span class="hljs-keyword">const</span> decoder = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TextDecoder</span>();
|
||||
<span class="hljs-keyword">let</span> buffer = <span class="hljs-string">''</span>;
|
||||
<span class="hljs-keyword">let</span> started = <span class="hljs-literal">false</span>;
|
||||
|
||||
<span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
|
||||
<span class="hljs-keyword">const</span> { done, value } = <span class="hljs-keyword">await</span> reader.<span class="hljs-title function_">read</span>();
|
||||
<span class="hljs-keyword">if</span> (done) <span class="hljs-keyword">break</span>;
|
||||
|
||||
buffer += decoder.<span class="hljs-title function_">decode</span>(value, { <span class="hljs-attr">stream</span>: <span class="hljs-literal">true</span> });
|
||||
<span class="hljs-keyword">const</span> lines = buffer.<span class="hljs-title function_">split</span>(<span class="hljs-string">'\n'</span>);
|
||||
buffer = lines.<span class="hljs-title function_">pop</span>()!;
|
||||
|
||||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> line <span class="hljs-keyword">of</span> lines) {
|
||||
<span class="hljs-keyword">if</span> (!line.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'data: '</span>)) <span class="hljs-keyword">continue</span>;
|
||||
<span class="hljs-keyword">const</span> event = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(line.<span class="hljs-title function_">slice</span>(<span class="hljs-number">6</span>));
|
||||
|
||||
<span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> === <span class="hljs-string">'source'</span>) { started = <span class="hljs-literal">true</span>; <span class="hljs-title function_">playSource</span>(event.<span class="hljs-property">source</span>.<span class="hljs-property">url</span>); }
|
||||
|
||||
<span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> === <span class="hljs-string">'done'</span> && !started) {
|
||||
<span class="hljs-title function_">showError</span>(<span class="hljs-string">'This title is currently unavailable. Try again later.'</span>);
|
||||
}
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="source-fallback-queue">Source Fallback Queue</h2>
|
||||
<p>Sources stream in as providers resolve. Queue every <code>source</code> event URL and fall back to the next
|
||||
one if playback fails mid-stream:</p>
|
||||
<pre><code class="hljs language-typescript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Hls</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'hls.js'</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">BASE</span> = <span class="hljs-string">'http://localhost:7860'</span>;
|
||||
|
||||
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">playWithFallback</span>(<span class="hljs-params">
|
||||
<span class="hljs-attr">videoEl</span>: <span class="hljs-title class_">HTMLVideoElement</span>,
|
||||
<span class="hljs-attr">tmdbId</span>: <span class="hljs-built_in">number</span>,
|
||||
<span class="hljs-attr">season</span>?: <span class="hljs-built_in">number</span>,
|
||||
<span class="hljs-attr">episode</span>?: <span class="hljs-built_in">number</span>,
|
||||
<span class="hljs-attr">onSourceChange</span>?: (label: <span class="hljs-built_in">string</span>) => <span class="hljs-built_in">void</span>
|
||||
</span>) {
|
||||
<span class="hljs-keyword">const</span> endpoint = season && episode
|
||||
? <span class="hljs-string">`<span class="hljs-subst">${BASE}</span>/tv?id=<span class="hljs-subst">${tmdbId}</span>&season=<span class="hljs-subst">${season}</span>&episode=<span class="hljs-subst">${episode}</span>`</span>
|
||||
: <span class="hljs-string">`<span class="hljs-subst">${BASE}</span>/movie?id=<span class="hljs-subst">${tmdbId}</span>`</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-attr">queue</span>: { <span class="hljs-attr">url</span>: <span class="hljs-built_in">string</span>; <span class="hljs-attr">label</span>: <span class="hljs-built_in">string</span> }[] = [];
|
||||
<span class="hljs-keyword">let</span> started = <span class="hljs-literal">false</span>;
|
||||
<span class="hljs-keyword">let</span> <span class="hljs-attr">hls</span>: <span class="hljs-title class_">Hls</span> | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;
|
||||
|
||||
<span class="hljs-keyword">function</span> <span class="hljs-title function_">tryNext</span>(<span class="hljs-params"></span>) {
|
||||
<span class="hljs-keyword">if</span> (!queue.<span class="hljs-property">length</span>) {
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">'All sources exhausted'</span>);
|
||||
<span class="hljs-keyword">return</span>;
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> { url, label } = queue.<span class="hljs-title function_">shift</span>()!;
|
||||
onSourceChange?.(label);
|
||||
|
||||
hls?.<span class="hljs-title function_">destroy</span>();
|
||||
hls = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Hls</span>({ <span class="hljs-attr">maxBufferLength</span>: <span class="hljs-number">30</span>, <span class="hljs-attr">maxMaxBufferLength</span>: <span class="hljs-number">60</span> });
|
||||
|
||||
hls.<span class="hljs-title function_">on</span>(<span class="hljs-title class_">Hls</span>.<span class="hljs-property">Events</span>.<span class="hljs-property">ERROR</span>, <span class="hljs-function">(<span class="hljs-params">_, err</span>) =></span> {
|
||||
<span class="hljs-keyword">if</span> (err.<span class="hljs-property">fatal</span>) {
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">warn</span>(<span class="hljs-string">`Source "<span class="hljs-subst">${label}</span>" failed:`</span>, err.<span class="hljs-property">type</span>);
|
||||
<span class="hljs-title function_">tryNext</span>();
|
||||
}
|
||||
});
|
||||
|
||||
hls.<span class="hljs-title function_">loadSource</span>(url);
|
||||
hls.<span class="hljs-title function_">attachMedia</span>(videoEl);
|
||||
hls.<span class="hljs-title function_">on</span>(<span class="hljs-title class_">Hls</span>.<span class="hljs-property">Events</span>.<span class="hljs-property">MANIFEST_PARSED</span>, <span class="hljs-function">() =></span> videoEl.<span class="hljs-title function_">play</span>());
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(endpoint);
|
||||
<span class="hljs-keyword">const</span> reader = res.<span class="hljs-property">body</span>!.<span class="hljs-title function_">getReader</span>();
|
||||
<span class="hljs-keyword">const</span> decoder = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TextDecoder</span>();
|
||||
<span class="hljs-keyword">let</span> buffer = <span class="hljs-string">''</span>;
|
||||
|
||||
<span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
|
||||
<span class="hljs-keyword">const</span> { done, value } = <span class="hljs-keyword">await</span> reader.<span class="hljs-title function_">read</span>();
|
||||
<span class="hljs-keyword">if</span> (done) <span class="hljs-keyword">break</span>;
|
||||
|
||||
buffer += decoder.<span class="hljs-title function_">decode</span>(value, { <span class="hljs-attr">stream</span>: <span class="hljs-literal">true</span> });
|
||||
<span class="hljs-keyword">const</span> lines = buffer.<span class="hljs-title function_">split</span>(<span class="hljs-string">'\n'</span>);
|
||||
buffer = lines.<span class="hljs-title function_">pop</span>()!;
|
||||
|
||||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> line <span class="hljs-keyword">of</span> lines) {
|
||||
<span class="hljs-keyword">if</span> (!line.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'data: '</span>)) <span class="hljs-keyword">continue</span>;
|
||||
<span class="hljs-keyword">const</span> event = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(line.<span class="hljs-title function_">slice</span>(<span class="hljs-number">6</span>));
|
||||
|
||||
<span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> === <span class="hljs-string">'source'</span>) {
|
||||
queue.<span class="hljs-title function_">push</span>({ <span class="hljs-attr">url</span>: event.<span class="hljs-property">source</span>.<span class="hljs-property">url</span>, <span class="hljs-attr">label</span>: event.<span class="hljs-property">source</span>.<span class="hljs-property">label</span> });
|
||||
<span class="hljs-keyword">if</span> (!started) { started = <span class="hljs-literal">true</span>; <span class="hljs-title function_">tryNext</span>(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-function">() =></span> hls?.<span class="hljs-title function_">destroy</span>();
|
||||
}
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="network-timeouts">Network Timeouts</h2>
|
||||
<p>The SSE stream can stay open 20+ seconds while slow providers resolve. Set a client-side timeout using
|
||||
<code>AbortController</code>:
|
||||
</p>
|
||||
<pre><code class="hljs language-typescript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">fetchStreamWithTimeout</span>(<span class="hljs-params">
|
||||
<span class="hljs-attr">url</span>: <span class="hljs-built_in">string</span>,
|
||||
timeoutMs = <span class="hljs-number">20000</span>
|
||||
</span>): <span class="hljs-title class_">Promise</span><<span class="hljs-title class_">ReadableStreamDefaultReader</span><<span class="hljs-title class_">Uint8Array</span>>> {
|
||||
<span class="hljs-keyword">const</span> controller = <span class="hljs-keyword">new</span> <span class="hljs-title class_">AbortController</span>();
|
||||
<span class="hljs-keyword">const</span> timer = <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> controller.<span class="hljs-title function_">abort</span>(), timeoutMs);
|
||||
|
||||
<span class="hljs-keyword">try</span> {
|
||||
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(url, {
|
||||
<span class="hljs-attr">signal</span>: controller.<span class="hljs-property">signal</span>
|
||||
});
|
||||
<span class="hljs-built_in">clearTimeout</span>(timer);
|
||||
<span class="hljs-keyword">return</span> res.<span class="hljs-property">body</span>!.<span class="hljs-title function_">getReader</span>();
|
||||
} <span class="hljs-keyword">catch</span> (<span class="hljs-attr">err</span>: <span class="hljs-built_in">any</span>) {
|
||||
<span class="hljs-built_in">clearTimeout</span>(timer);
|
||||
<span class="hljs-keyword">if</span> (err.<span class="hljs-property">name</span> === <span class="hljs-string">'AbortError'</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Stream timed out'</span>);
|
||||
<span class="hljs-keyword">throw</span> err;
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="handling-missing-subtitles">Handling Missing Subtitles</h2>
|
||||
<p>Subtitles arrive in the <code>meta</code> event. Always guard against an empty array:</p>
|
||||
<pre><code class="hljs language-typescript"><span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> === <span class="hljs-string">'meta'</span>) {
|
||||
<span class="hljs-keyword">if</span> (!event.<span class="hljs-property">subtitles</span>.<span class="hljs-property">length</span>) {
|
||||
subtitleButton.<span class="hljs-property">style</span>.<span class="hljs-property">display</span> = <span class="hljs-string">'none'</span>;
|
||||
} <span class="hljs-keyword">else</span> {
|
||||
<span class="hljs-title function_">attachSubtitles</span>(videoEl, event.<span class="hljs-property">subtitles</span>);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="retry-logic-for-stream-failures">Retry Logic for Stream Failures</h2>
|
||||
<p>Add a top-level retry for complete stream failures (network errors, server 5xx before the stream opens):</p>
|
||||
<pre><code class="hljs language-typescript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">fetchStreamWithRetry</span>(<span class="hljs-params">
|
||||
<span class="hljs-attr">url</span>: <span class="hljs-built_in">string</span>,
|
||||
attempts = <span class="hljs-number">3</span>,
|
||||
delayMs = <span class="hljs-number">2000</span>
|
||||
</span>): <span class="hljs-title class_">Promise</span><<span class="hljs-title class_">Response</span>> {
|
||||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < attempts; i++) {
|
||||
<span class="hljs-keyword">try</span> {
|
||||
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(url);
|
||||
<span class="hljs-keyword">if</span> (res.<span class="hljs-property">ok</span>) <span class="hljs-keyword">return</span> res;
|
||||
<span class="hljs-keyword">if</span> (i === attempts - <span class="hljs-number">1</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">`HTTP <span class="hljs-subst">${res.status}</span>`</span>);
|
||||
} <span class="hljs-keyword">catch</span> (err) {
|
||||
<span class="hljs-keyword">if</span> (i === attempts - <span class="hljs-number">1</span>) <span class="hljs-keyword">throw</span> err;
|
||||
<span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Promise</span>(<span class="hljs-function"><span class="hljs-params">r</span> =></span> <span class="hljs-built_in">setTimeout</span>(r, delayMs * (i + <span class="hljs-number">1</span>)));
|
||||
}
|
||||
}
|
||||
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">'Max retries exceeded'</span>);
|
||||
}
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="checking-health-before-requests">Checking Health Before Requests</h2>
|
||||
<p>If you're building a dashboard, check provider health before opening a stream to give users an early warning:
|
||||
</p>
|
||||
<pre><code class="hljs language-typescript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">checkAndStream</span>(<span class="hljs-params"><span class="hljs-attr">tmdbId</span>: <span class="hljs-built_in">number</span></span>) {
|
||||
<span class="hljs-keyword">const</span> health = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(
|
||||
<span class="hljs-string">'http://localhost:7860/api/health'</span>
|
||||
).<span class="hljs-title function_">then</span>(<span class="hljs-function"><span class="hljs-params">r</span> =></span> r.<span class="hljs-title function_">json</span>());
|
||||
|
||||
<span class="hljs-keyword">if</span> (health.<span class="hljs-property">status</span> === <span class="hljs-string">'degraded'</span>) {
|
||||
<span class="hljs-keyword">const</span> down = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(health.<span class="hljs-property">sources</span>)
|
||||
.<span class="hljs-title function_">filter</span>(<span class="hljs-function">(<span class="hljs-params">[, v]: <span class="hljs-built_in">any</span></span>) =></span> !v.<span class="hljs-property">ok</span>)
|
||||
.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">[k]</span>) =></span> k);
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">warn</span>(<span class="hljs-string">`Degraded providers: <span class="hljs-subst">${down.length}</span>`</span>);
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">return</span> <span class="hljs-title function_">fetch</span>(
|
||||
<span class="hljs-string">`http://localhost:7860/movie?id=<span class="hljs-subst">${tmdbId}</span>`</span>
|
||||
);
|
||||
}
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="full-error-boundary-react">Full Error Boundary (React)</h2>
|
||||
<pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { useState, useCallback, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
|
||||
<span class="hljs-keyword">import</span> <span class="hljs-title class_">Hls</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'hls.js'</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-variable constant_">BASE</span> = <span class="hljs-string">'http://localhost:7860'</span>;
|
||||
|
||||
<span class="hljs-keyword">type</span> <span class="hljs-title class_">PlayerState</span> =
|
||||
| { <span class="hljs-attr">status</span>: <span class="hljs-string">'idle'</span> }
|
||||
| { <span class="hljs-attr">status</span>: <span class="hljs-string">'loading'</span> }
|
||||
| { <span class="hljs-attr">status</span>: <span class="hljs-string">'playing'</span>; <span class="hljs-attr">sourceLabel</span>: <span class="hljs-built_in">string</span> }
|
||||
| { <span class="hljs-attr">status</span>: <span class="hljs-string">'error'</span>; <span class="hljs-attr">message</span>: <span class="hljs-built_in">string</span> };
|
||||
|
||||
<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">useStreamPlayer</span>(<span class="hljs-params"><span class="hljs-attr">tmdbId</span>: <span class="hljs-built_in">number</span></span>) {
|
||||
<span class="hljs-keyword">const</span> [state, setState] = useState<<span class="hljs-title class_">PlayerState</span>>({ <span class="hljs-attr">status</span>: <span class="hljs-string">'idle'</span> });
|
||||
<span class="hljs-keyword">const</span> hlsRef = useRef<<span class="hljs-title class_">Hls</span> | <span class="hljs-literal">null</span>>(<span class="hljs-literal">null</span>);
|
||||
|
||||
<span class="hljs-keyword">const</span> load = <span class="hljs-title function_">useCallback</span>(<span class="hljs-title function_">async</span> (<span class="hljs-attr">videoEl</span>: <span class="hljs-title class_">HTMLVideoElement</span>) => {
|
||||
<span class="hljs-title function_">setState</span>({ <span class="hljs-attr">status</span>: <span class="hljs-string">'loading'</span> });
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-attr">queue</span>: { <span class="hljs-attr">url</span>: <span class="hljs-built_in">string</span>; <span class="hljs-attr">label</span>: <span class="hljs-built_in">string</span> }[] = [];
|
||||
<span class="hljs-keyword">let</span> started = <span class="hljs-literal">false</span>;
|
||||
|
||||
<span class="hljs-keyword">function</span> <span class="hljs-title function_">tryNext</span>(<span class="hljs-params"></span>) {
|
||||
<span class="hljs-keyword">if</span> (!queue.<span class="hljs-property">length</span>) {
|
||||
<span class="hljs-title function_">setState</span>({ <span class="hljs-attr">status</span>: <span class="hljs-string">'error'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'All sources failed.'</span> });
|
||||
<span class="hljs-keyword">return</span>;
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">const</span> { url, label } = queue.<span class="hljs-title function_">shift</span>()!;
|
||||
hlsRef.<span class="hljs-property">current</span>?.<span class="hljs-title function_">destroy</span>();
|
||||
|
||||
<span class="hljs-keyword">const</span> hls = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Hls</span>();
|
||||
hlsRef.<span class="hljs-property">current</span> = hls;
|
||||
|
||||
hls.<span class="hljs-title function_">on</span>(<span class="hljs-title class_">Hls</span>.<span class="hljs-property">Events</span>.<span class="hljs-property">MANIFEST_PARSED</span>, <span class="hljs-function">() =></span> {
|
||||
<span class="hljs-title function_">setState</span>({ <span class="hljs-attr">status</span>: <span class="hljs-string">'playing'</span>, <span class="hljs-attr">sourceLabel</span>: label });
|
||||
videoEl.<span class="hljs-title function_">play</span>();
|
||||
});
|
||||
hls.<span class="hljs-title function_">on</span>(<span class="hljs-title class_">Hls</span>.<span class="hljs-property">Events</span>.<span class="hljs-property">ERROR</span>, <span class="hljs-function">(<span class="hljs-params">_, err</span>) =></span> {
|
||||
<span class="hljs-keyword">if</span> (err.<span class="hljs-property">fatal</span>) { hls.<span class="hljs-title function_">destroy</span>(); <span class="hljs-title function_">tryNext</span>(); }
|
||||
});
|
||||
|
||||
hls.<span class="hljs-title function_">loadSource</span>(url);
|
||||
hls.<span class="hljs-title function_">attachMedia</span>(videoEl);
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">try</span> {
|
||||
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-string">`<span class="hljs-subst">${BASE}</span>/movie?id=<span class="hljs-subst">${tmdbId}</span>`</span>);
|
||||
<span class="hljs-keyword">const</span> reader = res.<span class="hljs-property">body</span>!.<span class="hljs-title function_">getReader</span>();
|
||||
<span class="hljs-keyword">const</span> decoder = <span class="hljs-keyword">new</span> <span class="hljs-title class_">TextDecoder</span>();
|
||||
<span class="hljs-keyword">let</span> buffer = <span class="hljs-string">''</span>;
|
||||
|
||||
<span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
|
||||
<span class="hljs-keyword">const</span> { done, value } = <span class="hljs-keyword">await</span> reader.<span class="hljs-title function_">read</span>();
|
||||
<span class="hljs-keyword">if</span> (done) <span class="hljs-keyword">break</span>;
|
||||
|
||||
buffer += decoder.<span class="hljs-title function_">decode</span>(value, { <span class="hljs-attr">stream</span>: <span class="hljs-literal">true</span> });
|
||||
<span class="hljs-keyword">const</span> lines = buffer.<span class="hljs-title function_">split</span>(<span class="hljs-string">'\n'</span>);
|
||||
buffer = lines.<span class="hljs-title function_">pop</span>()!;
|
||||
|
||||
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> line <span class="hljs-keyword">of</span> lines) {
|
||||
<span class="hljs-keyword">if</span> (!line.<span class="hljs-title function_">startsWith</span>(<span class="hljs-string">'data: '</span>)) <span class="hljs-keyword">continue</span>;
|
||||
<span class="hljs-keyword">const</span> event = <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">parse</span>(line.<span class="hljs-title function_">slice</span>(<span class="hljs-number">6</span>));
|
||||
|
||||
<span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> === <span class="hljs-string">'source'</span>) {
|
||||
queue.<span class="hljs-title function_">push</span>({ <span class="hljs-attr">url</span>: event.<span class="hljs-property">source</span>.<span class="hljs-property">url</span>, <span class="hljs-attr">label</span>: event.<span class="hljs-property">source</span>.<span class="hljs-property">label</span> });
|
||||
<span class="hljs-keyword">if</span> (!started) { started = <span class="hljs-literal">true</span>; <span class="hljs-title function_">tryNext</span>(); }
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">if</span> (event.<span class="hljs-property">type</span> === <span class="hljs-string">'done'</span> && !started) {
|
||||
<span class="hljs-title function_">setState</span>({ <span class="hljs-attr">status</span>: <span class="hljs-string">'error'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'No sources available for this title.'</span> });
|
||||
}
|
||||
}
|
||||
}
|
||||
} <span class="hljs-keyword">catch</span> {
|
||||
<span class="hljs-title function_">setState</span>({ <span class="hljs-attr">status</span>: <span class="hljs-string">'error'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Network error. Check your connection.'</span> });
|
||||
}
|
||||
}, [tmdbId]);
|
||||
|
||||
<span class="hljs-keyword">return</span> { state, load };
|
||||
}
|
||||
</code></pre>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#error-response-shape">Error Response Shape</a></li>
|
||||
<li><a href="#handling-no-sources">Handling No Sources</a></li>
|
||||
<li><a href="#source-fallback-queue">Source Fallback Queue</a></li>
|
||||
<li><a href="#network-timeouts">Network Timeouts</a></li>
|
||||
<li><a href="#handling-missing-subtitles">Handling Missing Subtitles</a></li>
|
||||
<li><a href="#retry-logic-for-stream-failures">Retry Logic for Stream Failures</a></li>
|
||||
<li><a href="#checking-health-before-requests">Checking Health Before Requests</a></li>
|
||||
<li><a href="#full-error-boundary-react">Full Error Boundary (React)</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1253
guides/integrations/index.html
Normal file
202
home/index.html
Normal file
@@ -0,0 +1,202 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Vyla Home — Vyla</title>
|
||||
<meta name="description" content="The desktop control center for running the Vyla media stack locally." />
|
||||
<meta name="keywords"
|
||||
content="Vyla Home, desktop media player, Windows, macOS, Linux, self-hosted media, local streaming" />
|
||||
<link rel="canonical" href="https://vyla.cc/home.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/home.html" />
|
||||
<meta property="og:title" content="Vyla Home — Vyla" />
|
||||
<meta property="og:description" content="The desktop control center for running the Vyla media stack locally." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/desktop-preview.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/home.html" />
|
||||
<meta property="twitter:title" content="Vyla Home — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="The desktop control center for running the Vyla media stack locally." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/desktop-preview.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Vyla Home",
|
||||
"description": "The desktop control center for running the Vyla media stack locally.",
|
||||
"url": "https://vyla.cc/home.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var saved = localStorage.getItem('vyla-theme');
|
||||
var isDark = saved ? saved === 'dark' : true;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control.
|
||||
Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
</li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Vyla Home</h1>
|
||||
<p class="page-description">The desktop control center for running the Vyla media stack locally.</p>
|
||||
<img class="vyla-banner" src="/assets/images/desktop/site_preview.png"
|
||||
alt="Vyla Home desktop application preview" loading="lazy" fetchpriority="low">
|
||||
<h2 id="vyla-home">Vyla Home</h2>
|
||||
<p><strong>Vyla Home</strong> 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.</p>
|
||||
<h2 id="downloads">Downloads</h2>
|
||||
<p>Vyla Home is available for Windows, macOS, and Linux. Get the latest version below:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Windows</td>
|
||||
<td><a href="https://gitlab.com/vyla-entertainment/home/-/releases">Download .exe</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>macOS</td>
|
||||
<td><a href="https://gitlab.com/vyla-entertainment/home/-/releases">Download .dmg</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux</td>
|
||||
<td><a href="#linux-setup">Build from source</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Inside the releases page, you can find the latest version of Vyla Home for each platform, find the
|
||||
<code>.exe</code> for windows, and <code>.dmg</code> for macOS.
|
||||
</p>
|
||||
<h2 id="linux-setup">Linux Setup</h2>
|
||||
<p>For Linux, you'll need to build Vyla Home from source. Follow these steps to install on Fedora:</p>
|
||||
<p><strong>Step 1: Install dependencies</strong></p>
|
||||
<pre><code class="hljs language-bash"><span class="hljs-built_in">sudo</span> dnf install webkit2gtk4.1-devel curl wget openssl-devel libappindicator-gtk3-devel librsvg2-devel
|
||||
<span class="hljs-built_in">sudo</span> dnf install gcc gcc-c++ make automake autoconf
|
||||
</code></pre>
|
||||
<p><strong>Step 2: Install Node.js and NPM</strong></p>
|
||||
<pre><code class="hljs language-bash"><span class="hljs-built_in">sudo</span> dnf install nodejs npm
|
||||
</code></pre>
|
||||
<p><strong>Step 3: Install Rust</strong></p>
|
||||
<pre><code class="hljs language-bash">curl --proto <span class="hljs-string">'=https'</span> --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
<span class="hljs-built_in">source</span> <span class="hljs-string">"<span class="hljs-variable">$HOME</span>/.cargo/env"</span>
|
||||
</code></pre>
|
||||
<p><strong>Step 4: Clone the Git repository</strong></p>
|
||||
<pre><code class="hljs language-bash">git <span class="hljs-built_in">clone</span> https://gitlab.com/vyla-entertainment/home.git vyla-home
|
||||
<span class="hljs-built_in">cd</span> vyla-home
|
||||
</code></pre>
|
||||
<p><strong>Step 5: Install more dependencies</strong></p>
|
||||
<pre><code class="hljs language-bash">npm install
|
||||
</code></pre>
|
||||
<p><strong>Step 6: Build the Fedora package (.rpm)</strong></p>
|
||||
<pre><code class="hljs language-bash">cargo update --manifest-path src-tauri/Cargo.toml
|
||||
npx tauri build --bundles rpm,appimage
|
||||
</code></pre>
|
||||
<p><strong>Step 7: Final installation</strong></p>
|
||||
<pre><code class="hljs language-bash"><span class="hljs-built_in">sudo</span> dnf install src-tauri/target/release/bundle/rpm/*.rpm
|
||||
</code></pre>
|
||||
<p><em>Thank you @krishi279 for supplying the Linux setup instructions on Discord ❤️.</em></p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#vyla-home">Vyla Home</a></li>
|
||||
<li><a href="#downloads">Downloads</a></li>
|
||||
<li><a href="#linux-setup">Linux Setup</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
241
index.html
Normal file
@@ -0,0 +1,241 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Vyla Entertainment — Vyla</title>
|
||||
<meta name="description"
|
||||
content="A community-built media project focused on freedom, privacy, and giving people control over their own experience." />
|
||||
<meta name="keywords"
|
||||
content="Vyla, media streaming, self-hosted, privacy, open source, media player, streaming API, SDK, Android TV, desktop media" />
|
||||
<link rel="canonical" href="https://vyla.cc/" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/" />
|
||||
<meta property="og:title" content="Vyla Entertainment — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="A community-built media project focused on freedom, privacy, and giving people control over their own experience." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/" />
|
||||
<meta property="twitter:title" content="Vyla Entertainment — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="A community-built media project focused on freedom, privacy, and giving people control over their own experience." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Vyla",
|
||||
"description": "A community-built media project focused on freedom, privacy, and giving people control over their own experience.",
|
||||
"url": "https://vyla.cc/",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var saved = localStorage.getItem('vyla-theme');
|
||||
var isDark = saved ? saved === 'dark' : true;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control.
|
||||
Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Vyla Entertainment</h1>
|
||||
<p class="page-description">A community-built media project focused on freedom, privacy, and giving
|
||||
people
|
||||
control over their own experience.</p>
|
||||
<img class="vyla-banner" src="/assets/images/devices.png"
|
||||
alt="A collage of films and shows available through a Vyla experience" loading="lazy"
|
||||
fetchpriority="low">
|
||||
<h2 id="about-vyla">About Vyla</h2>
|
||||
<p>Vyla is a community-built media project created around a simple idea: <strong>people should have
|
||||
control over
|
||||
how they experience their media.</strong></p>
|
||||
<p>We started Vyla because we wanted something clean, private, and easy to use without relying on a
|
||||
single
|
||||
company or centralized service. What began as a small project has grown into a collection of
|
||||
applications and
|
||||
tools for desktop, Android, Android TV, and more.</p>
|
||||
<p>Vyla is not a streaming service, and we do not host or provide the media itself. Instead, we build
|
||||
the
|
||||
software that brings the experience together while leaving control in the hands of the person using
|
||||
it.</p>
|
||||
<h2 id="where-we-come-from">Where we come from</h2>
|
||||
<p>Vyla started as a small web project focused on creating a better way to discover and watch movies and
|
||||
shows.
|
||||
Over time, it grew into a broader project with applications for different platforms alongside the
|
||||
tools that
|
||||
power them.</p>
|
||||
<p>We care about keeping Vyla open, accessible, and community-driven. Our project has also been shared
|
||||
by
|
||||
communities such as <strong>FMHY</strong> and <strong>r/Piracy</strong>, where people interested in
|
||||
alternative and self-hosted media solutions have discovered Vyla.</p>
|
||||
<p>We are proud to be part of a wider community of people who build, share, and explore alternatives to
|
||||
traditional media platforms.</p>
|
||||
<h2 id="what-we-make">What we make</h2>
|
||||
<p>Vyla is made up of several projects designed for different ways of using the platform.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Description</th>
|
||||
<th>Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Vyla Home</td>
|
||||
<td>Desktop app</td>
|
||||
<td><a href="/home">Learn more</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vyla Go</td>
|
||||
<td>Android app</td>
|
||||
<td><a href="/go">Learn more</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vyla TV</td>
|
||||
<td>Android TV app</td>
|
||||
<td><a href="/tv">Learn more</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vyla SDK</td>
|
||||
<td>Developer SDK</td>
|
||||
<td><a href="/sdk">Learn more</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stream API</td>
|
||||
<td>Self-hosted API</td>
|
||||
<td><a href="/stream-api/overview">Learn more</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 id="made-for-people-not-platforms">Made for people, not platforms</h2>
|
||||
<p>We believe software should give people more control, not less.</p>
|
||||
<p>That means keeping Vyla local and self-hostable, avoiding unnecessary centralized dependencies, and
|
||||
allowing
|
||||
developers and users to decide how they want to use the project.</p>
|
||||
<p>Vyla can be as simple as a TV or mobile app, a desktop application, or the foundation for a
|
||||
completely custom
|
||||
media experience. <strong>You decide how much of Vyla you use and where it runs.</strong></p>
|
||||
<h2 id="start-exploring">Start exploring</h2>
|
||||
<ul>
|
||||
<li><strong>On desktop?</strong> See <a href="/home">Vyla Home</a>.</li>
|
||||
<li><strong>On Android?</strong> See <a href="/go">Vyla Go</a>.</li>
|
||||
<li><strong>Using Android TV?</strong> See <a href="/tv">Vyla TV</a>.</li>
|
||||
<li><strong>Building something with Vyla?</strong> Start with the <a href="/sdk-quickstart">SDK
|
||||
quickstart</a>.</li>
|
||||
<li><strong>Building your own client?</strong> See the <a href="/stream-api/overview">Stream
|
||||
API</a>.
|
||||
</li>
|
||||
<li><strong>Want to understand how everything fits together?</strong> Visit <a
|
||||
href="/architecture">Our
|
||||
Architecture</a>.</li>
|
||||
</ul>
|
||||
<div class="callout callout-info">
|
||||
<div class="callout-content">
|
||||
<p>Vyla is self-hosted software and does not host or distribute media. Please respect applicable
|
||||
laws,
|
||||
upstream terms, and rights-holder requests. See the <a href="/misc/dmca">DMCA policy</a> for
|
||||
removal
|
||||
information.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#about-vyla">About Vyla</a></li>
|
||||
<li><a href="#where-we-come-from">Where we come from</a></li>
|
||||
<li><a href="#what-we-make">What we make</a></li>
|
||||
<li><a href="#made-for-people-not-platforms">Made for people, not platforms</a></li>
|
||||
<li><a href="#start-exploring">Start exploring</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
77
llms.txt
Normal file
@@ -0,0 +1,77 @@
|
||||
# Vyla Documentation
|
||||
|
||||
Vyla is a community-built media project focused on freedom, privacy, and giving people control over their own experience.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Vyla is not a streaming service and does not host or provide media. Instead, Vyla builds software that brings the media experience together while leaving control in the hands of the person using it. The project consists of:
|
||||
|
||||
- **Vyla Home**: Desktop application for Windows, macOS, and Linux
|
||||
- **Vyla Go**: Android mobile application
|
||||
- **Vyla TV**: Android TV application
|
||||
- **Vyla SDK**: Node.js/TypeScript SDK for developers
|
||||
- **Stream API**: Self-hosted HTTP/SSE API for browser and mobile clients
|
||||
|
||||
## Key Features
|
||||
|
||||
- Self-hosted and privacy-focused
|
||||
- Open source community project
|
||||
- Provider-based streaming resolution
|
||||
- Multi-platform support (desktop, Android, Android TV)
|
||||
- Developer-friendly SDK and API
|
||||
- Support for HLS and MP4 streaming
|
||||
- Built-in health monitoring and fallback systems
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
- `/` - Introduction to Vyla
|
||||
- `/architecture` - System architecture and component relationships
|
||||
- `/home` - Vyla Home desktop application
|
||||
- `/go` - Vyla Go Android application
|
||||
- `/tv` - Vyla TV Android TV application
|
||||
- `/player` - Building custom media players
|
||||
- `/sdk` - SDK reference documentation
|
||||
- `/sdk-quickstart` - Quick start guide for SDK
|
||||
- `/stream-api/overview` - Stream API overview
|
||||
- `/stream-api/paths` - API paths and SSE events
|
||||
- `/stream-api/media-data` - Subtitles and downloads API
|
||||
- `/stream-api/operations` - Health, testing, and operations
|
||||
- `/self-hosting` - Self-hosting deployment guide
|
||||
- `/concepts/providers` - Provider configuration and verification
|
||||
- `/guides/integrations` - Client integration guide
|
||||
- `/guides/error-handling` - Error handling and fallbacks
|
||||
- `/misc/credit` - Credits and acknowledgments
|
||||
- `/misc/dmca` - DMCA and copyright policy
|
||||
|
||||
## Technical Stack
|
||||
|
||||
- Node.js 18+ for SDK and Stream API
|
||||
- TypeScript for SDK
|
||||
- Server-Sent Events (SSE) for streaming
|
||||
- HLS.js for web playback
|
||||
- TMDB API for metadata
|
||||
- Jetpack Compose for Android TV
|
||||
|
||||
## Integration Options
|
||||
|
||||
1. **Direct SDK**: For Node.js applications - embed provider resolution directly
|
||||
2. **Stream API**: For browser/mobile clients - HTTP/SSE interface
|
||||
3. **Vyla Home**: Managed desktop experience with bundled services
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Vyla is self-hosted only - no public hosted instance
|
||||
- Requires TMDB API key for metadata-dependent sources
|
||||
- Provider availability varies by network environment
|
||||
- Supports fallback strategies for resilient playback
|
||||
- CORS-enabled by default
|
||||
- No database required for Stream API
|
||||
|
||||
## Community
|
||||
|
||||
- GitLab: https://gitlab.com/vyla-entertainment/
|
||||
- Discord: https://discord.gg/5eqRhKeywG
|
||||
|
||||
## Legal
|
||||
|
||||
Vyla does not host or distribute media. Rights holders can request source removal via the DMCA policy. The project is intended for personal, educational, and development use. Users are responsible for complying with applicable laws.
|
||||
BIN
logo/light.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
167
misc/credit/index.html
Normal file
@@ -0,0 +1,167 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Credit — Vyla</title>
|
||||
<meta name="description" content="Credits and inspiration behind the Vyla API ecosystem." />
|
||||
<meta name="keywords"
|
||||
content="credits, attribution, open source, inspiration, P-Stream, CinePro, Movieslay, acknowledgments" />
|
||||
<link rel="canonical" href="https://vyla.cc/misc/credit.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/misc/credit.html" />
|
||||
<meta property="og:title" content="Credit — Vyla" />
|
||||
<meta property="og:description" content="Credits and inspiration behind the Vyla API ecosystem." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/misc/credit.html" />
|
||||
<meta property="twitter:title" content="Credit — Vyla" />
|
||||
<meta property="twitter:description" content="Credits and inspiration behind the Vyla API ecosystem." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Credit",
|
||||
"description": "Credits and inspiration behind the Vyla API ecosystem.",
|
||||
"url": "https://vyla.cc/misc/credit.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Project</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/misc/credit">Credit</a></li>
|
||||
<li><a class="" href="/misc/dmca">Dmca</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Credit</h1>
|
||||
<p class="page-description">Credits and inspiration behind the Vyla API ecosystem.</p>
|
||||
<p>This page highlights the open-source projects and developers that heavily inspired the Vyla API, player
|
||||
ecosystem, and overall UI direction.</p>
|
||||
<hr>
|
||||
<h2 id="player--ui-inspiration">Player & UI Inspiration</h2>
|
||||
<p>The overall player experience, streaming layout, and much of the UI direction for Vyla was heavily inspired
|
||||
by P-Stream.</p>
|
||||
<ul>
|
||||
<li>Repository: <a href="https://github.com/p-stream">https://github.com/p-stream</a></li>
|
||||
<li>Owner: <a href="https://github.com/Pasithea0">https://github.com/Pasithea0</a></li>
|
||||
</ul>
|
||||
<p>Their work served as a major reference point for the design language and viewing experience used throughout
|
||||
Vyla.</p>
|
||||
<hr>
|
||||
<h2 id="stream-api-inspiration">Stream API Inspiration</h2>
|
||||
<p>The architecture and implementation ideas behind the Vyla streaming API were heavily inspired by CinePro.</p>
|
||||
<h3 id="resources">Resources</h3>
|
||||
<ul>
|
||||
<li>Organization: <a href="https://github.com/cinepro-org/">https://github.com/cinepro-org/</a></li>
|
||||
<li>Main Website: <a href="https://cinepro.cc/">https://cinepro.cc/</a></li>
|
||||
<li>Documentation: <a href="https://docs.cinepro.cc/">https://docs.cinepro.cc/</a>
|
||||
</li>
|
||||
<li>UI Components: <a href="https://ui.cinepro.cc/">https://ui.cinepro.cc/</a></li>
|
||||
<li>Owner: <a href="https://github.com/An0n-00">https://github.com/An0n-00</a></li>
|
||||
</ul>
|
||||
<p>CinePro's ecosystem played a large role in shaping how Vyla handles providers, source structures, streaming
|
||||
workflows, and API organization.</p>
|
||||
<hr>
|
||||
<h2 id="source-implementation--fixes">Source Implementation & Fixes</h2>
|
||||
<p>The owner of Movieslay helped fix and implement new sources across the Vyla API.</p>
|
||||
<ul>
|
||||
<li>Repository: <a
|
||||
href="https://gitlab.com/gavingogaming/movieslay-emerald-temp">gitlab.com/gavingogaming/movieslay-emerald-temp</a>
|
||||
</li>
|
||||
<li>Discord: <a href="https://discord.gg/rD75qxGBVG">discord.gg/rD75qxGBVG</a></li>
|
||||
<li>Owner: <a href="https://gitlab.com/gavingogaming">gitlab.com/gavingogaming</a></li>
|
||||
</ul>
|
||||
<p>Movieslay Emerald is a successor to the original Movieslay webapp. gavingogaming's direct contributions were
|
||||
instrumental in expanding and stabilizing Vyla's source coverage.</p>
|
||||
<hr>
|
||||
<h2 id="open-source-appreciation">Open Source Appreciation</h2>
|
||||
<p>Vyla would not exist in its current form without the incredible open-source work created by these developers
|
||||
and communities.</p>
|
||||
<p>Massive thanks to:</p>
|
||||
<ul>
|
||||
<li>Pasithea0</li>
|
||||
<li>An0n-00</li>
|
||||
<li>gavingogaming (Movieslay)</li>
|
||||
<li>The P-Stream contributors</li>
|
||||
<li>The CinePro contributors</li>
|
||||
<li>The Movieslay contributors</li>
|
||||
</ul>
|
||||
<p>for building and sharing their projects publicly.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#player--ui-inspiration">Player & UI Inspiration</a></li>
|
||||
<li><a href="#stream-api-inspiration">Stream API Inspiration</a></li>
|
||||
<li><a href="#source-implementation--fixes">Source Implementation & Fixes</a></li>
|
||||
<li><a href="#open-source-appreciation">Open Source Appreciation</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
182
misc/dmca/index.html
Normal file
@@ -0,0 +1,182 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>DMCA & Copyright — Vyla</title>
|
||||
<meta name="description" content="Content sources used by Vyla API and how rights holders can request removal." />
|
||||
<meta name="keywords"
|
||||
content="DMCA, copyright, takedown, legal, content sources, rights holders, copyright policy, takedown request" />
|
||||
<link rel="canonical" href="https://vyla.cc/misc/dmca.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/misc/dmca.html" />
|
||||
<meta property="og:title" content="DMCA & Copyright — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="Content sources used by Vyla API and how rights holders can request removal." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/misc/dmca.html" />
|
||||
<meta property="twitter:title" content="DMCA & Copyright — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Content sources used by Vyla API and how rights holders can request removal." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "DMCA & Copyright",
|
||||
"description": "Content sources used by Vyla API and how rights holders can request removal.",
|
||||
"url": "https://vyla.cc/misc/dmca.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Project</p>
|
||||
<ul>
|
||||
<li><a class="" href="/misc/credit">Credit</a></li>
|
||||
<li><a class="active" href="/misc/dmca">Dmca</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>DMCA & Copyright</h1>
|
||||
<p class="page-description">Content sources used by Vyla API and how rights holders can request removal.</p>
|
||||
<p>Vyla API is a self-hosted streaming proxy that aggregates publicly accessible stream URLs from third-party
|
||||
providers. Vyla does not host, store, upload, or distribute any video content. All media is served directly
|
||||
from its original source.</p>
|
||||
<hr>
|
||||
<h2 id="how-it-works">How It Works</h2>
|
||||
<p>Vyla acts as a <strong>reverse proxy and aggregator</strong>. When a stream is requested:</p>
|
||||
<ol>
|
||||
<li>Vyla queries third-party providers for a stream URL.</li>
|
||||
<li>That URL is proxied through the Vyla server to handle CORS headers.</li>
|
||||
<li>The underlying video segments are fetched from their original CDN and passed through to the client.</li>
|
||||
</ol>
|
||||
<p>At no point does Vyla store, transcode, or re-host any media file.</p>
|
||||
<hr>
|
||||
<h2 id="content-sources">Content Sources</h2>
|
||||
<p>The following third-party sources are currently scraped or proxied by Vyla API. These are the upstream
|
||||
providers — Vyla does not control, operate, or own any of them.</p>
|
||||
<p>You can find the full list of sources in the <a
|
||||
href="https://gitlab.com/vyla-entertainment/sdk/-/tree/main/src/sources">stream-api sources directory</a>.
|
||||
</p>
|
||||
<div class="callout callout-note">
|
||||
<div class="callout-content">
|
||||
<p>This list reflects active sources. These sources are active or in the works.</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="dmca--takedown-requests">DMCA / Takedown Requests</h2>
|
||||
<p>If you are a rights holder and believe that content accessible through Vyla API infringes your copyright, you
|
||||
may submit a takedown request. Because Vyla does not host the content itself, the most effective path is to
|
||||
contact the upstream source directly.</p>
|
||||
<p>However, Vyla can disable specific providers or sources upon valid request.</p>
|
||||
<p><strong>To submit a takedown request, contact:</strong></p>
|
||||
<div class="card">
|
||||
<div class="card-title">DMCA Contact</div>
|
||||
<div class="card-body">
|
||||
<p>Open an issue on the GitLab repository or reach out via Discord or email.</p>
|
||||
<ul>
|
||||
<li><strong>GitLab Issues:</strong> <a
|
||||
href="https://gitlab.com/vyla-entertainment/stream-api/issues">https://gitlab.com/vyla-entertainment/stream-api/issues</a>
|
||||
</li>
|
||||
<li><strong>Discord:</strong> <a href="https://discord.gg/5eqRhKeywG">https://discord.gg/5eqRhKeywG</a>
|
||||
</li>
|
||||
<li><strong>Email:</strong> <a
|
||||
href="mailto:alwaysmesmerizingyou@gmail.com">alwaysmesmerizingyou@gmail.com</a></li>
|
||||
</ul>
|
||||
<p>Please include the following in your request:</p>
|
||||
<ul>
|
||||
<li>Your name and organization</li>
|
||||
<li>The specific content or provider you are requesting removal of</li>
|
||||
<li>Evidence of your rights to the content</li>
|
||||
<li>Your contact information</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="good-faith-compliance">Good Faith Compliance</h2>
|
||||
<p>Vyla API is operated in good faith. Upon receiving a valid and complete takedown request, the relevant source
|
||||
will be reviewed and disabled within a reasonable timeframe if the claim is substantiated.</p>
|
||||
<p>Rights holders such as Sony Pictures, Universal, Warner Bros., Disney, Netflix, or any other studio or
|
||||
distributor are welcome to contact via the channels above. We will respond promptly.</p>
|
||||
<hr>
|
||||
<h2 id="disclaimer">Disclaimer</h2>
|
||||
<p>Vyla API is intended for personal, educational, and development use only. The operator of this API does not
|
||||
endorse or encourage copyright infringement. Users of this API are responsible for complying with the laws of
|
||||
their jurisdiction.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#how-it-works">How It Works</a></li>
|
||||
<li><a href="#content-sources">Content Sources</a></li>
|
||||
<li><a href="#dmca--takedown-requests">DMCA / Takedown Requests</a></li>
|
||||
<li><a href="#good-faith-compliance">Good Faith Compliance</a></li>
|
||||
<li><a href="#disclaimer">Disclaimer</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
2021
package-lock.json
generated
Normal file
30
package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "vyla-docs",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node build.mjs",
|
||||
"serve": "cd dist && python3 -m http.server 8080"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"gray-matter": "^4.0.3",
|
||||
"hast-util-to-html": "^9.0.5",
|
||||
"hastscript": "^9.0.1",
|
||||
"highlight.js": "^11.12.0",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-mdx": "^3.1.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0"
|
||||
}
|
||||
}
|
||||
184
player/index.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Build a player — Vyla</title>
|
||||
<meta name="description"
|
||||
content="Design a resilient Vyla playback experience around your own product and infrastructure." />
|
||||
<meta name="keywords"
|
||||
content="Vyla player, media player, custom player, playback experience, HLS player, MP4 player, streaming player" />
|
||||
<link rel="canonical" href="https://vyla.cc/player.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/player.html" />
|
||||
<meta property="og:title" content="Build a player — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="Design a resilient Vyla playback experience around your own product and infrastructure." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/player.html" />
|
||||
<meta property="twitter:title" content="Build a player — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Design a resilient Vyla playback experience around your own product and infrastructure." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Build a player",
|
||||
"description": "Design a resilient Vyla playback experience around your own product and infrastructure.",
|
||||
"url": "https://vyla.cc/player.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
</li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="" href="/tv">Tv</a></li>
|
||||
<li><a class="active" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Build a player</h1>
|
||||
<p class="page-description">Design a resilient Vyla playback experience around your own product and
|
||||
infrastructure.</p>
|
||||
<h2 id="your-player-your-rules">Your player, your rules</h2>
|
||||
<p>Vyla supplies the integration layer; your application owns the player experience. That includes design,
|
||||
authentication, title discovery, parental controls, analytics, playback policy, and fallback behavior.</p>
|
||||
<p>For a local managed experience, Vyla Home starts its bundled player service. For a custom product, choose one
|
||||
of two boundaries:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Integration</th>
|
||||
<th>Best for</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>SDK directly</strong></td>
|
||||
<td>Node.js products that already have a backend and want complete control</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Stream API</strong></td>
|
||||
<td>browser or mobile clients that need an HTTP/SSE source feed</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 id="build-for-changing-conditions">Build for changing conditions</h2>
|
||||
<p>Provider results vary by title and network. A dependable player should:</p>
|
||||
<ol>
|
||||
<li>Begin playback as soon as it has a usable candidate.</li>
|
||||
<li>Keep later candidates as fallbacks.</li>
|
||||
<li>Keep subtitles independent from first playback.</li>
|
||||
<li>Report a helpful unavailable state when no candidates resolve.</li>
|
||||
<li>Destroy playback and abort in-flight work when the viewer changes titles.</li>
|
||||
</ol>
|
||||
<p>The SDK returns provider results directly; Stream API emits <code>meta</code>, <code>source</code>, and
|
||||
<code>done</code> events. Both models benefit from the same fallback strategy.
|
||||
</p>
|
||||
<h2 id="playback-formats">Playback formats</h2>
|
||||
<p>Candidates may be HLS manifests or direct media files. Let the result metadata and your player capabilities
|
||||
guide how a candidate is loaded; do not assume every URL has the same format or availability. HLS-capable web
|
||||
players normally use HLS.js where native HLS is unavailable, while direct media can be assigned to a video
|
||||
element.</p>
|
||||
<h2 id="continue-building">Continue building</h2>
|
||||
<ul>
|
||||
<li>Embed provider resolution with <a href="/sdk-quickstart">SDK quickstart</a>.</li>
|
||||
<li>Learn the optional HTTP event contract in <a href="/stream-api/paths">Paths and playback events</a>.
|
||||
</li>
|
||||
<li>Use the <a href="/guides/integrations">client integration guide</a> for a browser-oriented example.
|
||||
</li>
|
||||
<li>Review <a href="/guides/error-handling">error handling</a> before shipping a player.</li>
|
||||
</ul>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#your-player-your-rules">Your player, your rules</a></li>
|
||||
<li><a href="#build-for-changing-conditions">Build for changing conditions</a></li>
|
||||
<li><a href="#playback-formats">Playback formats</a></li>
|
||||
<li><a href="#continue-building">Continue building</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
5
robots.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /assets/
|
||||
|
||||
Sitemap: https://vyla.cc/sitemap.xml
|
||||
194
sdk-quickstart/index.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>SDK quickstart — Vyla</title>
|
||||
<meta name="description" content="Install Vyla SDK, resolve a source, and build a safe server-side integration." />
|
||||
<meta name="keywords"
|
||||
content="Vyla SDK, SDK quickstart, Node.js SDK, server-side integration, stream resolution, API integration" />
|
||||
<link rel="canonical" href="https://vyla.cc/sdk-quickstart.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/sdk-quickstart.html" />
|
||||
<meta property="og:title" content="SDK quickstart — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="Install Vyla SDK, resolve a source, and build a safe server-side integration." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/sdk-quickstart.html" />
|
||||
<meta property="twitter:title" content="SDK quickstart — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Install Vyla SDK, resolve a source, and build a safe server-side integration." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "SDK quickstart",
|
||||
"description": "Install Vyla SDK, resolve a source, and build a safe server-side integration.",
|
||||
"url": "https://vyla.cc/sdk-quickstart.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Vyla SDK</p>
|
||||
<ul>
|
||||
<li><a class="" href="/sdk">Sdk</a></li>
|
||||
<li><a class="active" href="/sdk-quickstart">Sdk Quickstart</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>SDK quickstart</h1>
|
||||
<p class="page-description">Install Vyla SDK, resolve a source, and build a safe server-side integration.</p>
|
||||
<h2 id="before-you-start">Before you start</h2>
|
||||
<p>Vyla SDK is for <strong>server-side Node.js</strong>. It contains provider integrations and makes outbound
|
||||
requests from the process where you install it. Do not import it into a browser bundle or expose provider
|
||||
results, credentials, or internal source keys directly to untrusted clients.</p>
|
||||
<p>You need Node.js 18 or later and, for sources that resolve title metadata, a TMDB API key.</p>
|
||||
<h2 id="install-and-initialize">Install and initialize</h2>
|
||||
<div class="code-group">
|
||||
<div class="code-group-tabs"><button class="code-group-tab active" data-tab-index="0" type="button"
|
||||
onclick="cgSwitch(this)">npm</button><button class="code-group-tab" data-tab-index="1" type="button"
|
||||
onclick="cgSwitch(this)">pnpm</button><button class="code-group-tab" data-tab-index="2" type="button"
|
||||
onclick="cgSwitch(this)">yarn</button></div>
|
||||
<div class="code-group-panels">
|
||||
<div class="code-group-panel active">
|
||||
<pre><code class="hljs language-bash">npm <span class="hljs-built_in">install</span> <span class="hljs-string">@vyla-entertainment/sdk</span></code></pre>
|
||||
</div>
|
||||
<div class="code-group-panel">
|
||||
<pre><code class="hljs language-bash">pnpm <span class="hljs-built_in">install</span> <span class="hljs-string">@vyla-entertainment/sdk</span></code></pre>
|
||||
</div>
|
||||
<div class="code-group-panel">
|
||||
<pre><code class="hljs language-bash">yarn <span class="hljs-built_in">add</span> <span class="hljs-string">@vyla-entertainment/sdk</span></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre><code class="hljs language-js"><span class="hljs-keyword">import</span> <span class="hljs-title class_">VylaSDK</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"@vyla-entertainment/sdk"</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> vyla = <span class="hljs-keyword">new</span> <span class="hljs-title class_">VylaSDK</span>({
|
||||
<span class="hljs-attr">tmdbApiKey</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">TMDB_API_KEY</span>,
|
||||
<span class="hljs-attr">penguManifest</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">PENGU_MANIFEST</span>
|
||||
});
|
||||
</code></pre>
|
||||
<div class="callout callout-warning">
|
||||
<div class="callout-content">
|
||||
<p>Keep <code>TMDB_API_KEY</code> and any deployment configuration in server-side environment variables.
|
||||
Never commit them or send them to a client.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="resolve-a-movie">Resolve a movie</h2>
|
||||
<p>First inspect active providers. Provider availability changes over time, so select a key from the running
|
||||
configuration instead of hard-coding a placeholder.</p>
|
||||
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> providers = vyla.<span class="hljs-title function_">getSources</span>(<span class="hljs-literal">true</span>);
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">table</span>(providers.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">{ key, label }</span>) =></span> ({ key, label })));
|
||||
|
||||
<span class="hljs-keyword">const</span> provider = providers[<span class="hljs-number">0</span>];
|
||||
<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> vyla.<span class="hljs-title function_">getStream</span>(provider.<span class="hljs-property">key</span>, <span class="hljs-number">155</span>);
|
||||
<span class="hljs-keyword">const</span> candidates = result.<span class="hljs-property">allUrls</span> ?? [result];
|
||||
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(candidates.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">{ url, type, quality }</span>) =></span> ({ url, type, quality })));
|
||||
</code></pre>
|
||||
<p><code>155</code> is a TMDB movie ID. For an episode, include season and episode:</p>
|
||||
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> vyla.<span class="hljs-title function_">getStream</span>(provider.<span class="hljs-property">key</span>, <span class="hljs-number">1396</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
|
||||
</code></pre>
|
||||
<h2 id="make-it-resilient">Make it resilient</h2>
|
||||
<p>Provider results are inherently variable: a source can fail, time out, return more than one candidate, or be
|
||||
temporarily disabled. In production:</p>
|
||||
<ol>
|
||||
<li>Probe sources or maintain a recent availability signal before choosing one.</li>
|
||||
<li>Give every provider call a request deadline in your application.</li>
|
||||
<li>Treat <code>allUrls</code> as fallback candidates, not a guarantee that every URL will play.</li>
|
||||
<li>Return only the fields your client needs and validate requests at your own API boundary.</li>
|
||||
<li>Cache and rate-limit at your boundary to avoid repeatedly triggering upstream work.</li>
|
||||
</ol>
|
||||
<pre><code class="hljs language-js"><span class="hljs-keyword">const</span> health = <span class="hljs-keyword">await</span> vyla.<span class="hljs-title function_">probeAllSources</span>();
|
||||
<span class="hljs-keyword">const</span> usableKey = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(health).<span class="hljs-title function_">find</span>(<span class="hljs-function">(<span class="hljs-params">[, result]</span>) =></span> result.<span class="hljs-property">ok</span>)?.[<span class="hljs-number">0</span>];
|
||||
|
||||
<span class="hljs-keyword">if</span> (!usableKey) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">"No providers are currently available"</span>);
|
||||
|
||||
<span class="hljs-keyword">const</span> stream = <span class="hljs-keyword">await</span> vyla.<span class="hljs-title function_">getStream</span>(usableKey, <span class="hljs-number">155</span>);
|
||||
</code></pre>
|
||||
<h2 id="next-steps">Next steps</h2>
|
||||
<ul>
|
||||
<li>Read the complete <a href="/sdk">SDK reference</a> for methods and return shapes.</li>
|
||||
<li>Learn provider flags, timeout behavior, and source keys in <a href="/concepts/providers">Providers</a>.
|
||||
</li>
|
||||
<li>Choose <a href="/stream-api/overview">Stream API</a> only when an SSE HTTP interface better fits your
|
||||
client architecture.</li>
|
||||
</ul>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#before-you-start">Before you start</a></li>
|
||||
<li><a href="#install-and-initialize">Install and initialize</a></li>
|
||||
<li><a href="#resolve-a-movie">Resolve a movie</a></li>
|
||||
<li><a href="#make-it-resilient">Make it resilient</a></li>
|
||||
<li><a href="#next-steps">Next steps</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
588
sdk/index.html
Normal file
@@ -0,0 +1,588 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>SDK Reference — Vyla</title>
|
||||
<meta name="description"
|
||||
content="Scrape video streams, fetch subtitles, retrieve download links, and monitor provider health directly from your Node.js application." />
|
||||
<meta name="keywords"
|
||||
content="Vyla SDK, Node.js SDK, TypeScript SDK, video streaming, subtitle API, download API, health monitoring, server-side streaming" />
|
||||
<link rel="canonical" href="https://vyla.cc/sdk.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/sdk.html" />
|
||||
<meta property="og:title" content="SDK Reference — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="Scrape video streams, fetch subtitles, retrieve download links, and monitor provider health directly from your Node.js application." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/sdk.html" />
|
||||
<meta property="twitter:title" content="SDK Reference — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Scrape video streams, fetch subtitles, retrieve download links, and monitor provider health directly from your Node.js application." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Vyla SDK",
|
||||
"description": "Scrape video streams, fetch subtitles, retrieve download links, and monitor provider health directly from your Node.js application.",
|
||||
"url": "https://vyla.cc/sdk.html",
|
||||
"applicationCategory": "DeveloperApplication",
|
||||
"operatingSystem": "Node.js",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var saved = localStorage.getItem('vyla-theme');
|
||||
var isDark = saved ? saved === 'dark' : true;
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Vyla SDK</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/sdk">Sdk</a></li>
|
||||
<li><a class="" href="/sdk-quickstart">Sdk Quickstart</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>SDK Reference</h1>
|
||||
<p class="page-description">Scrape video streams, fetch subtitles, retrieve download links, and monitor provider
|
||||
health directly from your Node.js application.</p>
|
||||
<p>The <strong>Vyla SDK</strong> (<code>@vyla-entertainment/sdk</code>) is a server-side JavaScript/TypeScript
|
||||
package that provides direct access to Vyla's stream resolvers, health probes, download resolvers, and
|
||||
subtitle providers.</p>
|
||||
<p>By embedding the SDK directly into your backend application, provider resolution executes in your own
|
||||
environment. It is an alternative to operating the Stream API as a separate process: your application owns its
|
||||
request boundary, caching, authorization, timeouts, and response shape.</p>
|
||||
<div class="callout callout-note">
|
||||
<div class="callout-content">
|
||||
<p>The SDK can be used independently of the self-hosted Stream API. If you prefer to embed Vyla capabilities
|
||||
in an existing Node.js application rather than run a separate HTTP service, begin with the <a
|
||||
href="/sdk-quickstart">SDK quickstart</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<h2 id="features">Features</h2>
|
||||
<ul>
|
||||
<li><strong>Direct-to-Client Playback</strong>: Scraped streams link directly to source CDNs. No proxy server
|
||||
overhead required unless specified by provider headers.</li>
|
||||
<li><strong>Built-In Health Probing</strong>: Monitor latency and uptime per provider with built-in health
|
||||
check utilities.</li>
|
||||
<li><strong>Automated Anime Sub/Dub Routing</strong>: Automatically configures audio track requirements
|
||||
(<code>sub</code> vs <code>dub</code>) based on provider naming conventions.</li>
|
||||
<li><strong>Multi-Source Subtitle Aggregation</strong>: Merges subtitle tracks from multiple redundant
|
||||
subtitle mirrors into a unified VTT/SRT format.</li>
|
||||
<li><strong>Metadata Fallbacks</strong>: Automatically maps TMDB IDs to external IDs (such as IMDB and
|
||||
AniList) for smooth stream lookups.</li>
|
||||
<li><strong>Self-Contained</strong>: No need to deploy a separate API server — all streaming functionality
|
||||
runs within your existing Node.js application.</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<h2 id="installation">Installation</h2>
|
||||
<p>Install the package into your Node.js backend using your package manager of choice:</p>
|
||||
<div class="code-group">
|
||||
<div class="code-group-tabs"><button class="code-group-tab active" data-tab-index="0" type="button"
|
||||
onclick="cgSwitch(this)">npm</button><button class="code-group-tab" data-tab-index="1" type="button"
|
||||
onclick="cgSwitch(this)">pnpm</button><button class="code-group-tab" data-tab-index="2" type="button"
|
||||
onclick="cgSwitch(this)">yarn</button></div>
|
||||
<div class="code-group-panels">
|
||||
<div class="code-group-panel active">
|
||||
<pre><code class="hljs language-bash">npm <span class="hljs-built_in">install</span> <span class="hljs-string">@vyla-entertainment/sdk</span></code></pre>
|
||||
</div>
|
||||
<div class="code-group-panel">
|
||||
<pre><code class="hljs language-bash">pnpm <span class="hljs-built_in">install</span> <span class="hljs-string">@vyla-entertainment/sdk</span></code></pre>
|
||||
</div>
|
||||
<div class="code-group-panel">
|
||||
<pre><code class="hljs language-bash">yarn <span class="hljs-built_in">add</span> <span class="hljs-string">@vyla-entertainment/sdk</span></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<p><strong>Note</strong>: Node.js <strong>v18+</strong> is required due to modern standard <code>fetch</code>
|
||||
and <code>AbortSignal.timeout</code> usage.</p>
|
||||
</blockquote>
|
||||
<hr>
|
||||
<h2 id="initialization">Initialization</h2>
|
||||
<p>Initialize the <code>VylaSDK</code> client by supplying your <strong>TMDB API Key</strong>. The key resolves
|
||||
media metadata, validates movie/TV show IDs, and translates TMDB IDs into AniList IDs for anime sources.</p>
|
||||
<p>You may optionally provide a pengu.uk manifest URL to enable the Pengu source.</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">VylaSDK</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"@vyla-entertainment/sdk"</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> sdk = <span class="hljs-keyword">new</span> <span class="hljs-title class_">VylaSDK</span>({
|
||||
<span class="hljs-attr">tmdbApiKey</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">TMDB_API_KEY</span>,
|
||||
|
||||
<span class="hljs-attr">penguManifest</span>: <span class="hljs-string">""</span> <span class="hljs-comment">// Optional, get from pengu.uk</span>
|
||||
});
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="core-methods">Core Methods</h2>
|
||||
<h3 id="getstreamkey-id-s-e-clientip"><code>getStream(key, id, s?, e?, clientIP?)</code></h3>
|
||||
<p>Scrapes video stream URLs and playability headers for a specified provider key.</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-comment">// Fetch a movie stream (TMDB ID: 155 - The Dark Knight)</span>
|
||||
<span class="hljs-keyword">const</span> movieStream = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getStream</span>(<span class="hljs-string">"sourcename"</span>, <span class="hljs-string">"155"</span>);
|
||||
|
||||
<span class="hljs-comment">// Fetch a TV show episode (TMDB ID: 1399, Season 1, Episode 1)</span>
|
||||
<span class="hljs-keyword">const</span> tvStream = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getStream</span>(<span class="hljs-string">"sourcename"</span>, <span class="hljs-string">"1399"</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
|
||||
|
||||
<span class="hljs-comment">// Pass client IP for providers that use geo-verification</span>
|
||||
<span class="hljs-keyword">const</span> geoStream = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getStream</span>(<span class="hljs-string">"sourcename"</span>, <span class="hljs-string">"155"</span>, <span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>, <span class="hljs-string">"203.0.113.195"</span>);
|
||||
</code></pre>
|
||||
<h4 id="parameters">Parameters</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Parameter</th>
|
||||
<th align="left">Type</th>
|
||||
<th align="left">Required</th>
|
||||
<th align="left">Default</th>
|
||||
<th align="left">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left"><code>key</code></td>
|
||||
<td align="left"><code>string</code></td>
|
||||
<td align="left"><strong>Yes</strong></td>
|
||||
<td align="left">—</td>
|
||||
<td align="left">Unique key identifier for the provider (e.g. <code>"sourcename"</code>,
|
||||
<code>"sourcename"</code>, <code>"sourcename"</code>).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>id</code></td>
|
||||
<td align="left"><code>string</code></td>
|
||||
<td align="left"><strong>Yes</strong></td>
|
||||
<td align="left">—</td>
|
||||
<td align="left">TMDB media ID (e.g., <code>"155"</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>s</code></td>
|
||||
<td align="left"><code>string | number</code></td>
|
||||
<td align="left"><strong>No</strong></td>
|
||||
<td align="left"><code>null</code></td>
|
||||
<td align="left">Season number (required for TV shows).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>e</code></td>
|
||||
<td align="left"><code>string | number</code></td>
|
||||
<td align="left"><strong>No</strong></td>
|
||||
<td align="left"><code>null</code></td>
|
||||
<td align="left">Episode number (required for TV shows).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>clientIP</code></td>
|
||||
<td align="left"><code>string</code></td>
|
||||
<td align="left"><strong>No</strong></td>
|
||||
<td align="left"><code>null</code></td>
|
||||
<td align="left">End-user IP address forwarded for geo-restricted scrapers.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4 id="example-response-format">Example Response Format</h4>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"url"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://example-cdn.com/hls/manifest.m3u8"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"quality"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"auto"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"isM3U8"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"headers"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"Referer"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://example.com/"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"Origin"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://example.com"</span>
|
||||
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"allUrls"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
|
||||
<span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"url"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://example-cdn.com/hls/manifest.m3u8"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"quality"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"1080p"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"isM3U8"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">]</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h3 id="getsubtitlesid-s-e"><code>getSubtitles(id, s?, e?)</code></h3>
|
||||
<p>Fetches subtitle files aggregated across multiple subtitle backend mirrors (<code>v1</code>, <code>v2</code>,
|
||||
<code>febbox</code>). Returns clean VTT and SRT tracks.
|
||||
</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-comment">// Fetch movie subtitles</span>
|
||||
<span class="hljs-keyword">const</span> movieSubs = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getSubtitles</span>(<span class="hljs-string">"155"</span>);
|
||||
|
||||
<span class="hljs-comment">// Fetch TV show episode subtitles</span>
|
||||
<span class="hljs-keyword">const</span> tvSubs = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getSubtitles</span>(<span class="hljs-string">"1399"</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
|
||||
</code></pre>
|
||||
<h4 id="parameters-1">Parameters</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Parameter</th>
|
||||
<th align="left">Type</th>
|
||||
<th align="left">Required</th>
|
||||
<th align="left">Default</th>
|
||||
<th align="left">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left"><code>id</code></td>
|
||||
<td align="left"><code>string</code></td>
|
||||
<td align="left"><strong>Yes</strong></td>
|
||||
<td align="left">—</td>
|
||||
<td align="left">TMDB media ID.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>s</code></td>
|
||||
<td align="left"><code>string | number</code></td>
|
||||
<td align="left"><strong>No</strong></td>
|
||||
<td align="left"><code>null</code></td>
|
||||
<td align="left">Season number (required for TV shows).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>e</code></td>
|
||||
<td align="left"><code>string | number</code></td>
|
||||
<td align="left"><strong>No</strong></td>
|
||||
<td align="left"><code>null</code></td>
|
||||
<td align="left">Episode number (required for TV shows).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4 id="example-response-format-1">Example Response Format</h4>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">[</span>
|
||||
<span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"English"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"file"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://sub.vdrk.site/v1/vtt/movie/155/English.vtt"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"vtt"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"source"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"v1"</span>
|
||||
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Spanish"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"file"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://fed-subs.pstream.mov/subtitles/spanish.srt"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"srt"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"source"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"febbox"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">]</span>
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h3 id="getdownloadsid-s-e"><code>getDownloads(id, s?, e?)</code></h3>
|
||||
<p>Fetches direct downloadable media files (MP4/MKV) paired with resolution and file size descriptors.</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-comment">// Movie downloads</span>
|
||||
<span class="hljs-keyword">const</span> downloads = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getDownloads</span>(<span class="hljs-string">"155"</span>);
|
||||
|
||||
<span class="hljs-comment">// TV Episode downloads</span>
|
||||
<span class="hljs-keyword">const</span> tvDownloads = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getDownloads</span>(<span class="hljs-string">"1399"</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
|
||||
</code></pre>
|
||||
<h4 id="example-response-format-2">Example Response Format</h4>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">[</span>
|
||||
<span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"url"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://download-node.com/file/the-dark-knight-1080p.mp4"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"quality"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"1080p"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"size"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.14 GB"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"format"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"MP4"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">]</span>
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="provider--health-management">Provider & Health Management</h2>
|
||||
<h3 id="getsourcesexcludedisabled"><code>getSources(excludeDisabled?)</code></h3>
|
||||
<p>Retrieves provider configurations registered in the SDK.</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-comment">// Get all sources including temporarily disabled ones</span>
|
||||
<span class="hljs-keyword">const</span> allSources = sdk.<span class="hljs-title function_">getSources</span>(<span class="hljs-literal">false</span>);
|
||||
|
||||
<span class="hljs-comment">// Get only active/enabled sources</span>
|
||||
<span class="hljs-keyword">const</span> activeSources = sdk.<span class="hljs-title function_">getSources</span>(<span class="hljs-literal">true</span>);
|
||||
</code></pre>
|
||||
<h4 id="parameters-2">Parameters</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Parameter</th>
|
||||
<th align="left">Type</th>
|
||||
<th align="left">Required</th>
|
||||
<th align="left">Default</th>
|
||||
<th align="left">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left"><code>excludeDisabled</code></td>
|
||||
<td align="left"><code>boolean</code></td>
|
||||
<td align="left"><strong>No</strong></td>
|
||||
<td align="left"><code>false</code></td>
|
||||
<td align="left">When <code>true</code>, filters out providers marked as disabled
|
||||
(<code>disabled: true</code>).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<h3 id="probesourcekey"><code>probeSource(key)</code></h3>
|
||||
<p>Executes a health ping test against a single provider to determine current availability and latency using a
|
||||
probe payload (<code>TMDB ID: 155</code>).</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> health = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">probeSource</span>(<span class="hljs-string">"vidrock"</span>);
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(health);
|
||||
<span class="hljs-comment">// Output: { ok: true, ms: 840 }</span>
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h3 id="probeallsources"><code>probeAllSources()</code></h3>
|
||||
<p>Executes health tests in parallel across all non-disabled provider modules.</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">probeAllSources</span>();
|
||||
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(results);
|
||||
</code></pre>
|
||||
<h4 id="example-response-format-3">Example Response Format</h4>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"sourcename1"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">"ok"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"ms"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">420</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"sourcename2"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">"ok"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"ms"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1150</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"sourcename3"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">"ok"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"ms"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">null</span></span> <span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<hr>
|
||||
<h2 id="provider-metadata-properties">Provider Metadata Properties</h2>
|
||||
<p>When calling <code>getSources()</code>, each provider entry returns metadata describing its features and
|
||||
streaming behavior:</p>
|
||||
<pre><code class="hljs language-javascript">{
|
||||
<span class="hljs-attr">key</span>: <span class="hljs-string">'example'</span>,
|
||||
<span class="hljs-attr">label</span>: <span class="hljs-string">'Example'</span>,
|
||||
<span class="hljs-attr">sourceFile</span>: <span class="hljs-string">'example'</span>,
|
||||
<span class="hljs-attr">proxyParam</span>: <span class="hljs-string">'ex'</span>,
|
||||
<span class="hljs-attr">timeout</span>: <span class="hljs-number">20000</span>,
|
||||
<span class="hljs-attr">jitter</span>: <span class="hljs-number">800</span>,
|
||||
<span class="hljs-attr">retries</span>: <span class="hljs-number">3</span>,
|
||||
<span class="hljs-attr">multiUrl</span>: <span class="hljs-literal">true</span>,
|
||||
<span class="hljs-attr">skipProxy</span>: <span class="hljs-literal">false</span>,
|
||||
<span class="hljs-attr">cdnHeaders</span>: [{
|
||||
<span class="hljs-attr">pattern</span>: <span class="hljs-regexp">/./</span>,
|
||||
<span class="hljs-attr">headers</span>: {
|
||||
<span class="hljs-title class_">Referer</span>: <span class="hljs-string">'https://example.com/'</span>,
|
||||
<span class="hljs-title class_">Origin</span>: <span class="hljs-string">'https://example.com'</span>
|
||||
}
|
||||
}]
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="property-reference">Property Reference</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left">Field</th>
|
||||
<th align="left">Type</th>
|
||||
<th align="left">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left"><code>key</code></td>
|
||||
<td align="left"><code>string</code></td>
|
||||
<td align="left">Unique identifier used in <code>getStream()</code> and <code>probeSource()</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>label</code></td>
|
||||
<td align="left"><code>string</code></td>
|
||||
<td align="left">Human-readable name for UI display.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>skipProxy</code></td>
|
||||
<td align="left"><code>boolean</code></td>
|
||||
<td align="left">When <code>true</code>, stream URLs can be fed directly to web video players (e.g.,
|
||||
HLS.js, Video.js) without needing an intermediate backend proxy.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>multiUrl</code></td>
|
||||
<td align="left"><code>boolean</code></td>
|
||||
<td align="left">Indicates whether the source returns multiple stream resolution choices.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>timeout</code></td>
|
||||
<td align="left"><code>number</code></td>
|
||||
<td align="left">Internal request timeout threshold in milliseconds.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>retries</code></td>
|
||||
<td align="left"><code>number</code></td>
|
||||
<td align="left">Max automatic retry attempts executed by health/stream scrapers.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>cdnHeaders</code></td>
|
||||
<td align="left"><code>Array</code></td>
|
||||
<td align="left">Regex pattern matching rules detailing required request headers (<code>Referer</code>,
|
||||
<code>Origin</code>) needed to playback stream segments without <code>403 Forbidden</code> errors.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"><code>disabled</code></td>
|
||||
<td align="left"><code>boolean</code></td>
|
||||
<td align="left">Set to <code>true</code> if a source is down or undergoing maintenance.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
<h2 id="practical-expressjs-backend-example">Practical Express.js Backend Example</h2>
|
||||
<p>Here is an example showing how to serve stream links to your client web application while handling custom CDN
|
||||
playback headers and subtitle merging:</p>
|
||||
<pre><code class="hljs language-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
|
||||
<span class="hljs-keyword">import</span> <span class="hljs-title class_">VylaSDK</span> <span class="hljs-keyword">from</span> <span class="hljs-string">"@vyla-entertainment/sdk"</span>;
|
||||
|
||||
<span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>();
|
||||
<span class="hljs-keyword">const</span> sdk = <span class="hljs-keyword">new</span> <span class="hljs-title class_">VylaSDK</span>({ <span class="hljs-attr">tmdbApiKey</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">TMDB_API_KEY</span> });
|
||||
|
||||
<span class="hljs-comment">// Scrape stream route</span>
|
||||
app.<span class="hljs-title function_">get</span>(<span class="hljs-string">"/api/stream"</span>, <span class="hljs-title function_">async</span> (req, res) => {
|
||||
<span class="hljs-keyword">const</span> { provider, tmdbId, season, episode } = req.<span class="hljs-property">query</span>;
|
||||
<span class="hljs-keyword">const</span> clientIP = req.<span class="hljs-property">headers</span>[<span class="hljs-string">"x-forwarded-for"</span>] || req.<span class="hljs-property">socket</span>.<span class="hljs-property">remoteAddress</span>;
|
||||
|
||||
<span class="hljs-keyword">try</span> {
|
||||
<span class="hljs-keyword">const</span> streamData = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getStream</span>(
|
||||
provider,
|
||||
tmdbId,
|
||||
season || <span class="hljs-literal">null</span>,
|
||||
episode || <span class="hljs-literal">null</span>,
|
||||
clientIP
|
||||
);
|
||||
|
||||
<span class="hljs-keyword">if</span> (!streamData || !streamData.<span class="hljs-property">url</span>) {
|
||||
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">404</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">"No stream found for this provider"</span> });
|
||||
}
|
||||
|
||||
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">json</span>({
|
||||
<span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>,
|
||||
provider,
|
||||
<span class="hljs-attr">stream</span>: streamData,
|
||||
});
|
||||
} <span class="hljs-keyword">catch</span> (error) {
|
||||
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: error.<span class="hljs-property">message</span> });
|
||||
}
|
||||
});
|
||||
|
||||
<span class="hljs-comment">// Fetch subtitles route</span>
|
||||
app.<span class="hljs-title function_">get</span>(<span class="hljs-string">"/api/subtitles"</span>, <span class="hljs-title function_">async</span> (req, res) => {
|
||||
<span class="hljs-keyword">const</span> { tmdbId, season, episode } = req.<span class="hljs-property">query</span>;
|
||||
|
||||
<span class="hljs-keyword">try</span> {
|
||||
<span class="hljs-keyword">const</span> subtitles = <span class="hljs-keyword">await</span> sdk.<span class="hljs-title function_">getSubtitles</span>(
|
||||
tmdbId,
|
||||
season || <span class="hljs-literal">null</span>,
|
||||
episode || <span class="hljs-literal">null</span>
|
||||
);
|
||||
|
||||
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">json</span>({ subtitles });
|
||||
} <span class="hljs-keyword">catch</span> (error) {
|
||||
<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">subtitles</span>: [] });
|
||||
}
|
||||
});
|
||||
|
||||
app.<span class="hljs-title function_">listen</span>(<span class="hljs-number">3000</span>, <span class="hljs-function">() =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"Vyla SDK Server running on port 3000"</span>));
|
||||
</code></pre>
|
||||
<h2 id="sdk-vs-self-hosted-api">SDK vs Self-Hosted API</h2>
|
||||
<p>The SDK and the self-hosted API server provide the same streaming functionality but in different deployment
|
||||
models:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Aspect</th>
|
||||
<th>SDK</th>
|
||||
<th>Self-Hosted API</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Deployment</strong></td>
|
||||
<td>Embedded in your Node.js app</td>
|
||||
<td>Separate server process</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Network</strong></td>
|
||||
<td>Direct source CDN access</td>
|
||||
<td>Built-in proxy for CORS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Rate Limiting</strong></td>
|
||||
<td>None (you control it)</td>
|
||||
<td>Per-API-key limits</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Setup</strong></td>
|
||||
<td>npm install + init</td>
|
||||
<td>Clone repo + configure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Use Case</strong></td>
|
||||
<td>Existing Node.js apps</td>
|
||||
<td>Standalone streaming service</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Choose the SDK if you want to integrate streaming directly into an existing Node.js application. Choose the
|
||||
self-hosted API if you want a standalone streaming service that can be used by multiple clients.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
<li><a href="#initialization">Initialization</a></li>
|
||||
<li><a href="#core-methods">Core Methods</a></li>
|
||||
<li><a href="#provider--health-management">Provider & Health Management</a></li>
|
||||
<li><a href="#provider-metadata-properties">Provider Metadata Properties</a></li>
|
||||
<li><a href="#practical-expressjs-backend-example">Practical Express.js Backend Example</a></li>
|
||||
<li><a href="#sdk-vs-self-hosted-api">SDK vs Self-Hosted API</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
280
self-hosting/index.html
Normal file
@@ -0,0 +1,280 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Self-host Stream API — Vyla</title>
|
||||
<meta name="description" content="Deploy the optional Stream API when your clients need an HTTP/SSE layer." />
|
||||
<meta name="keywords"
|
||||
content="self-hosting, Stream API deployment, Node.js deployment, HTTP API, SSE, server setup, infrastructure" />
|
||||
<link rel="canonical" href="https://vyla.cc/self-hosting.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/self-hosting.html" />
|
||||
<meta property="og:title" content="Self-host Stream API — Vyla" />
|
||||
<meta property="og:description" content="Deploy the optional Stream API when your clients need an HTTP/SSE layer." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/self-hosting.html" />
|
||||
<meta property="twitter:title" content="Self-host Stream API — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Deploy the optional Stream API when your clients need an HTTP/SSE layer." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Self-host Stream API",
|
||||
"description": "Deploy the optional Stream API when your clients need an HTTP/SSE layer.",
|
||||
"url": "https://vyla.cc/self-hosting.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Overview</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/overview">Overview</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Reference</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/paths">Paths</a></li>
|
||||
<li><a class="" href="/stream-api/media-data">Media Data</a></li>
|
||||
<li><a class="" href="/stream-api/operations">Operations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Deployment</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/self-hosting">Self Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Self-host Stream API</h1>
|
||||
<p class="page-description">Deploy the optional Stream API when your clients need an HTTP/SSE layer.</p>
|
||||
<h2 id="overview">Overview</h2>
|
||||
<p>Stream API is a <strong>self-hosted Node.js</strong> application that you run on infrastructure you control.
|
||||
It does not provide a public hosted instance. The service runs as a Node.js process, uses in-memory caching,
|
||||
and can fork workers through Node's built-in <code>cluster</code> module. No database is required by the
|
||||
service itself.</p>
|
||||
<div class="callout callout-warning">
|
||||
<div class="callout-content">
|
||||
<p>Stream API is self-hosted only. There is no public Vyla streaming endpoint. You only need it when your
|
||||
architecture requires an HTTP/SSE layer; Node.js applications can use <a href="/sdk">Vyla SDK</a>
|
||||
directly.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="requirements">Requirements</h2>
|
||||
<ul>
|
||||
<li>Node.js 18 or later</li>
|
||||
<li>A TMDB API key if you want title validation and metadata lookups</li>
|
||||
</ul>
|
||||
<h2 id="environment-variables">Environment variables</h2>
|
||||
<p>Create a <code>.env</code> file in the project root:</p>
|
||||
<pre><code class="hljs language-bash">TMDB_API_KEY=
|
||||
PORT=7860
|
||||
WORKER_COUNT=1
|
||||
ENABLE_DEBUG_ROUTE=<span class="hljs-literal">false</span>
|
||||
PROXY_STREAMS=<span class="hljs-literal">false</span>
|
||||
PROXY_URL=
|
||||
|
||||
<span class="hljs-comment"># Optional telemetry configuration</span>
|
||||
GA_MEASUREMENT_ID=
|
||||
GA_API_SECRET=
|
||||
</code></pre>
|
||||
<div class="card">
|
||||
<div class="card-title">Variable reference</div>
|
||||
<div class="card-body">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>TMDB_API_KEY</code></td>
|
||||
<td>Yes</td>
|
||||
<td>Enables TMDB-based ID validation, metadata lookups, and anime detection. Some sources will not
|
||||
work without it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>GA_MEASUREMENT_ID</code> / <code>GA_API_SECRET</code></td>
|
||||
<td>No</td>
|
||||
<td>Enables Google Analytics event reporting. Leave blank to disable.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>PORT</code></td>
|
||||
<td>No</td>
|
||||
<td>Port the server listens on. Defaults to <code>7860</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>WORKER_COUNT</code></td>
|
||||
<td>No</td>
|
||||
<td>Number of cluster workers to fork. Defaults to <code>1</code> locally.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ENABLE_DEBUG_ROUTE</code></td>
|
||||
<td>No</td>
|
||||
<td>Set to <code>true</code> to enable <code>/api/debug/:id</code>. Keep this <code>false</code> in
|
||||
production — it exposes upstream request/response headers and raw source output.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>PROXY_STREAMS</code></td>
|
||||
<td>No</td>
|
||||
<td>Set to <code>true</code> to route stream playback through your own server's <code>/api</code>
|
||||
proxy endpoint instead of direct URLs.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>PROXY_URL</code></td>
|
||||
<td>No</td>
|
||||
<td>An external proxy base URL to use instead of the built-in <code>/api</code> proxy. Only used if
|
||||
<code>PROXY_STREAMS</code> is enabled.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="installing-and-running">Installing and running</h2>
|
||||
<pre><code class="hljs language-bash">git <span class="hljs-built_in">clone</span> https://gitlab.com/vyla-entertainment/stream-api
|
||||
<span class="hljs-built_in">cd</span> stream-api
|
||||
npm install
|
||||
node server.js
|
||||
</code></pre>
|
||||
<p>The server listens on:</p>
|
||||
<pre><code>http://localhost:7860
|
||||
</code></pre>
|
||||
<p>Visit <code>/health</code> to confirm it is running and check which sources are currently reachable from your
|
||||
host.</p>
|
||||
<h2 id="running-with-multiple-workers">Running with multiple workers</h2>
|
||||
<p>Setting <code>WORKER_COUNT</code> above <code>1</code> forks additional worker processes via
|
||||
<code>cluster</code>.
|
||||
</p>
|
||||
<p>The primary process holds a shared in-memory cache (capped at 1500 entries, pruned every 30 seconds) and
|
||||
relays cache reads/writes between workers over IPC, allowing cached stream results to be shared rather than
|
||||
duplicated.</p>
|
||||
<pre><code class="hljs language-bash">WORKER_COUNT=4
|
||||
node server.js
|
||||
</code></pre>
|
||||
<p>Each worker independently loads source modules and handles outbound requests. Only shared cache operations
|
||||
and concurrency limits are coordinated through the primary process.</p>
|
||||
<h2 id="network-considerations">Network considerations</h2>
|
||||
<p>Several sources are documented in <code>config.js</code> as blocked on datacenter IPs. This commonly affects
|
||||
cloud hosting providers and VPS environments.</p>
|
||||
<p>If a source's <code>getStream</code> consistently returns nothing from your server but works from a
|
||||
residential connection, the cause is usually upstream blocking based on IP reputation. For the best
|
||||
experience, run Vyla API on a residential connection or use a residential proxy.</p>
|
||||
<p>When running on a local desktop environment, you should have minimal issues with source availability since
|
||||
you're using a residential IP address.</p>
|
||||
<h2 id="reverse-proxy--tls">Reverse proxy / TLS</h2>
|
||||
<p>The server runs over plain HTTP. For public deployments, place it behind a reverse proxy such as Caddy,
|
||||
Nginx, or Cloudflare Tunnel to terminate TLS and forward requests to the configured <code>PORT</code>.</p>
|
||||
<p>CORS is already configured at the application layer with:</p>
|
||||
<pre><code>Access-Control-Allow-Origin: *
|
||||
</code></pre>
|
||||
<p>No additional CORS configuration is required on the proxy.</p>
|
||||
<h2 id="verifying-your-deployment">Verifying your deployment</h2>
|
||||
<p>Run these checks in order:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p><code>GET /health</code></p>
|
||||
<p>Confirms the process is running and reports per-source reachability from your host.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>GET /api/test/155?source=vidrock</code></p>
|
||||
<p>Runs a real source resolution using a known TMDB movie ID and returns whether the source succeeded.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><code>GET /api/debug/155?source=vidrock</code></p>
|
||||
<p>Only available when <code>ENABLE_DEBUG_ROUTE=true</code>.</p>
|
||||
<p>Returns request traces, headers, and raw candidate URLs for debugging source failures.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="callout callout-warning">
|
||||
<div class="callout-content">
|
||||
<p>Leave <code>ENABLE_DEBUG_ROUTE</code> disabled on public deployments. The debug route exposes upstream
|
||||
request information and raw stream URLs, which can leak internal details.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#overview">Overview</a></li>
|
||||
<li><a href="#requirements">Requirements</a></li>
|
||||
<li><a href="#environment-variables">Environment variables</a></li>
|
||||
<li><a href="#installing-and-running">Installing and running</a></li>
|
||||
<li><a href="#running-with-multiple-workers">Running with multiple workers</a></li>
|
||||
<li><a href="#network-considerations">Network considerations</a></li>
|
||||
<li><a href="#reverse-proxy--tls">Reverse proxy / TLS</a></li>
|
||||
<li><a href="#verifying-your-deployment">Verifying your deployment</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
93
sitemap.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://vyla.cc/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/architecture.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/home.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/go.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/tv.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/player.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/sdk-quickstart.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/overview.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/paths.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/media-data.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/stream-api/operations.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/self-hosting.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/concepts/providers.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/guides/integrations.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/guides/error-handling.html</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/misc/credit.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://vyla.cc/misc/dmca.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
199
stream-api/media-data/index.html
Normal file
@@ -0,0 +1,199 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Subtitles and downloads — Vyla</title>
|
||||
<meta name="description" content="Unified media-data paths for movies and TV episodes." />
|
||||
<meta name="keywords"
|
||||
content="subtitles, downloads, media data, subtitle tracks, download links, VTT, SRT, subtitle API" />
|
||||
<link rel="canonical" href="https://vyla.cc/stream-api/media-data.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/stream-api/media-data.html" />
|
||||
<meta property="og:title" content="Subtitles and downloads — Vyla" />
|
||||
<meta property="og:description" content="Unified media-data paths for movies and TV episodes." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/stream-api/media-data.html" />
|
||||
<meta property="twitter:title" content="Subtitles and downloads — Vyla" />
|
||||
<meta property="twitter:description" content="Unified media-data paths for movies and TV episodes." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Subtitles and downloads",
|
||||
"description": "Unified media-data paths for movies and TV episodes.",
|
||||
"url": "https://vyla.cc/stream-api/media-data.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Overview</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/overview">Overview</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Reference</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/paths">Paths</a></li>
|
||||
<li><a class="active" href="/stream-api/media-data">Media Data</a></li>
|
||||
<li><a class="" href="/stream-api/operations">Operations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Deployment</p>
|
||||
<ul>
|
||||
<li><a class="" href="/self-hosting">Self Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Subtitles and downloads</h1>
|
||||
<p class="page-description">Unified media-data paths for movies and TV episodes.</p>
|
||||
<h2 id="subtitle-paths">Subtitle paths</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Media</th>
|
||||
<th>Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Movie</td>
|
||||
<td><code>GET /subtitles/movie/:id</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TV episode</td>
|
||||
<td><code>GET /subtitles/tv/:id/:season/:episode</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>The same routes are available under <code>/api/subtitles</code>. A successful response is a JSON array of
|
||||
tracks; a missing result returns <code>404</code> with an error object.</p>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">[</span>
|
||||
<span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"English"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"file"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://example.invalid/subtitles/en.vtt"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"vtt"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"source"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"v1"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">]</span>
|
||||
</code></pre>
|
||||
<p><code>file</code> is the track URL, <code>type</code> is normally <code>vtt</code> or <code>srt</code>, and
|
||||
<code>source</code> identifies the subtitle backend. Prefer the tracks included in the playback
|
||||
<code>meta</code> event when you are already opening a playback stream; use these paths when subtitles are
|
||||
fetched separately.
|
||||
</p>
|
||||
<h2 id="download-paths">Download paths</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Media</th>
|
||||
<th>Path</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Movie</td>
|
||||
<td><code>GET /downloads/movie/:id</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TV episode</td>
|
||||
<td><code>GET /downloads/tv/:id/:season/:episode</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>The <code>/api/downloads</code> prefix is also supported. A successful response is an array; an empty array
|
||||
simply means no candidates were found.</p>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">[</span>
|
||||
<span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"url"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://example.invalid/file.mp4"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"quality"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"1080p"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"size"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2.14 GB"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"format"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"MP4"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">]</span>
|
||||
</code></pre>
|
||||
<p>Download URLs are provider results, not durable Vyla-hosted files. Display quality and size as informational
|
||||
fields, and handle expired or unavailable links in your client.</p>
|
||||
<h2 id="request-and-response-rules">Request and response rules</h2>
|
||||
<ul>
|
||||
<li>IDs are TMDB IDs; TV paths additionally require season and episode numbers.</li>
|
||||
<li>Both families return JSON, unlike the SSE playback paths.</li>
|
||||
<li>Handle <code>404</code> for missing subtitles and <code>500</code> for an upstream or resolver failure.
|
||||
</li>
|
||||
<li>Do not block first playback on either call. Fetch them in parallel or after the player has started.</li>
|
||||
</ul>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#subtitle-paths">Subtitle paths</a></li>
|
||||
<li><a href="#download-paths">Download paths</a></li>
|
||||
<li><a href="#request-and-response-rules">Request and response rules</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer"><a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a></footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
167
stream-api/operations/index.html
Normal file
@@ -0,0 +1,167 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Health, testing, and safe operations — Vyla</title>
|
||||
<meta name="description"
|
||||
content="Observe SDK providers and diagnose a Stream API deployment without making debug data public." />
|
||||
<meta name="keywords"
|
||||
content="health check, API testing, operations, debugging, provider health, API monitoring, deployment" />
|
||||
<link rel="canonical" href="https://vyla.cc/stream-api/operations.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/stream-api/operations.html" />
|
||||
<meta property="og:title" content="Health, testing, and safe operations — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="Observe SDK providers and diagnose a Stream API deployment without making debug data public." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/stream-api/operations.html" />
|
||||
<meta property="twitter:title" content="Health, testing, and safe operations — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="Observe SDK providers and diagnose a Stream API deployment without making debug data public." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Health, testing, and safe operations",
|
||||
"description": "Observe SDK providers and diagnose a Stream API deployment without making debug data public.",
|
||||
"url": "https://vyla.cc/stream-api/operations.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Overview</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/overview">Overview</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Reference</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/paths">Paths</a></li>
|
||||
<li><a class="" href="/stream-api/media-data">Media Data</a></li>
|
||||
<li><a class="active" href="/stream-api/operations">Operations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Deployment</p>
|
||||
<ul>
|
||||
<li><a class="" href="/self-hosting">Self Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Health, testing, and safe operations</h1>
|
||||
<p class="page-description">Observe SDK providers and diagnose a Stream API deployment without making debug data
|
||||
public.</p>
|
||||
<h2 id="health">Health</h2>
|
||||
<p><code>GET /health</code> (or <code>/api/health</code>) probes active providers and returns a JSON summary. It
|
||||
includes a status, timestamp, TMDB-key presence, cache size, and a map of provider keys to
|
||||
<code>{ ok, ms }</code> results.
|
||||
</p>
|
||||
<p>Health is a signal, not a promise of playback. Provider behavior can differ by title, client network, and the
|
||||
moment of the request. Use it for observability and source selection—not as a guarantee that a viewer will
|
||||
receive a stream.</p>
|
||||
<h2 id="test-one-provider">Test one provider</h2>
|
||||
<p><code>GET /test/:id?source=:providerKey</code> resolves a single provider for a movie. Add
|
||||
<code>season</code> and <code>episode</code> (or <code>s</code> and <code>e</code>) for TV.
|
||||
</p>
|
||||
<pre><code class="hljs language-bash">curl <span class="hljs-string">"http://localhost:7860/test/155?source=provider-key"</span>
|
||||
curl <span class="hljs-string">"http://localhost:7860/test/1396?season=1&episode=1&source=provider-key"</span>
|
||||
</code></pre>
|
||||
<p>Use source keys reported by the running deployment. A test result reports whether resolution and verification
|
||||
succeeded; clients should inspect the result body rather than relying only on a transport success.</p>
|
||||
<h2 id="debug-route">Debug route</h2>
|
||||
<p><code>GET /debug/:id?source=:providerKey</code> offers detailed candidate and request traces, but it is only
|
||||
available when <code>ENABLE_DEBUG_ROUTE=true</code>.</p>
|
||||
<div class="callout callout-warning">
|
||||
<div class="callout-content">
|
||||
<p>Do not enable the debug route on a public deployment. It can expose upstream request data, raw
|
||||
candidates, and headers. Enable it temporarily in a controlled environment, then disable it again.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="production-checklist">Production checklist</h2>
|
||||
<ul>
|
||||
<li>Set <code>TMDB_API_KEY</code> when metadata-dependent sources are required.</li>
|
||||
<li>Keep <code>ENABLE_DEBUG_ROUTE=false</code> outside controlled troubleshooting.</li>
|
||||
<li>Use TLS and enforce your own access policy before making the service remotely reachable.</li>
|
||||
<li>Monitor health trends and playback failures separately.</li>
|
||||
<li>Run updates and provider changes through your normal deployment process.</li>
|
||||
<li>Build player fallbacks instead of assuming a specific provider is always available.</li>
|
||||
</ul>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#health">Health</a></li>
|
||||
<li><a href="#test-one-provider">Test one provider</a></li>
|
||||
<li><a href="#debug-route">Debug route</a></li>
|
||||
<li><a href="#production-checklist">Production checklist</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
194
stream-api/overview/index.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Stream API — Vyla</title>
|
||||
<meta name="description" content="An optional self-hosted HTTP and SSE layer built on top of Vyla SDK." />
|
||||
<meta name="keywords"
|
||||
content="Stream API, HTTP API, SSE, Server-Sent Events, streaming API, self-hosted API, Node.js API" />
|
||||
<link rel="canonical" href="https://vyla.cc/stream-api/overview.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/stream-api/overview.html" />
|
||||
<meta property="og:title" content="Stream API — Vyla" />
|
||||
<meta property="og:description" content="An optional self-hosted HTTP and SSE layer built on top of Vyla SDK." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/stream-api/overview.html" />
|
||||
<meta property="twitter:title" content="Stream API — Vyla" />
|
||||
<meta property="twitter:description" content="An optional self-hosted HTTP and SSE layer built on top of Vyla SDK." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Stream API",
|
||||
"description": "An optional self-hosted HTTP and SSE layer built on top of Vyla SDK.",
|
||||
"url": "https://vyla.cc/stream-api/overview.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Overview</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/stream-api/overview">Overview</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Reference</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/paths">Paths</a></li>
|
||||
<li><a class="" href="/stream-api/media-data">Media Data</a></li>
|
||||
<li><a class="" href="/stream-api/operations">Operations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Deployment</p>
|
||||
<ul>
|
||||
<li><a class="" href="/self-hosting">Self Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Stream API</h1>
|
||||
<p class="page-description">An optional self-hosted HTTP and SSE layer built on top of Vyla SDK.</p>
|
||||
<h2 id="use-this-only-when-http-is-the-right-boundary">Use this only when HTTP is the right boundary</h2>
|
||||
<p>Stream API packages Vyla SDK behind a small Node.js HTTP service. It is useful when a browser, mobile app, or
|
||||
another process needs progressive playback candidates over HTTP. It is not required to use Vyla, and it is not
|
||||
the primary integration for a Node.js application—use <a href="/sdk">Vyla SDK</a> directly in that case.
|
||||
</p>
|
||||
<p>The service accepts TMDB IDs, asks configured SDK providers for candidates, verifies results, and emits
|
||||
usable sources as they arrive. A player can start with the first source while retaining later sources as
|
||||
fallbacks.</p>
|
||||
<h2 id="what-it-provides">What it provides</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Capability</th>
|
||||
<th>Endpoint family</th>
|
||||
<th>Why it exists</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Progressive playback</td>
|
||||
<td><code>/movie</code>, <code>/tv</code></td>
|
||||
<td>Stream metadata and verified source candidates with SSE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Supplemental media</td>
|
||||
<td><code>/subtitles</code>, <code>/downloads</code></td>
|
||||
<td>Fetch tracks or downloadable candidates separately</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Operations</td>
|
||||
<td><code>/health</code>, <code>/test</code>, <code>/debug</code></td>
|
||||
<td>Check source status and investigate a provider when permitted</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2 id="start-a-local-instance">Start a local instance</h2>
|
||||
<pre><code class="hljs language-bash">git <span class="hljs-built_in">clone</span> https://gitlab.com/vyla-entertainment/stream-api
|
||||
<span class="hljs-built_in">cd</span> stream-api
|
||||
npm install
|
||||
TMDB_API_KEY=your_key
|
||||
node server.js
|
||||
</code></pre>
|
||||
<p>The default local address is <code>http://localhost:7860</code>. For remote access, place the service behind
|
||||
TLS and your own access controls. Full configuration is in <a href="/self-hosting">Self-hosting</a>.</p>
|
||||
<h2 id="lifecycle-of-a-playback-request">Lifecycle of a playback request</h2>
|
||||
<ol>
|
||||
<li>A client opens <code>/movie?id=:tmdbId</code> or
|
||||
<code>/tv?id=:tmdbId&season=:season&episode=:episode</code>.
|
||||
</li>
|
||||
<li>The API returns an SSE response and emits a <code>meta</code> event with title metadata and subtitle
|
||||
tracks when available.</li>
|
||||
<li>Active SDK providers resolve in parallel; verified results arrive as individual <code>source</code>
|
||||
events.</li>
|
||||
<li>The server sends <code>done</code> after all source attempts finish or time out.</li>
|
||||
</ol>
|
||||
<p>Start the player on the first source. Never wait for <code>done</code> before allowing playback, and always
|
||||
retain a fallback queue. See <a href="/stream-api/paths">Paths and events</a> for the contract.</p>
|
||||
<div class="callout callout-tip">
|
||||
<div class="callout-content">
|
||||
<p>If your application is already a Node.js service, call <code>VylaSDK#getStream</code>,
|
||||
<code>getSubtitles</code>, and <code>getDownloads</code> directly. That removes an unnecessary network hop
|
||||
and lets you define your own public API.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#use-this-only-when-http-is-the-right-boundary">Use this only when HTTP is the right boundary</a>
|
||||
</li>
|
||||
<li><a href="#what-it-provides">What it provides</a></li>
|
||||
<li><a href="#start-a-local-instance">Start a local instance</a></li>
|
||||
<li><a href="#lifecycle-of-a-playback-request">Lifecycle of a playback request</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
196
stream-api/paths/index.html
Normal file
@@ -0,0 +1,196 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Paths and playback events — Vyla</title>
|
||||
<meta name="description" content="The complete Stream API playback contract, unified for movies and TV episodes." />
|
||||
<meta name="keywords"
|
||||
content="Stream API paths, playback events, SSE events, API endpoints, movie API, TV API, streaming endpoints" />
|
||||
<link rel="canonical" href="https://vyla.cc/stream-api/paths.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/stream-api/paths.html" />
|
||||
<meta property="og:title" content="Paths and playback events — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="The complete Stream API playback contract, unified for movies and TV episodes." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/stream-api/paths.html" />
|
||||
<meta property="twitter:title" content="Paths and playback events — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="The complete Stream API playback contract, unified for movies and TV episodes." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/thumbnail.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Paths and playback events",
|
||||
"description": "The complete Stream API playback contract, unified for movies and TV episodes.",
|
||||
"url": "https://vyla.cc/stream-api/paths.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Overview</p>
|
||||
<ul>
|
||||
<li><a class="" href="/stream-api/overview">Overview</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Reference</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/stream-api/paths">Paths</a></li>
|
||||
<li><a class="" href="/stream-api/media-data">Media Data</a></li>
|
||||
<li><a class="" href="/stream-api/operations">Operations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Deployment</p>
|
||||
<ul>
|
||||
<li><a class="" href="/self-hosting">Self Hosting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Paths and playback events</h1>
|
||||
<p class="page-description">The complete Stream API playback contract, unified for movies and TV episodes.</p>
|
||||
<h2 id="playback-paths">Playback paths</h2>
|
||||
<p>The API supports both the short paths below and <code>/api</code>-prefixed equivalents. A movie needs only
|
||||
<code>id</code>; a TV episode also needs <code>season</code> and <code>episode</code>.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Media</th>
|
||||
<th>Path</th>
|
||||
<th>Response</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Movie</td>
|
||||
<td><code>GET /movie?id=:id</code></td>
|
||||
<td>Server-Sent Events</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TV episode</td>
|
||||
<td><code>GET /tv?id=:id&season=:season&episode=:episode</code></td>
|
||||
<td>Server-Sent Events</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><code>id</code> is a TMDB ID. For example:</p>
|
||||
<pre><code class="hljs language-bash">curl -N <span class="hljs-string">"http://localhost:7860/movie?id=155"</span>
|
||||
curl -N <span class="hljs-string">"http://localhost:7860/tv?id=1396&season=1&episode=1"</span>
|
||||
</code></pre>
|
||||
<h2 id="event-contract">Event contract</h2>
|
||||
<p>Playback routes use <code>text/event-stream</code>, not a single JSON document. Read each <code>data: </code>
|
||||
line as JSON.</p>
|
||||
<h3 id="meta"><code>meta</code></h3>
|
||||
<p>Sent before source resolution completes. It can include TMDB metadata and an array of subtitle tracks.</p>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"meta"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"meta"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">155</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"The Dark Knight"</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"subtitles"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">{</span> <span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"English"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"file"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"https://…"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"vtt"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"source"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"v1"</span> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">]</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<h3 id="source"><code>source</code></h3>
|
||||
<p>One event for each candidate that passes the service's verification pipeline.</p>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"source"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"source"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"source"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"provider-key"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"label"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Provider name"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"url"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"http://localhost:7860/api?url=…"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<p>The <code>url</code> is ready for a player. HLS sources may be wrapped by Vyla's proxy so manifest segments
|
||||
and keys can be fetched with the appropriate upstream headers. Some configured sources bypass proxying. Do not
|
||||
assume a filename extension alone determines playback behavior.</p>
|
||||
<h3 id="done"><code>done</code></h3>
|
||||
<p>Sent after the configured attempts are complete.</p>
|
||||
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"done"</span><span class="hljs-punctuation">,</span> <span class="hljs-attr">"total"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span> <span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<p><code>total: 0</code> means no playable sources were produced for that request. The SSE connection itself can
|
||||
still be successful.</p>
|
||||
<h2 id="client-rules">Client rules</h2>
|
||||
<ol>
|
||||
<li>Start playback as soon as the first <code>source</code> arrives.</li>
|
||||
<li>Queue every later source for manual switching or automatic fallback.</li>
|
||||
<li>Attach subtitle tracks from <code>meta</code> without blocking playback.</li>
|
||||
<li>Treat an empty result as a normal product state, not a malformed response.</li>
|
||||
<li>Abort the request when the viewer leaves the page or chooses another title.</li>
|
||||
</ol>
|
||||
<p>The <a href="/guides/integrations">integration guide</a> contains a practical player pattern; <a
|
||||
href="/guides/error-handling">error handling</a> covers fallback behavior.</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#playback-paths">Playback paths</a></li>
|
||||
<li><a href="#event-contract">Event contract</a></li>
|
||||
<li><a href="#client-rules">Client rules</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer"><a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a></footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
157
tv/index.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Vyla TV — Vyla</title>
|
||||
<meta name="description"
|
||||
content="The Android TV application for running the Vyla media experience on your television." />
|
||||
<meta name="keywords"
|
||||
content="Vyla TV, Android TV, TV streaming, smart TV, television app, media streaming, Android TV app" />
|
||||
<link rel="canonical" href="https://vyla.cc/tv.html" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vyla.cc/tv.html" />
|
||||
<meta property="og:title" content="Vyla TV — Vyla" />
|
||||
<meta property="og:description"
|
||||
content="The Android TV application for running the Vyla media experience on your television." />
|
||||
<meta property="og:image" content="https://vyla.cc/assets/images/android-tv-preview.png" />
|
||||
<meta property="og:site_name" content="Vyla" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://vyla.cc/tv.html" />
|
||||
<meta property="twitter:title" content="Vyla TV — Vyla" />
|
||||
<meta property="twitter:description"
|
||||
content="The Android TV application for running the Vyla media experience on your television." />
|
||||
<meta property="twitter:image" content="https://vyla.cc/assets/images/android-tv-preview.png" />
|
||||
<meta property="twitter:site" content="@vylaentertainment" />
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="Vyla" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/assets/base.css" />
|
||||
<link rel="stylesheet" href="/assets/highlight.css" />
|
||||
<link rel="stylesheet" href="/assets/mintlify-overrides.css" />
|
||||
|
||||
<!-- JSON-LD Structured Data -->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "Vyla TV",
|
||||
"description": "The Android TV application for running the Vyla media experience on your television.",
|
||||
"url": "https://vyla.cc/tv.html",
|
||||
"applicationCategory": "MultimediaApplication",
|
||||
"operatingSystem": "Windows, macOS, Linux, Android, Android TV",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="banner">
|
||||
<span>Vyla is self-hosted. Run Vyla Home, embed the SDK, or deploy Stream API on infrastructure you control. Rights
|
||||
holders can request source removal — <a href="/misc/dmca">DMCA policy</a>.</span>
|
||||
<button id="banner-close" onclick="dismissBanner()" aria-label="Dismiss">✕</button>
|
||||
</div>
|
||||
|
||||
<header id="navbar">
|
||||
<div class="nav-top">
|
||||
<a class="brand" href="/"><img src="/assets/images/logo.png" alt="Vyla" id="brand-logo" loading="lazy"
|
||||
fetchpriority="low"></a>
|
||||
<button id="search-trigger" type="button" onclick="openSearch()" aria-label="Search"></button>
|
||||
<div class="links">
|
||||
<button id="theme-toggle" type="button" onclick="toggleThemeMenu(event)" aria-label="Theme"></button>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="tabs">
|
||||
<a class="tab-link" href="/">Vyla</a>
|
||||
<a class="tab-link" href="/sdk">SDK</a>
|
||||
<a class="tab-link active" href="/stream-api/overview">Stream API</a>
|
||||
<a class="tab-link" href="/guides/integrations">Resources</a>
|
||||
<a class="tab-link" href="/misc/credit">Misc</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="layout">
|
||||
<nav id="sidebar">
|
||||
<div class="group">
|
||||
<p class="group-title">Start here</p>
|
||||
<ul>
|
||||
<li><a class="active" href="/">Introduction</a></li>
|
||||
</li>
|
||||
<li><a class="" href="/architecture">Architecture</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Products</p>
|
||||
<ul>
|
||||
<li><a class="" href="/home">Home</a></li>
|
||||
<li><a class="" href="/go">Go</a></li>
|
||||
<li><a class="active" href="/tv">Tv</a></li>
|
||||
<li><a class="" href="/player">Player</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="group">
|
||||
<p class="group-title">Concepts</p>
|
||||
<ul>
|
||||
<li><a class="" href="/concepts/providers">Providers</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="content-col">
|
||||
<main class="prose">
|
||||
<h1>Vyla TV</h1>
|
||||
<p class="page-description">The Android TV application for running the Vyla media experience on your television.
|
||||
</p>
|
||||
<img class="vyla-banner" src="/assets/images/android-tv/site-preview.png"
|
||||
alt="A collage of films and shows available through a Vyla experience" loading="lazy" fetchpriority="low">
|
||||
<h2 id="vyla-tv">Vyla TV</h2>
|
||||
<p><strong>Vyla TV</strong> is an Android TV streaming app built with Jetpack Compose and ExoPlayer that lets
|
||||
you browse and watch movies and TV shows from TMDB.</p>
|
||||
<h2 id="downloads">Downloads</h2>
|
||||
<p>Vyla TV is available for Android TV. Get the latest version below:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Android TV</td>
|
||||
<td><a href="https://gitlab.com/vyla-entertainment/TV/-/releases">Download .apk</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Inside the releases page, you can find the latest version of Vyla TV for Android TV, find the
|
||||
<code>.apk</code> file, and install it on your device.
|
||||
</p>
|
||||
</main>
|
||||
<aside id="toc">
|
||||
<p class="toc-title">On this page</p>
|
||||
<ul>
|
||||
<li><a href="#vyla-tv">Vyla TV</a></li>
|
||||
<li><a href="#downloads">Downloads</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
<footer id="site-footer">
|
||||
<a href="https://discord.gg/5eqRhKeywG" target="_blank" rel="noopener">Discord</a>
|
||||
<a href="https://gitlab.com/vyla-entertainment/" target="_blank" rel="noopener">GitLab</a>
|
||||
</footer>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
118
update_seo.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
base_dir = "dist"
|
||||
|
||||
seo_templates = {
|
||||
"architecture": {
|
||||
"title": "Our Architecture — Vyla",
|
||||
"description": "How Vyla Home, SDK, and Stream API relate, and where each responsibility belongs.",
|
||||
"keywords": "Vyla architecture, Vyla Home, Vyla SDK, Stream API, system design, media stack, deployment patterns, self-hosted media",
|
||||
"canonical": "architecture.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"go": {
|
||||
"title": "Vyla Go — Vyla",
|
||||
"description": "The Android control center for running the Vyla media stack locally.",
|
||||
"keywords": "Vyla Go, Android media player, mobile streaming, self-hosted media, Android app, media streaming",
|
||||
"canonical": "go.html",
|
||||
"image": "android-preview.png"
|
||||
},
|
||||
"player": {
|
||||
"title": "Build a player — Vyla",
|
||||
"description": "Design a resilient Vyla playback experience around your own product and infrastructure.",
|
||||
"keywords": "Vyla player, media player, custom player, playback experience, HLS player, MP4 player, streaming player",
|
||||
"canonical": "player.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"sdk-quickstart": {
|
||||
"title": "SDK quickstart — Vyla",
|
||||
"description": "Install Vyla SDK, resolve a source, and build a safe server-side integration.",
|
||||
"keywords": "Vyla SDK, SDK quickstart, Node.js SDK, server-side integration, stream resolution, API integration",
|
||||
"canonical": "sdk-quickstart.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"self-hosting": {
|
||||
"title": "Self-host Stream API — Vyla",
|
||||
"description": "Deploy the optional Stream API when your clients need an HTTP/SSE layer.",
|
||||
"keywords": "self-hosting, Stream API deployment, Node.js deployment, HTTP API, SSE, server setup, infrastructure",
|
||||
"canonical": "self-hosting.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"tv": {
|
||||
"title": "Vyla TV — Vyla",
|
||||
"description": "The Android TV application for running the Vyla media experience on your television.",
|
||||
"keywords": "Vyla TV, Android TV, TV streaming, smart TV, television app, media streaming, Android TV app",
|
||||
"canonical": "tv.html",
|
||||
"image": "android-tv-preview.png"
|
||||
},
|
||||
"stream-api/overview": {
|
||||
"title": "Stream API — Vyla",
|
||||
"description": "An optional self-hosted HTTP and SSE layer built on top of Vyla SDK.",
|
||||
"keywords": "Stream API, HTTP API, SSE, Server-Sent Events, streaming API, self-hosted API, Node.js API",
|
||||
"canonical": "stream-api/overview.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"stream-api/paths": {
|
||||
"title": "Paths and playback events — Vyla",
|
||||
"description": "The complete Stream API playback contract, unified for movies and TV episodes.",
|
||||
"keywords": "Stream API paths, playback events, SSE events, API endpoints, movie API, TV API, streaming endpoints",
|
||||
"canonical": "stream-api/paths.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"stream-api/media-data": {
|
||||
"title": "Subtitles and downloads — Vyla",
|
||||
"description": "Unified media-data paths for movies and TV episodes.",
|
||||
"keywords": "subtitles, downloads, media data, subtitle tracks, download links, VTT, SRT, subtitle API",
|
||||
"canonical": "stream-api/media-data.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"stream-api/operations": {
|
||||
"title": "Health, testing, and safe operations — Vyla",
|
||||
"description": "Observe SDK providers and diagnose a Stream API deployment without making debug data public.",
|
||||
"keywords": "health check, API testing, operations, debugging, provider health, API monitoring, deployment",
|
||||
"canonical": "stream-api/operations.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"concepts/providers": {
|
||||
"title": "Providers — Vyla",
|
||||
"description": "How Vyla SDK sources are configured, selected, and verified.",
|
||||
"keywords": "providers, source configuration, stream sources, provider verification, SDK providers, source selection",
|
||||
"canonical": "concepts/providers.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"guides/integrations": {
|
||||
"title": "Stream API client integration — Vyla",
|
||||
"description": "Build an optional Stream API player client with HLS/MP4 handling, SSE sources, subtitles, and fallbacks.",
|
||||
"keywords": "client integration, player integration, HLS.js, React, Vue, iOS, Android, SSE client, streaming client",
|
||||
"canonical": "guides/integrations.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"guides/error-handling": {
|
||||
"title": "Stream API error handling — Vyla",
|
||||
"description": "Handle SSE failures, source fallbacks, and edge cases in an optional Stream API client.",
|
||||
"keywords": "error handling, fallbacks, SSE errors, streaming errors, retry logic, error boundary, source fallback",
|
||||
"canonical": "guides/error-handling.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"misc/credit": {
|
||||
"title": "Credit — Vyla",
|
||||
"description": "Credits and inspiration behind the Vyla API ecosystem.",
|
||||
"keywords": "credits, attribution, open source, inspiration, P-Stream, CinePro, Movieslay, acknowledgments",
|
||||
"canonical": "misc/credit.html",
|
||||
"image": "thumbnail.png"
|
||||
},
|
||||
"misc/dmca": {
|
||||
"title": "DMCA & Copyright — Vyla",
|
||||
"description": "Content sources used by Vyla API and how rights holders can request removal.",
|
||||
"keywords": "DMCA, copyright, takedown, legal, content sources, rights holders, copyright policy, takedown request",
|
||||
"canonical": "misc/dmca.html",
|
||||
"image": "thumbnail.png"
|
||||
}
|
||||
}
|
||||
|
||||
def generate_seo_head(template):
|
||||
name = template["title"].replace(" — Vyla", "")
|
||||
json_ld = " <!-- JSON-LD Structured Data -->\n <script type="application/ld+json">\n {\n "@context": "https://schema.org",\n "@type": "SoftwareApplication",\n "name": "" + name + "",\n "description": "" + template["description"] + "",\n "url": "https://vyla.cc/" + template["canonical"] + "",\n "applicationCategory": "MultimediaApplication",\n "operatingSystem": "Windows, macOS, Linux, Android, Android TV",\n "offers": {\n "@type": "Offer",\n "price": "0",\n "priceCurrency": "USD"\n }\n }\n </script>"
|
||||
|
||||
return " <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <title>" + template["title"] + "</title>\n <meta name="description" content="" + template["description"] + "" />\n <meta name="keywords" content="" + template["keywords"] + "" />\n <link rel="canonical" href="https://vyla.cc/" + template["canonical"] + "" />\n\n <!-- Open Graph / Facebook -->\n <meta property="og:type" content="website" />\n <meta property="og:url" content="https://vyla.cc/" + template["canonical"] + "" />\n <meta property="og:title" content="" + template["title"] + "" />\n <meta property="og:description" content="" + template["description"] + "" />\n <meta property="og:image" content="https://vyla.cc/assets/images/" + template["image"] + "" />\n <meta property="og:site_name" content="Vyla" />\n\n <!-- Twitter -->\n <meta property="twitter:card" content="summary_large_image" />\n <meta property="twitter:url" content="https://vyla.cc/" + template["canonical"] + "" />\n <meta property="twitter:title" content="" + template["title"] + "" />\n <meta property="twitter:description" content="" + template["description"] + "" />\n <meta property="twitter:image" content="https://vyla.cc/assets/images/" + template["image"] + "" />\n <meta property="twitter:site" content="@vylaentertainment" />\n\n <!-- Additional SEO -->\n <meta name="robots" content="index, follow" />\n <meta name="author" content="Vyla" />\n\n <link rel="icon" href="/favicon.ico" />\n <link rel="stylesheet" href="/assets/base.css" />\n <link rel="stylesheet" href="/assets/highlight.css" />\n <link rel="stylesheet" href="/assets/mintlify-overrides.css" />\n\n" + json_ld + "\n\n <script>\n (fun
|
||||