Node.js resolver and local player for 111movies.net HLS streams. Decodes the site’s encrypted player in a sandboxed VM, races mirror endpoints in parallel, and serves a local HLS relay for playback.
Zero npm dependencies — Node.js built-ins only.
git clone https://github.com/sharoon7171/111movies-stream-solver.git
cd 111movies-stream-solver
npm startOpen http://127.0.0.1:8788, enter a TMDB movie or TV ID, and click Resolve.
| Route | Example |
|---|---|
| Movie | /movie/533535 |
| TV | /tv/44217/1/1 |
| Variable | Default | Purpose |
|---|---|---|
PORT |
8788 |
Listen port |
MOVIES111_ORIGIN |
https://111movies.net |
Upstream site origin |
Requires Node.js 20.10+ or 22+ (fetch, AbortSignal.timeout).
111movies does not publish a plain m3u8 link in page HTML. Playback is gated behind an obfuscated client player that decrypts a Next.js page payload, calls an internal API, and returns a list of mirror servers. Each mirror resolves to a CDN-hosted HLS manifest.
The resolver reproduces that client logic entirely in Node.js:
- Page fetch — load
/movie/{id}or/tv/{id}/{season}/{episode}from 111movies (or pass_datato skip this step when the encrypted blob is already available). - VM decode — parse
__NEXT_DATA__, run the site player bytecode inside a sandboxedvmwith React stubs and vendored webpack crypto. The VM performs the same{apiPrefix}/srrequest the browser player would, yielding the mirror list and required request headers. - Mirror race — probe every mirror concurrently (7s timeout each). The first success starts playback immediately; remaining mirrors arrive as NDJSON events so the UI can switch servers without re-resolving.
Each mirror is fetched at:
{ORIGIN}/{apiPrefix base}/w/{server.data} → { url, ... }
flowchart TB
START(["POST /api/play<br/>contentPath + type"]) --> E1["event: decoding"]
E1 --> FETCH{"_data<br/>provided?"}
FETCH -->|"no"| PAGE["GET /movie/{id}<br/>or /tv/{id}/s/e"]
FETCH -->|"yes"| OPT["buildPlayerOption"]
PAGE --> NEXT["parse __NEXT_DATA__"]
NEXT --> OPT
OPT --> VM["runPlayerVm<br/>bytecode + React stubs"]
VM --> SR["GET {apiPrefix}/sr"]
SR --> META["apiPrefix · servers[] · headers"]
META --> E2["event: decoded"]
E2 --> POOL["pool — all mirrors at once"]
POOL --> M1["Alpha"]
POOL --> M2["NgFlix"]
POOL --> M3["…"]
M1 --> API1["GET {base}/w/{server.data}"]
M2 --> API2["GET {base}/w/{server.data}"]
M3 --> API3["GET {base}/w/{server.data}"]
API1 --> OUT["{ url }"]
API2 --> OUT
API3 --> OUT
OUT --> E3["event: server<br/>streamUrl · referer · browserUrl?"]
E3 --> FIRST["first hit → playback starts"]
E3 --> REST["remaining hits → server switch"]
REST --> E4["event: done<br/>streams map"]
FIRST --> E4
Upstream CDNs enforce Referer and Origin headers tied to 111movies. Browsers and most external apps cannot attach those headers on cross-origin CDN requests, so a raw m3u8 URL returns 403 when loaded directly without a relay.
The relay adds those headers on every upstream fetch and returns segments as video/mp2t. Routing is split by CDN host in relay/charlie.js and relay/ngflix.js:
| CDN | Host | Manifest handling |
|---|---|---|
| Charlie | *.nexlunar99.site |
Root-relative segment paths rewritten to /api/hls/p/{token}/… |
| NgFlix | *.workers.dev |
Directory-relative segments; query entry redirects to path URL (~108 KB manifest) |
Shared fetch helpers live in relay/shared.js. relay/hls.js picks the handler from the upstream URL or path token.
| Playback target | URL | Referer |
|---|---|---|
| Browser, Stremio, Kodi, IINA | Browser URL from the UI | Handled by proxy |
| VLC, MPV | Direct URL + CLI flag | --http-referrer / --referrer |
NgFlix resolves with a path Browser URL (/api/hls/p/{token}/index.m3u8). Other mirrors use /api/hls?url=…. VLC and MPV export the upstream m3u8 with a ready-made referer flag — no localhost relay needed during playback.
flowchart TB
RES["streamUrl · referer · browserUrl?<br/>from /api/play"]
subgraph Proxied["Proxied — Browser URL"]
direction TB
P1["HLS.js · Stremio · Kodi · IINA"]
P2["GET /api/hls?url=…<br/>or /api/hls/p/{token}/…"]
P3["charlie.js · ngflix.js<br/>Referer + Origin"]
P4["CDN"]
P1 --> P2 --> P3 --> P4
P4 --> P3 --> P1
end
subgraph Direct["Direct — VLC · MPV export"]
direction TB
D1["VLC / MPV"]
D2["upstream m3u8"]
D3["--http-referrer / --referrer"]
D1 --> D3 --> D2 --> D1
end
RES --> P2
RES --> D2
The server is a single Node.js process with no npm dependencies. api/handler.js routes requests; resolver/ owns the decode and mirror pipeline; player/ runs the site VM; relay/ proxies HLS back to the CDN with the correct headers.
flowchart TB
subgraph Client["Client"]
UI["public/<br/>index.html · player.js · HLS.js"]
EXT["External players<br/>Stremio · Kodi · VLC · MPV"]
end
subgraph Server["Node server · :8788"]
direction TB
HND["api/handler.js"]
subgraph Resolve["resolver/"]
STR["stream.js<br/>NDJSON pipeline"]
DEC["decode.js"]
MIR["mirrors.js + pool.js<br/>parallel probe · 7s timeout"]
end
subgraph VM["player/"]
RUN["run.js + bytecode.js"]
PAGE["page.js"]
CRY["site-crypto.js"]
end
subgraph Vendor["vendor/chunks/"]
VEND["webpack crypto"]
end
subgraph Relay["relay/"]
RHLS["hls.js<br/>handler router"]
RSH["shared.js<br/>referer · fetch · relay"]
RCH["charlie.js"]
RNG["ngflix.js"]
end
CFG["config/site.js<br/>ORIGIN · CDN_REFERER · UA"]
HND --> STR
HND --> RHLS
STR --> DEC
STR --> MIR
DEC --> PAGE
DEC --> RUN
RUN --> CRY
RHLS --> RSH
RHLS --> RCH
RHLS --> RNG
RCH --> RSH
RNG --> RSH
DEC --> CFG
MIR --> CFG
RSH --> CFG
CRY --> VEND
end
subgraph Upstream["Upstream"]
SITE["111movies.net<br/>pages · /sr · mirrors"]
CDN["CDN<br/>m3u8 · segments"]
end
UI -->|"POST /api/play"| HND
UI -->|"GET /api/hls<br/>GET /api/hls/p/…"| HND
EXT -->|"Browser URL"| HND
EXT -.->|"Direct URL + referer<br/>VLC · MPV only"| CDN
DEC --> SITE
MIR --> SITE
RSH --> CDN
src/
server.mjs HTTP entry
config/site.js ORIGIN, CDN_REFERER, USER_AGENT
api/handler.js /api/play, /api/hls, /api/hls/p, static routes
http/static.js public/ assets + embed paths
resolver/
decode.js page fetch + VM orchestration
stream.js NDJSON play pipeline
mirrors.js parallel mirror probe
pool.js concurrent task pool
relay/
hls.js CDN handler router
shared.js referer headers, manifest fetch, segment relay
charlie.js nexlunar99.site relay
ngflix.js workers.dev relay
player/
run.js VM sandbox + bytecode runner
page.js __NEXT_DATA__ parse + option builder
site-crypto.js vendored webpack crypto loader
react-stubs.js minimal React runtime stubs
bytecode.js extracted site player VM body
public/
index.html resolver UI
player.js HLS.js player + export helpers
style.css
vendor/chunks/ vendored site webpack chunks
NDJSON stream — one JSON object per line.
{ "contentPath": "/movie/533535", "type": "movie", "id": "533535" }{ "contentPath": "/tv/44217/1/1", "type": "tv", "id": "44217" }Optional: _data, backdrop, ad, query — skip page fetch when encrypted payload is already available.
| Event | Payload |
|---|---|
decoding |
— |
decoded |
contentPath, type, apiPrefix, servers[] (name only) |
server |
server: { name, ok, ms, streamUrl, referer, browserUrl? } |
done |
ok, contentPath, type, apiPrefix, streams |
error |
stage, error |
streams — map of mirror name → { streamUrl, referer, browserUrl? }. browserUrl is set for NgFlix (*.workers.dev) path proxy URLs.
curl -N -X POST http://127.0.0.1:8788/api/play \
-H 'Content-Type: application/json' \
-d '{"contentPath":"/movie/533535","type":"movie"}'Proxies an upstream playlist or segment with Referer and Origin. NgFlix playlists redirect to the path URL below; Charlie playlists are rewritten inline.
Path-based proxy. {token} is base64url of the upstream manifest directory; {file} is the manifest or segment name. NgFlix keeps relative segment lines in the manifest; Charlie serves rewritten segment URLs.
This project is provided for educational and research purposes only. It demonstrates how client-side stream protection, encrypted payloads, and CDN access controls work in modern web video pipelines.
You are solely responsible for how you use this software. Only access content you have the legal right to view. The authors do not host, distribute, or endorse copyrighted material, and this tool must not be used to circumvent licensing or terms of service unlawfully.