Commit Graph

91 Commits

Author SHA1 Message Date
Jonathan Sykes
3f913de0c3 Watch party invites by profile: host invites online profiles, invited users see running parties on open and can join one or dismiss 2026-10-15 19:26:37 +08:00
Jonathan Sykes
63cea89b68 Admin: Gmail and Discord-bot settings for password reset, setup warning on open; export notes admin guard 2026-10-15 19:02:53 +08:00
Jonathan Sykes
ca47528581 Protected profiles: recovery email and Discord contacts with reset codes (Gmail SMTP, Discord relay) 2026-10-15 17:28:10 +08:00
Jonathan Sykes
dbcee5d5f8 Deep search: pass the database client, not the db module, so start/jobs/estimate stop returning 500 2026-10-15 16:41:25 +08:00
Jonathan Sykes
59871a5b88 Merge codex/admin-ops: editor redesign, deep search, admin tools and ops 2026-10-15 11:42:07 +08:00
Jonathan Sykes
5c94a7d46a Search: answer on the first YouTube page, fill the rest in background; never cache empty results; gzip search JSON 2026-10-15 10:30:20 +08:00
Jonathan Sykes
c242641b2d iPhone: stop rate-slewing the video for sync; add playback telemetry beacon 2026-10-14 08:31:38 +08:00
Jonathan Sykes
27c694901d Add upload library management and ops diagnostics 2026-10-14 03:09:12 +08:00
Jonathan Sykes
5177a4d441 Add device diagnostics and P2P access controls 2026-10-14 02:45:33 +08:00
Jonathan Sykes
e9a597c415 Add live server performance monitoring 2026-10-14 02:43:20 +08:00
Jonathan Sykes
703aef617b Add resumable bulk lyrics operations 2026-10-14 02:36:30 +08:00
Jonathan Sykes
34b1173ea6 Add searchable index health and repair tools 2026-10-14 02:02:07 +08:00
Jonathan Sykes
180a28d3e4 Add a paged lyrics quality queue 2026-10-14 01:56:53 +08:00
Jonathan Sykes
58d0e6f140 Add resumable admin deep search 2026-10-14 01:47:35 +08:00
Jonathan Sykes
ca10fe6486 Refetch missing media from catalog source 2026-10-14 01:26:25 +08:00
Jonathan Sykes
593ecaecd9 Extend video catalog source metadata 2026-10-14 01:22:32 +08:00
Jonathan Sykes
dd09f66578 Separate runtime manifest inputs from directly served files 2026-10-09 14:40:46 +08:00
Jonathan Sykes
54a07a0a2a Synchronize verified assets incrementally and guard update activation 2026-10-07 23:40:20 +08:00
Jonathan Sykes
d18d17cf29 Preserve legacy cache rules and pin service worker bytes at boot 2026-10-07 23:01:55 +08:00
Jonathan Sykes
6cb00d5073 Add per-file asset hashes and a rollback-compatible server manifest 2026-10-07 22:57:41 +08:00
Jonathan Sykes
b77938ab4a Queue bounded stream prewarm requests 2026-10-07 03:13:30 +08:00
Jonathan Sykes
2042c9b6fc Recover related videos with bounded discovery and retryable fallbacks 2026-10-04 02:42:02 +08:00
Jonathan Sykes
795a5aaed4 Prefer direct copies for playback, saves and exports within verified device groups 2026-10-03 20:38:51 +08:00
Jonathan Sykes
f341b0c76e Restart failed saves through a guarded server retry 2026-10-03 19:16:08 +08:00
Jonathan Sykes
5485a3c2f4 Try alternate YouTube clients for unavailable save sources 2026-10-03 19:12:48 +08:00
Jonathan Sykes
856ddda6a6 Backfill device song metadata and queue guarded server caching 2026-10-03 17:55:49 +08:00
Jonathan Sykes
0374e19396 Add lazy piano transcription and optional leased server worker 2026-10-03 15:47:53 +08:00
Jonathan Sykes
2b38717c05 Add admin analytics, metadata collection, and grouped lyric cues
Queue reviewable Whisper drafts from the song list and lyrics editor. Preserve line breaks within one timed cue across editing, saving, reporting, and service views.

