jozamudi 71a4055e89 Save watch position on fatal player error; make failure test CDN-independent
playerFail() is the single choke point for every fatal error (hls.js fatal,
v.onerror, autoplay block) but never saved the position — periodic saves
(timeupdate) stop when hls is destroyed, so a fatal error at e.g. 40s lost
everything after the last 10s save. Call savePos() there.

test_stream_failure_retry redesigned around that: it no longer needs a
healthy CDN (which hangs on segment bytes intermittently). It waits only for
playlist metadata (duration comes from the VOD playlist, not segment bytes),
wipes the show's history, seeks to 6s (below the 10s periodic-save threshold,
above the 3s minimum — so only an error-time save could persist it), aborts
/stream to force a deterministic fatal, and asserts the position landed.
Retry-recovery now asserts a fresh token fetch instead of segment playback.
2026-08-30 18:48:52 +00:00
2026-08-26 18:36:52 +00:00

ani-web

Web front-end for anime streaming: search, browse, and stream anime in the browser via anidb.app. Multi-user, with per-user watch history and HLS playback proxied through the backend. Tuned for phone as much as desktop.

login

Quick start

uv venv .venv
uv pip install --python .venv/bin/python -r requirements.txt
.venv/bin/python -m server.users add <you>   # create your account (one-time)
./run.sh

Open it through your HTTPS proxy (or http://localhost:8321 from the host itself — browsers treat localhost as trusted). run.sh restarts cleanly if the server is already running; logs go to server.log. With ANIWEB_SECURE=1 (set by run.sh), the session cookie is Secure, so remote plain-HTTP access can't hold a session — only the HTTPS proxy works. A legacy credentials.json password, if any, is migrated to user admin on first start.

Account management (no self-signup by design):

.venv/bin/python -m server.users add <name>    # add user (prompted password)
.venv/bin/python -m server.users rm <name>     # remove user + their history
.venv/bin/python -m server.users list          # list users

Security: passwords are PBKDF2-SHA256 (210k iters, per-user salts) in server/data/users.json (0600). Sessions are signed 30-day HttpOnly SameSite=Lax cookies; run.sh sets ANIWEB_SECURE=1 so cookies are Secure and only work through your HTTPS proxy. Accounts deleted via the app or CLI are rejected immediately, even with an old cookie.

Features

Accounts & watch history

Accounts are created from the CLI only — there is no signup screen, because the app is meant to sit behind your proxy on the open internet. Each user gets a fully isolated watch history (server/data/history/<user>.json): nothing is stored in the browser, so a new device picks up your exact watch state after login. Deleting an account (hamburger menu → delete, or server.users rm) kills its session cookies immediately and removes its history.

The history page shows every entry with poster, progress, and a per-entry delete button:

history

Home: continue watching

The home screen leads with a continue watching hero — the most recently watched episode with its poster, position, and a Resume button — followed by a recently played grid with progress bars and relative timestamps.

home

The header search box queries anidb.app and shows results as a poster grid. Your last 8 searches (per user) appear in a dropdown under the box, filter live as you type, and click to re-run. Results open the detail view: synopsis, stats, season tabs, and filler badges.

search

Player

Playback is HLS through a backend proxy (/stream/{token}) — the video CDN sends no CORS headers, so the browser can't fetch it directly. hls.js handles ABR across 1080/720/360p.

  • Resume — autoplay on open, resumes at the saved position; the position is saved every ~10s
  • Dub/sub toggle, playback speed (0.5–2×, remembered), and a hide fillers switch (fillers are badged and skipped by prev/next/auto-next)
  • Auto-next — when an episode ends, a 5-second countdown offers the next episode; Skip jumps straight to it, ✕ cancels
  • Watched state — completed episodes are dimmed with a ✓, the next unwatched one is highlighted
  • Failure overlay with a Retry button if a stream can't be played, plus loading/buffering indicators
  • Fast repeat navigation — upstream data is TTL-cached and HLS playlists/segments are LRU-cached, so re-opening a show or re-seeking is near-instant (the first load still pays the upstream round-trips)

detail

Layout

server/   FastAPI app (app.py), anidb.app fetchers (upstream.py),
          accounts (users.py). upstream.py is the ONLY Cloudflare-facing code
static/   front-end: index.html, login.html, app.js, style.css, hls.min.js (vendored)
tests/    pytest suite: conftest.py (throwaway server), HTTP + browser tests
docs/img/  README screenshots
run.sh    launch/restart the server (sets ANIWEB_SECURE=1)
SPEC.md   design + the verified anidb.app data flow

Dev notes

  • anidb.app is behind Cloudflare; all upstream requests use curl_cffi Chrome TLS impersonation. If that ever stops working, everything is isolated in server/upstream.py.
  • The video CDN sends no CORS headers, so playback goes through the backend HLS proxy (/stream/{token}), which rewrites playlist URIs.
  • .venv/bin/python server/upstream.py runs a self-check of the fetchers (search → details → episodes → dub stream for Chainsaw Man).
  • .venv/bin/pytest tests/ -q runs the full test suite against a throwaway server (:8399, temp data dir — never touches the real data). The API tests use stdlib only; the browser tests need playwright (uv pip install playwright && playwright install chromium) and are skipped if absent.
  • Episode titles are not available: anidb's episode API only returns {id, number, number2, filler}. Adding titles needs a free MyAnimeList API key.
S
Description
No description provided
Readme
8 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 65.2%
JavaScript 25.9%
CSS 5.9%
HTML 2.1%
Shell 0.9%