Before You Start
You’ll need a TMDB ID for the content you want to stream. Every movie and TV show on themoviedb.org has one — it’s the number in the URL.| URL | TMDB ID | Title |
|---|---|---|
themoviedb.org/movie/550 | 550 | Fight Club |
themoviedb.org/movie/27205 | 27205 | Inception |
themoviedb.org/tv/1396 | 1396 | Breaking Bad |
themoviedb.org/tv/94997 | 94997 | House of the Dragon |
Step 1 — Open the SSE Stream
The/movie and /tv endpoints are Server-Sent Events — not JSON. Open a streaming connection and handle three event types as they arrive: meta, source, and done.
curl -N "https://missourimonster-vyla.hf.space/movie?id=550"
Example SSE stream output
Example SSE stream output
data: {"type":"meta","meta":{"id":550,"title":"Fight Club","release_date":"1999-10-15","runtime":139},"subtitles":[{"label":"English","file":"https://sub.vdrk.site/v1/vtt/movie/550/English.vtt","type":"vtt","source":"v1"},{"label":"Spanish","file":"https://sub.vdrk.site/v1/vtt/movie/550/Spanish.vtt","type":"vtt","source":"v1"}]}
data: {"type":"source","source":{"source":"provider-a","label":"Provider A","url":"https://missourimonster-vyla.hf.space/api?url=...&pa=1"}}
data: {"type":"source","source":{"source":"provider-b","label":"Provider B","url":"https://missourimonster-vyla.hf.space/api?url=...&pb=1"}}
data: {"type":"done","total":2}
Step 2 — Handle Events and Play
Parse eachdata: line as JSON and act on the type field. Start playback on the first source event — don’t wait for done.
- Vanilla JS
- React
- Next.js
- Python
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js"></script>
</head>
<body>
<video id="player" controls style="width:100%"></video>
<div id="status">Connecting…</div>
<script>
const BASE = 'https://missourimonster-vyla.hf.space';
const video = document.getElementById('player');
const status = document.getElementById('status');
let hls;
let sources = [];
let started = false;
function playSource(url) {
hls?.destroy();
if (!Hls.isSupported()) { video.src = url; return; }
hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
status.textContent = 'Playing';
video.play();
});
hls.on(Hls.Events.ERROR, (_, err) => {
if (err.fatal) {
const next = sources.find(s => s.url !== url);
if (next) { status.textContent = `Trying ${next.label}…`; playSource(next.url); }
else status.textContent = 'All sources failed.';
}
});
}
async function loadMovie(tmdbId) {
const res = await fetch(`${BASE}/movie?id=${tmdbId}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.type === 'meta') {
status.textContent = `Loading ${event.meta?.title ?? ''}…`;
event.subtitles?.forEach((sub, i) => {
const track = document.createElement('track');
track.kind = 'subtitles';
track.label = sub.label;
track.src = sub.file;
track.default = i === 0;
video.appendChild(track);
});
}
if (event.type === 'source') {
sources.push(event.source);
if (!started) { started = true; playSource(event.source.url); }
}
if (event.type === 'done' && !started) {
status.textContent = 'No sources available.';
}
}
}
}
loadMovie(550);
</script>
</body>
</html>
import { useEffect, useRef, useState } from 'react';
import Hls from 'hls.js';
const BASE = 'https://missourimonster-vyla.hf.space';
export function Player({ tmdbId }: { tmdbId: number }) {
const videoRef = useRef<HTMLVideoElement>(null);
const hlsRef = useRef<Hls | null>(null);
const sourcesRef = useRef<string[]>([]);
const [status, setStatus] = useState('Connecting…');
function playUrl(url: string) {
if (!videoRef.current) return;
hlsRef.current?.destroy();
if (!Hls.isSupported()) { videoRef.current.src = url; return; }
const hls = new Hls();
hlsRef.current = hls;
hls.loadSource(url);
hls.attachMedia(videoRef.current);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
setStatus('Playing');
videoRef.current?.play();
});
hls.on(Hls.Events.ERROR, (_, err) => {
if (err.fatal) {
const next = sourcesRef.current.find(u => u !== url);
if (next) { setStatus('Trying next source…'); playUrl(next); }
else setStatus('All sources failed.');
}
});
}
useEffect(() => {
let started = false;
(async () => {
const res = await fetch(`${BASE}/movie?id=${tmdbId}`);
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop()!;
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.type === 'source') {
sourcesRef.current.push(event.source.url);
if (!started) { started = true; playUrl(event.source.url); }
}
if (event.type === 'done' && !started) setStatus('No sources available.');
}
}
})();
return () => hlsRef.current?.destroy();
}, [tmdbId]);
return (
<>
<video ref={videoRef} controls style={{ width: '100%' }} />
<p>{status}</p>
</>
);
}
'use client';
import { useEffect, useRef } from 'react';
const BASE = 'https://missourimonster-vyla.hf.space';
export default function Player({ tmdbId }: { tmdbId: number }) {
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
let started = false;
import('hls.js').then(({ default: Hls }) => {
if (!Hls.isSupported() || !videoRef.current) return;
const hls = new Hls();
(async () => {
const res = await fetch(`${BASE}/movie?id=${tmdbId}`);
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop()!;
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.type === 'source' && !started) {
started = true;
hls.loadSource(event.source.url);
hls.attachMedia(videoRef.current!);
}
}
}
})();
return () => hls.destroy();
});
}, [tmdbId]);
return <video ref={videoRef} controls className="w-full" />;
}
import requests, json
BASE = 'https://missourimonster-vyla.hf.space'
def stream_movie(tmdb_id: int):
sources = []
subtitles = []
with requests.get(f'{BASE}/movie', params={'id': tmdb_id}, stream=True) as res:
for line in res.iter_lines():
if not line or not line.startswith(b'data: '):
continue
event = json.loads(line[6:])
if event['type'] == 'meta':
subtitles = event.get('subtitles', [])
print('Title:', event['meta'].get('title'))
elif event['type'] == 'source':
sources.append(event['source'])
print(f" + {event['source']['label']}: {event['source']['url']}")
elif event['type'] == 'done':
print(f"Done. {event['total']} sources.")
break
return sources, subtitles
sources, subtitles = stream_movie(550)
Step 3 — Build a Fallback Queue
Sources stream in order of speed. Load the first one immediately and queue the rest — if one dies mid-stream, try the next.const BASE = 'https://missourimonster-vyla.hf.space';
async function streamWithFallback(
videoEl: HTMLVideoElement,
tmdbId: number,
season?: number,
episode?: number
) {
const endpoint = season && episode
? `${BASE}/tv?id=${tmdbId}&season=${season}&episode=${episode}`
: `${BASE}/movie?id=${tmdbId}`;
const queue: string[] = [];
let started = false;
let hls: any;
function tryNext() {
if (!queue.length) return;
hls?.destroy();
hls = new Hls();
const url = queue.shift()!;
hls.loadSource(url);
hls.attachMedia(videoEl);
hls.on(Hls.Events.MANIFEST_PARSED, () => videoEl.play());
hls.on(Hls.Events.ERROR, (_: any, err: any) => {
if (err.fatal) tryNext();
});
}
const res = await fetch(endpoint);
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop()!;
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.type === 'source') {
queue.push(event.source.url);
if (!started) { started = true; tryNext(); }
}
if (event.type === 'done' && !started) {
throw new Error('No sources available');
}
}
}
}
Step 4 — Add Subtitles
Subtitles arrive in themeta event, before any source. Attach them to your video element immediately.
// In your meta event handler:
if (event.type === 'meta') {
event.subtitles.forEach((sub, i) => {
const track = document.createElement('track');
track.kind = 'subtitles';
track.label = sub.label;
track.src = sub.file;
track.default = i === 0;
videoEl.appendChild(track);
});
}
// Or fetch subtitles standalone (no streaming needed):
const subtitles = await fetch(
'https://missourimonster-vyla.hf.space/subtitles/movie/550'
).then(r => r.json());
Check Provider Health
Before building, verify which providers are currently live:curl https://missourimonster-vyla.hf.space/health
{
"status": "ok",
"timestamp": "2025-01-01T00:00:00.000Z",
"tmdb": true,
"cache": 0,
"probe_id": "155",
"sources": {
"provider-a": { "ok": true, "ms": 1204 },
"provider-b": { "ok": true, "ms": 980 },
"provider-c": { "ok": false, "ms": 435 }
}
}
status: "degraded" means at least one provider is down — the rest still work and your fallback queue handles it automatically.
Debug a Single Provider
Use/test to isolate a specific provider without running the full fanout:
curl "https://missourimonster-vyla.hf.space/test/550?source=<provider-key>"
Available provider keys are listed in the
/health response under the sources field.Full API Reference →
See complete SSE event docs for every endpoint.

