mirror of
https://gitlab.com/vyla-entertainment/documentation.git
synced 2026-11-15 06:57:21 -05:00
4.7 KiB
4.7 KiB
Building a Custom Player
Architectural principles, format handling, and engineering guidelines for building video players backed by Vyla infrastructure.
Overview
Vyla handles discovery, scraping, and proxying; your app owns the player. Whether building a web player, desktop shell, or mobile UI, this guide outlines the core playback patterns.
Integration Paths
- Node.js Backends: Use the Vyla SDK directly in-process via
sdk.getStream(). Ideal for custom authentication and server-side tracking. - Web / Mobile / TV: Connect to the Stream API gateway via HTTP/SSE (
/movieor/tv). Ideal for frontend players requiring a live streaming feed.
Playback Formats
- HLS Streams (
type: "hls"): Adaptive master playlists (.m3u8). Proxied via Stream API (/api?url=...), which rewrites chunk URIs and AES encryption keys with permissive CORS headers. Handled via HLS.js or native Safari. - Direct Video (
type: "mp4"): Direct MP4 or MKV files. Proxied with HTTP Range forwarding (206 Partial Content) for scrubbing.
Player Guidelines
- Start on First Source: Begin playback as soon as the first
sourceevent arrives (3–8s). Queue later events as fallbacks. - Auto-Failover: If the current stream fails, catch fatal playback errors and immediately switch to the next queued source.
- Async Subtitles: Fetch subtitles in the background without blocking video buffering.
- Watchdog Timer: If no working source resolves within 15–20s, show an empty state with a retry option.
- Clean Teardown: When switching media, close the SSE connection and destroy the HLS instance before opening a new stream.
Minimal Web Player Example
<!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://api.vyla.cc";
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) {
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>