animekai.be fronted two server backends per episode: megaplay.buzz
(same player/CDN family as hianime — 403s our egress IP) and
my.1anime.site (direct progressive MP4, independent CDN — verified
serving our IP at ~2.4MB/s). This is the rescue path around the
MegaPlay Cloudflare block, per the ani-cli #1945/#1918 work.
- providers/animekai.py: standard interface, per-episode backend
dispatch (1anime first, then megaplay), same listed-but-dead ->
raise semantics as hianime. All endpoints ride the shared TTL cache
(no re-fetching on repeat UI hits).
- upstream.py: megaplay_master() moved out of hianime — the MegaPlay
chain (embed -> data-id -> getSourcesNew -> enc decrypt -> CDN proof)
is now shared by every provider fronting that player.
- app.py: /stream forwards Range for progressive files (206 +
Content-Range passthrough, partial bodies never LRU-cached, 8MB cap
on the parallel path so a 250MB MP4 can't pin RAM). api_stream
reports hls=true/false from the URL — the player kind is a property
of the URL, so future stream types slot in without app changes.
- app.js: hls===false -> native <video> (existing Range-seeking),
hls.js path untouched.
- run.sh: ANI_SOURCE=animekai,hianime,anidb,anikoto (revert to the old
chain when the MegaPlay block lifts).
- tests: test_animekai.py (offline: interface, enc decrypt, /stream
Range via monkeypatched network) + test_animekai_live.py (opt-in
ANI_LIVE=1 end-to-end, skipped by default so the suite never touches
animekai.be).
- CDN probe no longer downloads 64KB of video (or even a variant
playlist): one small master-playlist fetch proves reachability.
State stays OK/DEGRADED, kbps is no longer measured, and the pill/
panel only show a rate when one exists.
- hianime: 5x getSourcesNew re-roll -> 1 attempt. The enc payload is
deterministic per data-id (observed identical across calls), so
re-rolling only hammered the provider.
- anikoto: 2x re-roll -> 1 attempt per server, same reason.
Kept: idle-gated + 30-min degraded backoff from the previous commit,
the single _get retry (protects real user playback from CF blips),
and the origin page check (the 'down' signal). Net upstream traffic is
now what an actual viewer would generate, nothing more.
MegaPlay (behind both hianime and anikoto) stopped returning
sources.file in plaintext and now ships the stream URL in an
AES-256-CBC 'enc' field. Key/IV are static, hardcoded in their public
player JS (newclient.min.js) — anti-scrape obscurity, not access
control. Shared megaplay_sources() in upstream.py tolerates both
shapes, so a revert upstream breaks nothing.
Also: hianime master_m3u8 now raises when servers are listed but none
resolve (403s, stale format) instead of returning None — a listed-but
dead server is a provider fault, not 'no dub', and the old None
silently short-circuited the ANI_SOURCE fallback chain.
hianime's HD-1 slot rotated to a megaplay chain whose CDN (norami.top)
is Cloudflare-blocked for us, which killed stream resolution (CDN probe
sat at 0 KB/s). The ZokoAnime server still serves the working
zokoanime chain. _master_m3u8 now walks every listed server of the
wanted type; _embed_payload handles both chains (window.__P blob and
megaplay data-id/getSourcesNew with re-roll). stream_meta returns {}
when the host is ours but no referer is needed, so the /stream
allowlist still holds.
hianime's subtitle 'lang' field is useless (everything says 'en'); the
real language is the label, and it is not always first — Chainsaw Man
E1 leads with Arabic. Pick label=='English', fall back to first track.
- hianime.py: capture the first .vtt from the deobfuscated embed JSON
(SUBS), expose subtitles(ep_id)
- upstream.py: subtitles() dispatcher (None for anidb, which bakes subs in)
- app.py: /api/episode/{id}/stream also returns a 'sub' stream token
- app.js: add a native <track> after hls.attachMedia (hls.js _cleanTracks
disables tracks present at attach time — that silently killed earlier
attempts) and set mode=showing so Chrome fetches the cues
- providers/{anidb,anikoto,hianime}.py: per-source modules behind the
ANI_SOURCE fallback dispatch (upstream.py _call/_providers)
- hianime.py: pystardust/ani-cli#1897 flow — search/episode-list/
servers API, HD-1 hash -> base64 embed url, window.__P blob
(base64 + XOR 'otaku-embed-v1') -> master m3u8; registers CDN hosts
and the required Referer via stream_meta()
- upstream.py: get_stream/fetch_parallel accept extra headers;
default source order hianime,anidb,anikoto (anidb.app is 503)
- app.py: /stream allowlist accepts provider-registered hosts and
sends their Referer; status probe follows the primary provider
(hianime variant URIs are relative to the variant path)
- app.js: error-time position save floors at furthest point reached
(hls.js resets currentTime to 0 before playerFail on fatal errors)
- tests: default-order + hianime import checks; live browser suite green