Skip to content

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.

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:

  1. A /proxy endpoint becomes active.
  2. Every sourceUrl in anime /stream responses and every URL in imageUrls for manga /stream responses 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.
  3. Every subtitle url returned in /stream or /tracks responses is rewritten through /proxy too, with a ct=text/vtt hint so the proxy forces the right Content-Type even when the upstream CDN serves application/octet-stream.

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.

GET /proxy?url=<encoded-url>[&h=<base64-headers>][&ct=<content-type>]
ParamRequiredDescription
urlURL-encoded target URL to fetch
hBase64-encoded JSON of request headers to forward upstream
ctOverride 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).

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 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.

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.

All /proxy responses include:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-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.

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.