Commit Graph

273 Commits

Author SHA1 Message Date
Jonathan Sykes
08ff8398b5 Transfer saved songs directly between paired devices with resumable OPFS writes 2026-10-03 20:32:45 +08:00
Jonathan Sykes
88e2e67eeb Authorize direct file invitations within paired rooms and parties 2026-10-03 20:26:07 +08:00
Jonathan Sykes
ef70da1806 Document device media paths and direct transfer requirements 2026-10-03 20:24:10 +08:00
Jonathan Sykes
d93fc0d70f Use Glass Stage icons for playlist and offline controls 2026-10-03 20:14:19 +08:00
Jonathan Sykes
79a3c28c8e Center Glass Stage tab icons above their labels 2026-10-03 20:11:10 +08:00
Jonathan Sykes
5e78374efb Explain save failures and keep download diagnostics available 2026-10-03 19:23:07 +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
613f8fb902 Cover Now Playing returns after browsing in both layouts 2026-10-03 18:29:48 +08:00
Jonathan Sykes
cdc367a972 Restore the playing playlist when returning to Now Playing 2026-10-03 18:28:58 +08:00
Jonathan Sykes
f2c9459a54 Unify Glass Stage navigation and page controls with Now Playing 2026-10-03 18:19:56 +08:00
Jonathan Sykes
8ea92c1aac Report phone playback metadata once per browser session 2026-10-03 17:56:01 +08:00
Jonathan Sykes
856ddda6a6 Backfill device song metadata and queue guarded server caching 2026-10-03 17:55:49 +08:00
Jonathan Sykes
9e960cf70f iPhone background audio in every playback mode; Classic playlist card actions; export sheet bar
- Dual mode (separate video + audio, used by adaptive streams and the iPhone EQ
  copies): going to the background now makes the audio the master, so progress,
  Now Playing and auto-advance keep working while iOS has the video suspended
  (the paused video used to be the only source of "ended", so playback stopped
  after the current song). The picture is re-synced on return.
- A track that starts while the app is in the background (auto-advance, the
  lock-screen Next button) starts as audio — iOS will not start a <video> there —
  and gets its picture back at the same position when the app returns.
- The handoff also runs on pagehide and when iOS pauses the master video while
  the page is hidden, for when visibilitychange arrives too late.
- Classic: playlist cards place their three actions as a 2x2 block (note + add
  on top, remove below) instead of the note button wrapping to the bottom-left.
- Save to this device: hide the full progress bar once the file is ready; it
  read as a squashed, broken button above "Save to Photos / Files".
