Files
documentation/guides/error-handling/index.html

432 lines
32 KiB
HTML

<!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> &#x26;&#x26; !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 &#x26;&#x26; episode
? <span class="hljs-string">`<span class="hljs-subst">${BASE}</span>/tv?id=<span class="hljs-subst">${tmdbId}</span>&#x26;season=<span class="hljs-subst">${season}</span>&#x26;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>&#x3C;<span class="hljs-title class_">ReadableStreamDefaultReader</span>&#x3C;<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>&#x3C;<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 &#x3C; 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&#x3C;<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&#x3C;<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> &#x26;&#x26; !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>