Stream Proxy
Most streaming servers block cross-origin requests and require custom Referer or User-Agent headers. Browsers can’t set these freely, so direct playback from a web app fails. The built-in proxy solves this by fetching video data server-side and forwarding it to the browser with full CORS headers.
Enabling the proxy
Section titled “Enabling the proxy”Pass proxy: true to startServer:
import { HttpClient, GogoanimeProvider, startServer } from 'anime-sdk';
startServer({ providers: [new GogoanimeProvider(new HttpClient())], port: 3000, proxy: true,});That’s it. Three things happen automatically:
- A
/proxyendpoint becomes active. - Every
sourceUrlin anime/streamresponses and every URL inimageUrlsfor manga/streamresponses is rewritten to go through/proxy, with the required headers encoded in the URL. Your frontend uses these URLs as-is: no header forwarding needed. - Every subtitle
urlreturned in/streamor/tracksresponses is rewritten through/proxytoo, with act=text/vtthint so the proxy forces the rightContent-Typeeven when the upstream CDN servesapplication/octet-stream.
How stream URLs are rewritten
Section titled “How stream URLs are rewritten”Without proxy (proxy: false, default):
{ "sourceUrl": "https://cdn.example.com/episode1.m3u8", "isHLS": true, "headers": { "Referer": "https://provider.com/watch/episode-1" }}With proxy (proxy: true):
{ "sourceUrl": "http://localhost:3000/proxy?url=https%3A%2F%2Fcdn.example.com%2Fepisode1.m3u8&h=eyJSZWZlcmVyIjoiaHR0cHM6Ly9wcm92aWRlci5jb20vd2F0Y2gvZXBpc29kZS0xIn0%3D", "isHLS": true, "headers": { "Referer": "https://provider.com/watch/episode-1" }}The h param is the base64-encoded JSON of the original headers object. The proxy decodes it and includes those headers in the upstream request.
The /proxy endpoint
Section titled “The /proxy endpoint”GET /proxy?url=<encoded-url>[&h=<base64-headers>][&ct=<content-type>]| Param | Required | Description |
|---|---|---|
url | ✓ | URL-encoded target URL to fetch |
h | Base64-encoded JSON of request headers to forward upstream | |
ct | Override the response Content-Type (used for text/vtt on subtitles) |
The response includes Access-Control-Allow-Origin: * and passes through the upstream body. The Content-Type is the upstream value unless ct is set (or the proxy detects an HLS manifest, in which case it serves application/vnd.apple.mpegurl).
HLS manifest rewriting
Section titled “HLS manifest rewriting”When the upstream response is an HLS manifest (.m3u8 or Content-Type: application/vnd.apple.mpegurl), the proxy rewrites every URI in it: segment URLs, sub-playlist URLs, #EXT-X-KEY encryption key URLs, #EXT-X-MAP initialization URIs: to also go through /proxy. The h param is preserved on every rewritten URI, so the full header chain propagates automatically.
This means an HLS player (e.g. hls.js) only needs to load the proxy URL for the master playlist. All subsequent requests are handled transparently.
Subtitle proxying
Section titled “Subtitle proxying”Subtitle URLs returned in IVideoPayload.subtitles and IUnitTracks.subtitles are rewritten the same way, with ct=text/vtt appended for VTT tracks. That ensures the browser parses the response as WebVTT even when the upstream CDN claims application/octet-stream (common with anizara/megastatics/etc.). Hand the rewritten URL straight to a <track> element: no crossorigin shenanigans needed beyond setting crossOrigin="anonymous" on the <video>:
<video crossOrigin="anonymous" controls> {stream.subtitles?.map((t) => ( <track key={t.url} kind="subtitles" src={t.url} srcLang={t.language} label={t.label} /> ))}</video>If you’re constructing subtitle URLs by hand outside of startServer, the SDK exports proxifySubtitleUrl(proxyBase, track, opts?) that does this encoding for you.
Playing streams in the browser
Section titled “Playing streams in the browser”With proxy enabled, sourceUrl is a plain URL the browser can fetch without any extra configuration:
import Hls from 'hls.js';
function Player({ sourceUrl, isHLS }: { sourceUrl: string; isHLS: boolean }) { const ref = useRef<HTMLVideoElement>(null);
useEffect(() => { const v = ref.current!; if (isHLS && Hls.isSupported()) { const hls = new Hls(); hls.loadSource(sourceUrl); // proxy URL: just works hls.attachMedia(v); return () => hls.destroy(); } v.src = sourceUrl; }, [sourceUrl, isHLS]);
return <video ref={ref} controls />;}No xhrSetup, no custom headers, no CORS workarounds.
Response headers
Section titled “Response headers”All /proxy responses include:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, OPTIONSAccess-Control-Allow-Headers: *All other API routes (/search, /content, /stream, /tracks) also include these headers, so you can call the server directly from any browser origin without a reverse proxy in front.
Fetching the proxy URL manually
Section titled “Fetching the proxy URL manually”If you’re building a custom client you can construct proxy URLs yourself:
const headers = { Referer: 'https://provider.com/watch/episode-1' };const h = btoa(JSON.stringify(headers));const proxyUrl = `http://localhost:3000/proxy?url=${encodeURIComponent(streamUrl)}&h=${encodeURIComponent(h)}`;Or use the server’s rewritten sourceUrl directly: it already has everything encoded.