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

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

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

|
||||||
|
|
||||||
|
### 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
|
## 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 |
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; }
|
.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); }
|
||||||
|
|
||||||
Reference in new issue
Block a user