docs: README feature walkthrough with screenshots; css: .active wins over .nextup

This commit is contained in:
jozamudi committed 2026-08-19 20:23:39 +00:00
1 parent 29fab7ff5f
commit d6bf7e149b
7 files changed
+71 -29

No files matched your search

+70 -28
View File
@@ -1,7 +1,11 @@
# ani-web # ani-web
Web front-end for the [ani-cli](https://github.com/pystardust/ani-cli) Web front-end for anime streaming: search, browse, and stream anime in
workflow: search, browse, and stream anime in the browser via anidb.app. 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](docs/img/login.png)
## Quick start ## Quick start
@@ -36,25 +40,61 @@ the app or CLI are rejected immediately, even with an old cookie.
## Features ## Features
- Per-user accounts (CLI-created, no self-signup): 30-day signed ### Accounts & watch history
session cookie, 5 wrong attempts → 15-min lockout per user.
PBKDF2-SHA256 hashes in `server/data/users.json` (0600). Accounts are created from the CLI only — there is no signup screen,
- Per-user watch history (`server/data/history/<user>.json`): position because the app is meant to sit behind your proxy on the open
saved every ~10s, resume where you left off, top-5 "recently played" internet. Each user gets a fully isolated watch history
on the home page, full history page with per-entry delete. Nothing (`server/data/history/<user>.json`): nothing is stored in the browser,
stored in the browser — new devices pick up your watch state after so a new device picks up your exact watch state after login. Deleting
login, isolated per account. In-header chip shows the user, with logout an account (hamburger menu → delete, or `server.users rm`) kills its
+ delete-account (hamburger menu). session cookies immediately and removes its history.
- Search with poster grid, detail view with synopsis + season tabs
- HLS playback (vendored hls.js), browser ABR across 1080/720/360p; The history page shows every entry with poster, progress, and a
loading/buffering overlay, and a failure overlay with Retry when a per-entry delete button:
stream can't be played
- Dub/sub toggle ![history](docs/img/history.png)
- Fast repeat navigation: upstream data is TTL-cached and HLS
playlists/segments are LRU-cached, so re-opening a show or re-seeking is ### Home: continue watching
near-instant (first load still pays the upstream round-trips)
- Autoplays the first episode (or resumes the last watched one); if the The home screen leads with a **continue watching** hero — the most
browser blocks autoplay, a "click to play" overlay appears recently watched episode with its poster, position, and a Resume
button — followed by a **recently played** grid with progress bars and
relative timestamps.
![home](docs/img/home.png)
### Search
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](docs/img/search.png)
### 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](docs/img/detail.png)
## Layout ## Layout
@@ -62,7 +102,8 @@ the app or CLI are rejected immediately, even with an old cookie.
server/ FastAPI app (app.py), anidb.app fetchers (upstream.py), server/ FastAPI app (app.py), anidb.app fetchers (upstream.py),
accounts (users.py). upstream.py is the ONLY Cloudflare-facing code 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) static/ front-end: index.html, login.html, app.js, style.css, hls.min.js (vendored)
tests/ test suite (run.py orchestrator, HTTP + browser tests) tests/ pytest suite: conftest.py (throwaway server), HTTP + browser tests
docs/img/ README screenshots
run.sh launch/restart the server (sets ANIWEB_SECURE=1) run.sh launch/restart the server (sets ANIWEB_SECURE=1)
SPEC.md design + the verified anidb.app data flow SPEC.md design + the verified anidb.app data flow
``` ```
@@ -74,12 +115,13 @@ SPEC.md design + the verified anidb.app data flow
everything is isolated in `server/upstream.py`. everything is isolated in `server/upstream.py`.
- The video CDN sends no CORS headers, so playback goes through the - The video CDN sends no CORS headers, so playback goes through the
backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs. backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs.
- `python server/upstream.py` runs a self-check of the fetchers - `.venv/bin/python server/upstream.py` runs a self-check of the
(search → details → episodes → dub stream for Chainsaw Man). fetchers (search → details → episodes → dub stream for Chainsaw Man).
- `python tests/run.py` runs the full test suite against a throwaway - `.venv/bin/pytest tests/ -q` runs the full test suite against a
server (:8399, temp data dir — never touches the real data). Browser throwaway server (:8399, temp data dir — never touches the real
tests need playwright (`pip install playwright && playwright install data). The API tests use stdlib only; the browser tests need
chromium`); they're skipped if absent. playwright (`uv pip install playwright && playwright install
chromium`) and are skipped if absent.
- Episode titles are not available: anidb's episode API only returns - Episode titles are not available: anidb's episode API only returns
`{id, number, number2, filler}`. Adding titles needs a free `{id, number, number2, filler}`. Adding titles needs a free
MyAnimeList API key. MyAnimeList API key.
Binary file not shown.

After

Width:  |  Height:  |  Size: 811 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 KiB

+1 -1
View File
@@ -113,9 +113,9 @@ button.tiny.on { border-color: var(--acc); color: var(--acc); }
.eps { display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 0.4rem; } .eps { display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 0.4rem; }
.ep { position: relative; font-size: 0.9rem; } .ep { position: relative; font-size: 0.9rem; }
.ep.active { border-color: var(--acc); color: var(--acc); }
.ep.filler { opacity: 0.55; } .ep.filler { opacity: 0.55; }
.ep.done { opacity: 0.5; } .ep.done { opacity: 0.5; }
.ep.nextup { border-color: #4b5568; background: #1f2130; } .ep.nextup { border-color: #4b5568; background: #1f2130; }
.ep.active { border-color: var(--acc); color: var(--acc); } /* declared last: wins over nextup */
.badge { font-size: 0.6rem; color: var(--muted); } .badge { font-size: 0.6rem; color: var(--muted); }