PC Caster solves a specific problem — HLS stream CDNs return HTTP 403 to anything that isn’t a real browser. Two conditions trigger the block: a missing or wrongDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/iluisgm/PC_Caster/llms.txt
Use this file to discover all available pages before exploring further.
Referer header, and a non-Chrome TLS fingerprint. Pasting the raw .m3u8 URL into VLC or a Roku always fails for exactly these reasons. The solution is a three-stage pipeline that runs entirely on your Windows PC, intercepting the stream URL before it leaves the browser, re-serving it with the right credentials, and pushing it to the Roku over plain HTTP the TV can actually read.
Pipeline overview
Stage 1 — Stream detection (stream_finder.py)
stream_finder.py tries two strategies in sequence. Strategy 1 (_scrape_html) fetches the raw page HTML with requests and applies a compiled regex (M3U8_RE) to find any .m3u8 URL sitting literally in the page source. This is fast and costs nothing, but most live-streaming sites build the stream URL dynamically in JavaScript and never expose it in the HTML — so the scrape typically finds nothing and Strategy 2 takes over.
Strategy 2 (_sniff_browser) launches a headless Chromium browser via Playwright and attaches a request listener with page.on('request', _record) to the main page and every new tab. The interactive variant (find_streams_interactive) opens a visible browser instead and lets the user click the server link themselves.
Every intercepted request is checked with _is_m3u8(url), which looks for .m3u8 in the URL path portion only — before the ? — so tokenised query strings don’t cause false negatives or false positives. When a match is found, the listener records the Referer and Origin headers the browser sent with that request and emits a result dict:
stream_finder.py
.m3u8 request, PC Caster captures it and shows it in the scanner modal. Ad and popup tabs are automatically closed in the background loop so they don’t bury the real player window.
Stage 2 — Local HLS proxy (hls_proxy.py)
The proxy is a ThreadingHTTPServer bound to 0.0.0.0:8011. It starts lazily the first time a cast is initiated. When a device requests a URL like:
hls_proxy.py
creq is curl_cffi.requests — a Python bindings layer on top of libcurl compiled with BoringSSL. The impersonate='chrome' argument makes libcurl reproduce Chrome’s exact TLS ClientHello and JA3 fingerprint. The proxy also injects the captured Referer and derives Origin from it before forwarding.
If the response is an M3U8 playlist (detected by #EXTM3U magic bytes, mpegurl in the Content-Type, or a .m3u8 path), every child URI in the playlist — variant streams, segment paths, EXT-X-KEY URIs — is rewritten to point back through the proxy. This ensures every downstream request the Roku makes also carries the browser credentials.
Stage 3 — Roku channel control (roku_deploy.py + BrightScript)
PC Caster ships a minimal BrightScript SceneGraph channel called PC Caster that lives in the roku_receiver/ directory. On first cast, roku_deploy.sideload() zips the channel and POSTs it to the Roku’s developer web server:
contentId parameter carries the full proxy URL (e.g. http://192.168.1.50:8011/p.m3u8?u=...&r=...). While the channel is already running, a new stream can be pushed instantly without relaunching:
roInputEvent, reads contentId from the event info, and calls video.control = "play" on a new ContentNode.
Why curl_cffi is required
Python’s built-in ssl module (via urllib3 / requests) has a distinctive JA3 fingerprint that CDNs use as a blocklist signal. The TLS ClientHello from a plain Python process is missing the GREASE values, specific cipher suite ordering, and extension types that real Chrome sends — CDNs detect this pattern and return 403 before the HTTP layer is even reached.
curl_cffi uses libcurl with BoringSSL to reproduce Chrome’s complete TLS handshake, including cipher suites, elliptic curves, and GREASE pseudo-values. This is the only change that causes the CDN to serve the stream instead of blocking it.
SSDP device discovery
PC Caster finds Roku devices on the LAN automatically, without requiring the user to know the IP address. It sends a UDP M-SEARCH to the multicast address239.255.255.250:1900 with two search targets:
pc_caster.py
Location: header. The IP is extracted from that header with a regex, then a friendly name is fetched from the Roku’s ECP device-info endpoint:
user-device-name (or model-name as a fallback) becomes the display label in the device list.
The proxy only handles M3U8 and MPEG-TS content. It does not support DASH (MPD) streams.
