ROOT CAUSE: AudioPlayer was the only component holding the <audio>
element, and it was only mounted when expanded=true. The MiniPlayer
(default state after Play) had no audio element at all. So clicking
Play set the store state but no audio was ever loaded or played.
Fix: New AudioEngine component holds the single <audio> element and
all playback logic. Mounted globally in App.tsx whenever an item is
loaded — independent of MiniPlayer/AudioPlayer UI state.
Store: New seekRequest (counter-based) lets external UI request seeks
without direct audio element access. New playerError surfaces errors
across MiniPlayer (red progress bar) and AudioPlayer (banner).
AudioPlayer + MiniPlayer reduced to pure UI components that interact
through the store. They can mount/unmount freely without affecting
playback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Streaming: Drop token-in-URL auth entirely. Session-ID (UUID, 128-bit
entropy) IS the auth — same approach as Audiobookshelf. Eliminates the
entire class of token-related failures and matches how every other
streaming server handles this. Logs every stream request with Range
header and User-Agent for diagnostics.
Player: Visible error banner in UI when audio fails (with HTML5 media
error code translated to German). Stream URL is shown in the banner so
the user can see exactly what failed.
Scanner: Cover extraction from two new sources (in addition to API
matching):
1. Folder-level images (cover.jpg, folder.jpg, front.jpg, etc.)
2. Embedded artwork (ID3 APIC, MP4 covr, FLAC/Vorbis pictures)
Runs on every scan — also fills in covers for items that were already
scanned but never got one from matching.
New endpoint POST /api/items/{id}/extract-cover triggers this manually
for a single item.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Streaming: Custom range-aware HTTP endpoint. Returns 206 Partial Content
for Range requests (with Content-Range, Content-Length, Accept-Ranges).
This was the root cause of broken seeking — Starlette's default
FileResponse behavior wasn't reliable across all clients. Now seeking
works natively via standard HTML5 audio.
Player: Full rewrite. Cleaner separation between absolute book time and
per-track time. Track switching uses pendingSeek + canplay/loadedmetadata
handlers. Console logs for debugging. Removed crossOrigin to avoid CORS
issues. Removed hls.js entirely.
Matcher: Critical bug fix — get_work_details (OpenLibrary) was returning
a sparse MatchResult that REPLACED the rich search result, losing cover,
author, year. New _enrich_match merges details into best without
overwriting existing values (except description/chapters which are
preferred from details fetch).
Scoring: Lenient min/max-weighted similarity (better for German episodic
titles like "Die drei ??? - Folge 215"). Thresholds lowered:
UNCERTAIN 0.50→0.40, AUTO_ACCEPT 0.75→0.65.
Search: search_for_item now returns ALL fields (narrator, publisher,
series, genres, description, language) so manual apply has full data.
Apply: apply_match now always constructs from body first, then enriches
with details. Previously OL applies would lose cover/author. Added
detailed logging across matcher and apply paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Player: Replace HLS with direct FileResponse streaming. Token passed as
query param (?token=JWT) so browser <audio> can authenticate. Multi-track
support: seeks and track transitions handled in AudioPlayer with refs.
Removes hls.js dependency from playback path.
Admin: Add DNB to match sources list. Replace toggle buttons with ordered
drag-to-reorder list (HTML5 drag API) + separate add/remove buttons so
source priority is explicit and adjustable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Neue Farbpalette: #0a0d0b Hintergrund, #111511 Surface, #1ed760 Akzent
- Inter-Font via Google Fonts (400/500/600)
- CSS Grid Layout: 240px Sidebar + 1fr Content + 88px Player Bar
- Sidebar: neue Nav-Items mit Padding, Uppercase-Labels, grünes Logo-Icon
- CoverImage: 2-Buchstaben-Kürzel mit 6 Cover-Placeholder-Farben
- BookCard: Play-Overlay (34px), 3px Progress-Bar am Kartenrand
- MiniPlayer: Player Bar mit 3-Spalten-Grid, 4px Progress-Track
- Alle Pages und Komponenten auf neue Tokenfarben aktualisiert
- BookDetail: Fehlermeldung wenn kein Match gefunden
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MatchResult was missing subtitle field, causing AttributeError in
_apply_match that silently killed every background match task
- Wrap _apply_match in try/except with exc_info logging so failures
are visible in docker compose logs backend
- New POST /api/libraries/:id/match-all endpoint to trigger matching
for all unlocked items (useful for items scanned before the fix)
- Admin UI: Match button per library next to the Scan button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth: username lookup is now case-insensitive via func.lower()
- scanner: trigger match_audiobook for each newly found item after scan
- matcher: read match_sources from library settings; refactored to loop
over configured sources in priority order instead of hardcoded sequence
- schemas/routers: expose matchSources in LibraryOut API response
- Admin UI: pill-toggle for MusicBrainz/Open Library/Google Books per library
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sidebar: show Podcasts section only when podcast libraries exist;
show hint text when no libraries are configured yet
- Admin: extract LibraryForm component, add edit (Pencil) button per
library with inline form and PATCH support
- Backend: add media_type to LibraryUpdate schema and PATCH handler
- FileBrowser: add quick-access shortcuts (/audiofiles /data /media /),
show all files+dirs so users can confirm correct folder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Toggle: add left-0.5 anchor so translate works correctly
- FileBrowser: show files (grayed out) alongside dirs so users can
confirm they are in the right folder before selecting it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- On first start (no users in DB), show a setup page in the web UI
to create the admin account instead of reading credentials from .env
- New public endpoints: GET /api/setup/status, POST /api/setup
- New admin endpoint: GET /api/filebrowser?path=... for directory listing
- FileBrowser modal component with navigation, used in Admin > Libraries
- Remove _seed_admin / _seed_default_library from server startup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Login.tsx: Eye/EyeOff toggle button on password field
- main.py: _seed_admin() now updates stored bcrypt hash when ENV password changed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>