A local HTTP proxy for Dart/Flutter apps. Point it at any URL or file path, attach custom headers, and get back a local URL you can hand to a video player or a cast device (Chromecast, DLNA, AirPlay, etc.).
- Remote HLS manifests (
.m3u8) are rewritten so their child playlists, segments and keys are fetched through the proxy too, carrying the same headers — useful when the player/cast device can't set headers itself. - Everything else (mp4, images, any file) is streamed through with full range/seek support.
- Per-source headers travel inside an opaque token, so many streams can be proxied at once without clobbering each other.
Cast devices and many native players can't attach Referer, User-Agent or
cookies to their requests, and CDNs often require them. reproxy sits on the
device, fetches the real resource with the right headers, and exposes a plain
local URL that any player can consume.
player / cast device ──▶ http://192.168.1.5:52345/proxy?token=…
│ (adds your headers)
▼
real CDN / local file
dependencies:
reproxy:
path: ../reproxy # or a git/pub referenceimport 'package:reproxy/reproxy.dart';final proxy = ReproxyServer();
// Start listening. Both arguments are optional:
// port -> defaults to 52345 (use 0 for an OS-assigned port)
// address -> defaults to the device LAN IP (deviceIp())
await proxy.start();
print(proxy.baseUrl); // http://192.168.1.5:52345
// Remote stream that needs headers:
final hls = proxy.proxyUrl(
source: 'https://host/master.m3u8',
headers: {
'Referer': 'https://host',
'User-Agent': 'Mozilla/5.0',
},
);
// Local file (served with range support, no headers needed):
final file = proxy.proxyUrl(source: '/storage/emulated/0/video.mp4');
// Hand `hls` / `file` to your player or cast device.
// player.setUrl(hls);
// When you're done:
await proxy.stop();proxyUrl works the same whether source is a http(s):// URL or an absolute
file path — reproxy detects which it is.
await proxy.start(port: 8080); // fixed port
await proxy.start(port: 0); // any free port
await proxy.start(address: '0.0.0.0'); // all interfaces
await proxy.start(address: '192.168.1.5', port: 52345);Need the device IP yourself? deviceIp() is exported.
| Member | Description |
|---|---|
ReproxyServer() |
Create an instance (start as many as you like). |
start({int port = 52345, String? address}) |
Bind and listen. port: 0 = OS-assigned; address defaults to deviceIp(). |
proxyUrl({required String source, Map<String,String> headers = const {}}) |
Local URL for a remote URL or file path, embedding headers. |
baseUrl |
http://<address>:<port> (throws if not started). |
isRunning |
Whether the server is listening. |
stop() |
Stop listening and release resources. |
deviceIp() |
The device's primary LAN IPv4 (top-level function). |
The repo ships a quick manual checker, example/try_url.dart:
# A URL
dart run example/try_url.dart 'https://host/master.m3u8?token=…'
# A URL with headers (Name:value pairs)
dart run example/try_url.dart 'https://host/master.m3u8' \
'Referer:https://host' 'User-Agent:Mozilla/5.0'
# A local file
dart run example/try_url.dart '/path/to/video.mp4'It starts the proxy on 127.0.0.1, prints the proxy URL and status, and for HLS
shows the rewritten manifest and follows the first child to confirm the chain
works end to end.
To check playback (not just the fetch), copy the printed proxy URL into a player:
ffplay "http://127.0.0.1:PORT/proxy?token=…"
# VLC: Open Network Stream → paste the URL- The proxy exposes a single route,
GET|HEAD /proxy?token=…. - The token is
base64url(json({target, headers}))— no server-side state, so concurrent streams never share/overwrite headers. - Remote, HLS (
content-typecontainsmpegurlor the path ends in.m3u8/.m3u): the manifest is fetched, and every child reference (EXT-X-MEDIA,EXT-X-KEY,EXT-X-MAP, variant playlists, segments, …) is resolved withUri.resolveand rewritten to/proxy?token=…carrying the same headers. Handles relative,../, root-relative and absolute references. - Remote, other: streamed straight through.
Range,If-Range,If-Modified-SinceandIf-None-Matchare forwarded so seeking and caching keep working; hop-by-hop and stalecontent-encoding/content-lengthheaders are cleaned up. - Local files: served with range support (
206 Partial Content), correct MIME type andHEADhandling. - Permissive CORS is added to every response (including the range headers players need).
- Manifests are returned as UTF-8 bytes (the spec for
.m3u8). If a tool shows mojibake, it's the tool decoding as latin1 — the bytes on the wire are fine. - Signed/expiring URLs work as long as the signature is valid for the proxy's outgoing request (some CDNs bind the signature to a client IP).
- Local
.m3u8files are served as-is (no rewriting of on-disk manifests).