2026-10-03 17:46:23 +08:00
Jonathan Sykes
ca836e0cd2 Align saved video actions and confirm storage removal 2026-10-03 16:10:46 +08:00
Jonathan Sykes
cf9b6f4133 Clarify download states and make the empty page useful 2026-10-03 16:05:42 +08:00
Jonathan Sykes
0374e19396 Add lazy piano transcription and optional leased server worker 2026-10-03 15:47:53 +08:00
Jonathan Sykes
d4354d220e Preserve and display bilingual and phonetic lyric cues 2026-10-03 15:20:52 +08:00
Jonathan Sykes
c8ab741d72 Upgrade to ten-band EQ with migrated curves and headphone widening 2026-10-03 15:16:09 +08:00
Jonathan Sykes
0b3e82a768 Add voice search and large-control Car mode 2026-10-03 15:10:30 +08:00
Jonathan Sykes
172e8231f0 Import worship setlists with confirmed song matches and keys 2026-10-03 15:09:51 +08:00
Jonathan Sykes
de89508bf4 Add centre-panned vocal reduction to live and saved audio 2026-10-03 14:59:44 +08:00
Jonathan Sykes
7d84fdb3dc Add transparent OBS lyrics output through presenter pairing 2026-10-03 14:58:20 +08:00
Jonathan Sykes
05e05ebace Add learnable MIDI pedal controls with hot-plug support 2026-10-03 14:56:06 +08:00
Jonathan Sykes
b72d2e36a6 Add floating synced lyrics with picture-in-picture fallback 2026-10-03 14:54:23 +08:00
Jonathan Sykes
d7721045fb Add host-approved party suggestions and live upvotes 2026-10-03 14:52:10 +08:00
Jonathan Sykes
75dbc468cc Add download controls and device storage breakdown 2026-10-03 14:47:57 +08:00
Jonathan Sykes
09ae6a2a85 Organize settings into searchable sections with responsive navigation 2026-10-03 14:42:39 +08:00
Jonathan Sykes
264bb26a36 iPhone: EQ and loudness levelling for saved songs that keep playing when locked
iOS stops Web Audio when the screen locks, so live processing can't reach a
locked phone. For saved songs, EqRender renders the same chain as the live EQ
(5 bands -> level gain -> limiter) offline into a 32 kHz stereo WAV in OPFS
(eq-renders/<id>.wav, written by the OPFS worker's new write op), one song at a
time in the background. Playback then takes the picture from the saved video
(muted) and the sound from that copy in dual mode — plain media playback, so it
continues with the screen locked; live gains are zeroed so nothing is applied
twice. Copies are re-rendered when the EQ curve or a song's loudness changes
and removed when nothing needs applying.

- Level volume is now on by default everywhere (on iPhone it reaches saved
  songs through these copies).
- New iPhone-only Settings -> Playback -> Live sound processing (off by
  default): live Web Audio EQ/levelling for streamed songs, stops when locked.
- The EQ dialog works on iPhone and explains where the curve applies.
2026-10-03 13:55:27 +08:00
Jonathan Sykes
2c6f285cfe Settings: the saved-videos list folds
Settings → Offline cache lists every saved video; it now sits in a collapsible
'Saved videos' section showing the count and total size, closed by default,
remembering how it was left.
2026-10-03 08:08:53 +08:00
Jonathan Sykes
258aad83dd Add piano transcription, interactive key visualizer, and practice queue features 2026-10-03 07:55:33 +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
716b61ffee Document feature ideas and enhancement roadmap across playback, audio, worship and library 2026-10-03 07:55:33 +08:00
Jonathan Sykes
5d94acf3f0 Chapter picker: tap the chapter badge to jump to any chapter
The CH badge by the scrubber is now a button opening a list of every chapter
(number, title, start time); the playing one is highlighted and scrolled into
view, a tap seeks there (and resumes if paused). Works with shared chapters and
YouTube's.
2026-10-03 07:50:15 +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
796293c0a5 Admin upload card: styled file pickers, aligned fields, drag-and-drop
The raw browser file inputs (grey 'Choose File', truncated 'No fi...osen')
sat beside styled fields of other heights with Upload orphaned on its own row.
Now a drop zone that names the chosen file (size, type) and accepts drops, a
labelled Title / Artist / Cover row with a cover thumbnail preview, and a
clear 'choose a file first' message. Same IDs and upload code; stacks on phones.
2026-10-03 07:37:22 +08:00
Jonathan Sykes
47feded91b Sidebar: hide the Classic SVG nav icons outside the Classic layout
They relied on the hidden attribute, which browsers do not reliably honour on
SVG elements, so in Glass Stage each nav item drew a full-size black icon.
2026-10-03 07:29:38 +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
343eb53d89 Redesign Classic layout for desktop and mobile 2026-10-03 06:44:22 +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
8084411f6a Bulk-select a channel's videos into a playlist; last card no longer hidden under the bottom bars
- Channel pages join the select-mode views: tap Select in the filter row, pick
  videos (or Select all — respects the filter), then + Queue / + Playlist.
  Delete is hidden there (nothing to delete on a channel).
- --bottom-chrome (the fixed bottom nav + mini player height) is now measured
  whenever the mini player shows/hides and on resize/rotation, not only while
  the section rail scrolls, and the list pane pads its end by it, so the last
  card scrolls fully clear with or without the mini player.
2026-10-03 02:21:55 +08:00
Jonathan Sykes
2dfce8a2fb iPhone/iPad: no page zoom; PCs keep browser zoom
Stray pinches and double-taps kept zooming the installed app. On iOS only the
viewport gets maximum-scale=1/user-scalable=no, Safari's gesture events are
cancelled and the page uses touch-action: manipulation. The player's own pinch
zoom (pointer events) still works.
2026-10-03 02:07:41 +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
6581509ddd Update icon beside the search box after the update dialog is dismissed
Shown as soon as an update is offered, so Later, a backdrop tap or a failed
download all leave a one-tap way to update; it targets the newest build and
disappears while updating. The search form and box now shrink so the icon
never pushes Search off a phone screen.
2026-10-03 01:26:52 +08:00
Jonathan Sykes
65f5325a8e Search from service mode no longer strands the user on the home screen
In service mode's Video view the overlay lives inside the player pane, which
the home-over rule hid, while service mode kept the bottom bar and mini bar
hidden. Going home now leaves service mode first, and the rule spares the
service overlay.
2026-10-03 01:15:44 +08:00
Jonathan Sykes
caf0199c11 Fast lyric fitting for long videos; Search tab returns to the home screen while playing
- fitLyricLines measures text on a canvas with the line's own font and writes
  every --fit after measuring (it used an in-list DOM probe and wrote inside
  the loop, re-laying-out the whole list per line). 1200 lines: 600 ms -> 28 ms;
  sizes match the old method within 1 px. Service mode on long sermons with
  hundreds of transcribed lines opened slowly enough on phones to look broken.
- Bottom-nav Search while something plays shows the home screen (playlist
  board / hero) over the player pane; the media keeps playing unseen and the
  mini bar brings the player back (its close button too, so there is always a
  way back). Starting another video restores the player.
2026-10-03 01:10:15 +08:00
Jonathan Sykes
171a0bd127 Glass Stage buffered chip: 'Saved on device' for device copies, and measure the range around the playhead
It read the last buffered range, so seeking ahead made the figure jump, and a
device copy showed a meaningless partial percentage.
2026-10-03 01:00:09 +08:00
Jonathan Sykes
cbffa1fd8b Saves: paint progress only on Downloads rows, not on search/playlist cards
Every card of a video carries .downloading while it saves, so the progress
painter rewrote their channel line and its final repaint left 'Saving for
offline…' on the card after the save had finished.
2026-10-03 00:59:18 +08:00