7.1 KiB
Building a Custom Player
Architectural principles, format handling, and engineering guidelines for building resilient video players backed by Vyla infrastructure.
Overview
In the Vyla ecosystem, you are never locked into an official frontend. Vyla provides the discovery, resolution, and proxy layers; your application owns the player experience. Whether you are building a custom web portal, desktop client, or mobile app, this guide covers the core engineering patterns required for a robust player.
Integration Boundaries
Choose your integration point based on your architecture:
| Architecture | Integration Point | Data Flow | Best For |
|---|---|---|---|
| Node.js Backend | Vyla SDK Directly | sdk.getStream() in-process |
Applications with full backend control, custom authentication, and server-side session tracking. |
| Browser / Mobile | Stream API Gateway | SSE /movie or /tv over HTTP |
Web applications, smart TV frontends, or lightweight mobile clients requiring an HTTP/SSE source feed. |
Playback Formats: HLS vs Direct Video
Resolved candidate streams come in two primary container types:
- HLS Streams (
type: "hls"):- Master M3U8 playlists containing multiple bitrate/resolution variants.
- Proxied through Stream API (
/api?url=...), which automatically rewrites chunk URLs and AES-128 encryption key URIs. - Requires native HLS support (Safari on macOS/iOS) or a JavaScript HLS MSE library such as HLS.js in modern browsers.
- Direct Media Files (
type: "mp4"):- Progressive MP4 or MKV streams.
- Proxied with HTTP Range header forwarding (
206 Partial Content), allowing smooth scrubbing and seeking. - Directly assignable to the HTML5
<video>elementsrcattribute.
Resilient Player Architecture
Upstream streaming hosts are dynamic: network latencies fluctuate, upstream proxies fail, and geo-restrictions vary. A production-ready Vyla player must adhere to five design rules:
1. Start on the First Candidate
Never wait for all providers to finish. Stream API emits verified sources over Server-Sent Events (SSE). The moment the first source event arrives (typically within 3 to 8 seconds):
- Initialize your player immediately.
- Store any subsequent incoming
sourceevents in a priority fallback array.
2. Maintain a Fallback Priority Array
If the active stream stalls, encounters an unrecoverable network error, or buffer-underruns:
- Catch the error event (e.g.
Hls.Events.ERRORwith fatal flag). - Pop the next candidate from your fallback queue.
- Re-attach the media source without disrupting the user's viewing session.
3. Decouple Subtitles from Video Playback
Subtitles are retrieved independently from the video stream. Never block initial video buffering waiting for subtitle tracks to download. Load the video stream immediately and inject subtitle tracks (<track kind="subtitles">) asynchronously once resolved.
4. Implement a Client-Side Timeout
Always set a client-side watchdog timer (typically 15 to 20 seconds). If no viable candidate emits a valid video stream before the timer expires, render an informative empty state allowing the user to retry or switch servers.
5. Clean Up on Title Switch
When a viewer selects a new movie or episode:
- Call
eventSource.close()on the active SSE connection. - Invoke
hls.destroy()orvideo.removeAttribute('src'). - Clear pending retry timers and abort in-flight fetch requests.
Reference Implementation: Minimal Web Player
Below is a complete, copyable implementation using native HTML5 and HLS.js:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Vyla Custom Player</title>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<style>
video { width: 100%; max-width: 960px; height: auto; background: #000; border-radius: 8px; }
.status { font-family: sans-serif; margin-top: 8px; color: #666; }
</style>
</head>
<body>
<video id="vyla-video" controls playsinline></video>
<div id="status" class="status">Connecting to Stream API...</div>
<script>
const API_BASE = "https://your-stream-api.example.com";
const TMDB_ID = 155;
const video = document.getElementById("vyla-video");
const statusEl = document.getElementById("status");
const fallbackSources = [];
let isPlaying = false;
let hlsInstance = null;
const eventSource = new EventSource(`${API_BASE}/movie?id=${TMDB_ID}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "meta") {
statusEl.textContent = `Found: ${data.meta.title} (${data.meta.year})`;
if (data.subtitles) loadSubtitles(data.subtitles);
}
else if (data.type === "source") {
if (!isPlaying) {
playCandidate(data);
} else {
fallbackSources.push(data);
}
}
else if (data.type === "done") {
eventSource.close();
if (!isPlaying && fallbackSources.length === 0) {
statusEl.textContent = "No compatible streaming sources found.";
}
}
};
eventSource.onerror = () => {
eventSource.close();
if (!isPlaying) statusEl.textContent = "Error connecting to Stream API.";
};
function playCandidate(candidate) {
isPlaying = true;
statusEl.textContent = `Streaming from provider: ${candidate.source} (${candidate.quality})`;
if (candidate.type === "hls") {
if (Hls.isSupported()) {
if (hlsInstance) hlsInstance.destroy();
hlsInstance = new Hls({ maxBufferLength: 30 });
hlsInstance.loadSource(candidate.url);
hlsInstance.attachMedia(video);
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => video.play());
hlsInstance.on(Hls.Events.ERROR, (event, errorData) => {
if (errorData.fatal) {
console.warn("HLS fatal error encountered. Switching to fallback candidate...");
hlsInstance.destroy();
isPlaying = false;
if (fallbackSources.length > 0) playCandidate(fallbackSources.shift());
}
});
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = candidate.url;
video.play();
}
} else {
video.src = candidate.url;
video.play();
}
}
function loadSubtitles(subtitles) {
subtitles.forEach((sub) => {
const track = document.createElement("track");
track.kind = "subtitles";
track.label = sub.label;
track.srclang = sub.label.slice(0, 2).toLowerCase();
track.src = sub.file;
video.appendChild(track);
});
}
</script>
</body>
</html>
Next Steps
- Explore the complete Client Integration Guide for React, Vue, and ExoPlayer examples.
- Review error classification in the Error Handling Guide.
- Review the SSE event protocol in Paths & Playback Events.