Add storage and listening analytics with a durable metadata collector, related-search depth, video limits, thumbnail storage, and a browsable metadata library.
2026-10-03 07:55:33 +08:00
Jonathan Sykes
ea9e11d054 Loudness levelling (EBU R128, -14 LUFS) measured in the browser with WebAssembly
- frontend/wasm/loudness.c → loudness-wasm.js (1.5 KB, base64-embedded; built by
  scripts/build-loudness-wasm.sh with clang or Zig's clang): K-weighting + 100 ms
  energy steps. loudness.js does the 400 ms gating (abs -70 LUFS, rel -10 LU)
  with an identical JS fallback. EBU Tech 3341 sine cases measure -23.0/-33.0
  on both engines; a real track reads -21.7 vs ffmpeg ebur128 -21.8.
- The device measures from audio it already has (saved copy or the server
  cache's m4a sidecar, songs up to 12 min, decoded natively at 22.05 kHz) and
  reports the number to /api/loudness, so each song is measured once for
  everyone; the server stores numbers only, no audio processing.
- The EQ graph gains a level stage (gain -> limiter) that eases each track
  toward -14 LUFS (-12..+9 dB). Settings -> Playback -> Level volume: on by
  default on Android/desktop, opt-in on iPhone (Web Audio stops sound when the
  screen locks there).
2026-10-03 07:44:44 +08:00
Jonathan Sykes
5154ef5f75 Collect discovered video metadata and recommend videos from listening history 2026-10-03 07:16:51 +08:00
Jonathan Sykes
54fcd12119 YouTube chapters: extracted automatically, ticks on the seek bar and Glass scrubber, [ / ] to jump
- Server: chapters come from yt-dlp's `chapters` (creator chapters and
  description timestamps) whenever a video is resolved, kept in a yt_chapters
  table; GET /api/chapters?v= serves them (resolving once if needed, 30-day
  cache) so server-cached copies get them too. Never fails the player.
- Player: a video's shared chapters win; with none, YouTube's stand in —
  listed as "Chapters from YouTube", read-only (no edit/delete; + Chapter
  copies them into an editable list). They drive the existing seek-bar ticks
  (classic and Glass Stage), the chapter badge and the chapter list, and are
  cached offline with the video's notes.
- Keys: [ previous chapter (restarts the current one when >3 s in), ] next.
2026-10-03 03:52:50 +08:00
Jonathan Sykes
9ce9a96f38 Channels load for every channel type; parallel saves setting (default 4)
Channels: one loader tries, in order, the Videos/Live/Shorts tabs (normal
channels), the uploads playlist (Topic channels), the Releases/Playlists tabs
expanded into their tracks (artist/music channels whose only content is albums,
e.g. Cathedral of Praise Worship), then the home page; bare names are resolved
to an id first. Nothing public → ok with a message, never a raw yt-dlp error.

Saves: SaveSlots caps concurrent offline saves (Settings → Offline cache →
Videos saved at once, 1–8, default 4); queued saves show "Waiting for a free
slot", and resumed saves now run up to the limit instead of one at a time.
2026-10-03 02:04:27 +08:00
Jonathan Sykes
71f1913d0b Optional profile password or key, with a credentials file to sign in other devices
Profiles without protection behave as before. Protected profiles store only an
argon2id hash; load, save, protection changes and the playlist inbox need the
secret (X-Profile-Secret), wrong guesses are throttled per profile and IP.
Settings: Protect (password or generated key), change/remove, export and import
a credentials file; loading a protected profile prompts for the secret.
2026-10-03 01:51:07 +08:00
Jonathan Sykes
91627dc0a9 Share target: shared YouTube links play, shared audio/video uploads to the user's library; page shortcuts
- manifest share_target (POST multipart: title/text/url + audio/video files)
  and eight shortcuts (Search, Queue, Saved, Downloads, History, Library,
  Notes, Settings). /?view=<page> now actually opens that page — the old
  shortcuts pointed at it but nothing read it.
- sw.js receives the share: files are parked in the ytp-share-inbox cache (not
  a versioned shell cache, so deploys never evict it) and the app is opened;
  links/text go to /?shared=. A server POST /share-target fallback covers the
  first visit before a worker is in control (links only).
- The app plays a YouTube link found anywhere in the shared text (watch,
  youtu.be, shorts, live, embed, music; keeps t=), otherwise searches the text.
  /?shared=<link> also works from an iOS Shortcut (iOS has no share target).
- Shared files upload with progress to PUT /api/uploads/shared and join a
  "Shared uploads" playlist; network failures stay in the inbox for the next
  open, refusals are dropped with the reason.
- The public route is bounded: audio/video only (ffprobe-validated, error text
  without server paths), 500 MB per file, 2 GB and 20 a day per device, 50 GB
  for all shared uploads, one at a time per device (all env-tunable). Shared
  uploads are unlisted: reachable by id, never in anyone else's search.
  uploads gains owner + listed columns (idempotent ALTER).
2026-10-03 01:33:51 +08:00
Jonathan Sykes
16e2b24c31 Resumable saves: prepare reports the mp4's real size, not the cache row's mp4+m4a total
The media_cache row's size counts the audio sidecar too, so a device asked for
bytes past the end of the file and could never finish a ranged save.
2026-10-03 00:49:24 +08:00
Jonathan Sykes
56b3f4e449 Resumable saves: ranged downloads that survive dropped connections, reloads and app kills
Server
- /api/download/:id answers Range with a strong ETag ("<id>.<gen>") and honours
  If-Range; a stale partial gets the whole current file (streamed, so Bun does
  not re-apply the Range itself). Uploads get the same treatment.
- GET /api/download/:id/prepare never blocks: ready {gen,size,sha256,etag,ext},
  working (server still fetching), legacy (HEVC / too long / cache offline),
  failed. Recently refused prepares are remembered for 10 minutes.

Browser
- The OPFS worker saves in 8 MiB ranges, writes at the byte offset, flushes
  each chunk, retries each chunk 6 times with backoff (30 s idle timeout) and
  keeps the .part plus a .part.json sidecar naming the server copy it belongs
  to. A changed copy restarts cleanly; the finished file is hashed once and
  checked against the server's SHA-256.
- SaveQueue remembers unfinished saves and resumes them on start, online,
  return to the foreground and every 2 minutes while visible; one at a time.
- Downloads shows live MB progress, 'Preparing on server', 'Verifying', and
  paused saves with Resume and Cancel (confirmed).
- listVideos ignores the sidecars; new listPartials/discardPartial helpers.

Verified in Chromium through a connection-dropping proxy: paused at 8 MiB,
auto-resumed after a reload from byte 8388608, final SHA-256 matched.
2026-10-03 00:46:52 +08:00
Jonathan Sykes
11ddb8a970 Uploads library on the USB drive with a nightly stable-disk copy; Topic channels resolve by name
- UPLOAD_DIR moves to the USB drive; reads fall back to UPLOAD_BACKUP_DIR on the
  ytplayer-data volume, and while the drive is offline new uploads land there
- scripts/ops/uploads-backup.sh (homelab cron, 03:30) copies both ways, never
  deletes; the server removes a deleted upload from both places
- /api/channel resolves a bare channel name ('Artist - Topic') to its id via a
  search, and the client falls back to the channel name for old saved entries
2026-10-02 22:38:05 +08:00
Jonathan Sykes
1f9a97a6ae Media cache on the homelab USB drive with an offline guard; Topic channels list their tracks
- MEDIA_DIR moves to /mnt/data/ytplayer (bind-mounted at /app/bulk); DB, lyrics,
  notes and uploads stay on the ytplayer-data volume
- MEDIA_VOLUME_MARKER: while the marker on the drive is missing the cache writes,
  serves and deletes nothing, so playback streams and an unmounted drive never
  fills the disk under its mountpoint or loses its index on a reboot
- Budget 250 GiB, keep 20 GiB free on the drive
- /api/channel falls back to the uploads playlist and the channel home page when
  a channel has no Videos tab (auto-generated '- Topic' music channels)
2026-10-02 21:21:23 +08:00
Claude
ee248d2303 IndexedDB search library (1000x100, 15-day copies, Update search), server video metadata for instant local results 2026-10-02 02:15:28 +00:00
Claude
1b10a994dd Version-stamped static URLs cached forever, thumbnail cache 25000 with throttled trimming 2026-10-02 02:11:38 +00:00
Claude
c69cc3a4bc Persistent server search cache: 200 results per query, 250k queries, 15-day stale-while-revalidate, refresh=1 2026-10-02 02:10:39 +00:00
Claude
14e2307a92 Keep search results with thumbnails until cleared in Settings; add stale-while-revalidate search and longer asset caching 2026-10-01 22:04:51 +00:00
Claude
02b337fcbc Add save-to-device export, server export route and an On other devices P2P list 2026-10-01 18:53:00 +00:00
Claude
08e95814e5 Support hour-long uploads and videos: raise body limit, stream uploads to disk, cache up to 3 h, add deferred-ideas note 2026-10-01 18:48:37 +00:00
Claude
84cd00368d Add a P2P panel to the admin page 2026-09-30 08:02:52 +00:00
Claude
60a28fc593 Restore an evicted server copy from an online holder 2026-09-30 07:58:42 +00:00
Claude
8d57a654c1 Let a device hand a file to the server for hashing and validation 2026-09-30 07:50:38 +00:00
Claude
6fa930ad62 Add the /ws/p2p presence and signalling hub and the holders endpoint 2026-09-30 07:42:35 +00:00
Claude
c629ff5836 Register devices and report verified holdings to the server 2026-09-30 07:38:47 +00:00
Claude
eb3fe45761 Count views and evict server copies by retention criteria before LRU 2026-09-30 07:25:56 +00:00