docs: README feature walkthrough with screenshots; css: .active wins over .nextup
This commit is contained in:
1 parent
29fab7ff5f
commit
d6bf7e149b
7 files changed
+71
-29
No files matched your search
@@ -1,7 +1,11 @@
|
||||
# ani-web
|
||||
|
||||
Web front-end for the [ani-cli](https://github.com/pystardust/ani-cli)
|
||||
workflow: search, browse, and stream anime in the browser via anidb.app.
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -36,25 +40,61 @@ the app or CLI are rejected immediately, even with an old cookie.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-user accounts (CLI-created, no self-signup): 30-day signed
|
||||
session cookie, 5 wrong attempts → 15-min lockout per user.
|
||||
PBKDF2-SHA256 hashes in `server/data/users.json` (0600).
|
||||
- Per-user watch history (`server/data/history/<user>.json`): position
|
||||
saved every ~10s, resume where you left off, top-5 "recently played"
|
||||
on the home page, full history page with per-entry delete. Nothing
|
||||
stored in the browser — new devices pick up your watch state after
|
||||
login, isolated per account. In-header chip shows the user, with logout
|
||||
+ delete-account (hamburger menu).
|
||||
- Search with poster grid, detail view with synopsis + season tabs
|
||||
- HLS playback (vendored hls.js), browser ABR across 1080/720/360p;
|
||||
loading/buffering overlay, and a failure overlay with Retry when a
|
||||
stream can't be played
|
||||
- Dub/sub toggle
|
||||
- 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 (first load still pays the upstream round-trips)
|
||||
- Autoplays the first episode (or resumes the last watched one); if the
|
||||
browser blocks autoplay, a "click to play" overlay appears
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 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)
|
||||
|
||||

|
||||
|
||||
## 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),
|
||||
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/ 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)
|
||||
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`.
|
||||
- The video CDN sends no CORS headers, so playback goes through the
|
||||
backend HLS proxy (`/stream/{token}`), which rewrites playlist URIs.
|
||||
- `python server/upstream.py` runs a self-check of the fetchers
|
||||
(search → details → episodes → dub stream for Chainsaw Man).
|
||||
- `python tests/run.py` runs the full test suite against a throwaway
|
||||
server (:8399, temp data dir — never touches the real data). Browser
|
||||
tests need playwright (`pip install playwright && playwright install
|
||||
chromium`); they're skipped if absent.
|
||||
- `.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.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 811 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 994 KiB |
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
@@ -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; }
|
||||
.ep { position: relative; font-size: 0.9rem; }
|
||||
.ep.active { border-color: var(--acc); color: var(--acc); }
|
||||
.ep.filler { opacity: 0.55; }
|
||||
.ep.done { opacity: 0.5; }
|
||||
.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); }
|
||||
|
||||
Reference in new issue
Block a user