Skip to content

Kiosk & appearance fixes (#107, #106, #103, #105, #104) - #108

Open
klay2000 wants to merge 6 commits into
mainfrom
issue-107
Open

Kiosk & appearance fixes (#107, #106, #103, #105, #104)#108
klay2000 wants to merge 6 commits into
mainfrom
issue-107

Conversation

@klay2000

@klay2000 klay2000 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Stacked branch covering five open issues.


#107 — Auto dark/light mode changes

auto mode re-checked the clock once an hour, so a boundary could pass up to 59 minutes before the theme moved, and there was no way to opt out of mid-session switching.

  • msUntilNextSwitch() (pure, tested) computes the exact ms to the next boundary; theme.js arms a setTimeout for it, flips, and re-arms — no polling.
  • Re-checks on visibilitychange, since timers don't survive suspend.
  • New Settings → Appearance → Switch while running (default On), separate from the startup-only auto choice.
  • Changing the dark/light times still applies immediately either way — that's a deliberate user action, not a timed switch.

Test: Theme → Auto, set "Dark from" a minute or two ahead, leave the app open — it should flip on the dot. Turn the new toggle off and it should sit still until restart.

#106 — Touch friendly mode

  • Every :hover rule (39 of them) is now written :global(html:not(.no-hover)) …:hover.
  • Settings → Appearance → Touch friendly puts no-hover on <html>, switching them all off at once.

Test: toggle it on, tap around on the touchscreen — no highlight should stick after a tap.

#103 — Kiosk interface scale

  • Settings → Kiosk → Interface scale: a 1×–4× slider with clickable 1×/2×/4× markers; dragging near a marker snaps to it.
  • Applied as CSS zoom on <html>, so the layout reflows rather than being magnified.

Test: drag the slider on the kiosk, check the layout at 2× and 4× and that it survives a restart.

#105 — Scrubbing breaks playback on kiosk

Root cause, and it's server-side. Gonic has a transcode profile bound to the client name tonearm. Measured against your server:

client response
c=tonearm 200, audio/mpeg, chunked, no Content-Length/Accept-Rangesunseekable
c=DSub (or any other name) 206, audio/flac, Accept-Ranges: bytes — seekable

The app's stream URL never changed — it's been bare {id} since the first commit — so scrubbing worked until that profile was added on the server. With no seekable ranges, assigning currentTime left WebKitGTK stuck in a seek that never completes, which took play/pause down with it.

Scrubbing now works in every configuration:

  • "Original" sends format=raw, which overrides the profile: verified 206 + Accept-Ranges + real Content-Length, and the Pi's system GStreamer decodes the resulting FLAC.
  • Transcoded bitrates (320/192/128) seek via timeOffset. Gonic honours it for audio despite the spec calling it video-only — verified on the exact URL shape the app sends: timeOffset=120 returns precisely 120s fewer bytes. planSeek() picks between seeking the element and re-requesting the stream at an offset.
  • PlayerBar now tracks streamOffset as the loaded stream's time base, so position, duration and scrobble thresholds stay in track time; track loading is keyed on track id so an offset reload isn't mistaken for a track change; and awaitingLoad stops the store→element sync from reloading in a loop while a new src still reports a stale currentTime.
  • A failed play() resyncs the playing store (ignoring the expected AbortError on src change), so the button can't lie.

Test: scrub around, forwards and backwards, then hit play/pause — at Original and at a transcoded bitrate. Note "Original" now streams raw files (FLAC, ~26MB/track vs ~4MB) — the Pi pulled one in 1.2s over wifi, so it's comfortable, but worth a listen. If you'd rather not stream raw, removing the tonearm transcode profile in Gonic gets you seekable streams too.

#104 — Kiosk audio crackle

The Pi had wifi power save on, parking the radio between beacons. Those latency spikes starve the 200ms ALSA buffer, which comes out as crackle — despite an excellent signal (-13 dBm, 433 Mbit/s). The playback buffer had drained to a single 10ms period.

  • Installer warns when it detects wifi power save; --fix-wifi turns it off via a NetworkManager drop-in plus a live iw apply (no reconnect).
  • Already applied to cinnamoroll, so it's testable now without reinstalling.
  • The scrubbing breaks playback on kiosk #105 change likely helps too: a ranged raw download buffers ahead, where the chunked live transcode put every network hiccup straight into the decoder.

Test: play for a while and listen. Two independent causes were addressed, so if crackle persists, say so — that rules one out.

🤖 Generated with Claude Code

klay2000 and others added 6 commits August 7, 2026 14:28
Auto mode now arms a timer for the exact next dark/light boundary instead of
polling hourly, flipping the theme as it passes and re-checking on
visibilitychange (timers don't survive suspend). Gated behind a new
'Switch while running' setting, separate from the startup-only auto choice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hover highlights stick after a tap on a touchscreen. Every :hover rule is now
guarded by :global(html:not(.no-hover)), and the new touch-friendly setting
puts that class on <html> to switch them all off at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a 1x-4x scale slider to Settings > Kiosk, applied as CSS zoom on <html>
so the layout reflows instead of just being magnified. Clickable 1x/2x/4x
markers under the track, and the slider snaps to them when dragged close.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gonic streams a server-side transcode chunked, with no Content-Length or
Accept-Ranges, so the stream has no seekable ranges. Assigning currentTime
anyway left WebKitGTK stuck in a seek that never completes, taking play/pause
with it.

- 'Original' now sends format=raw, which the server serves with real range
  support (verified: 206 + Accept-Ranges), so scrubbing works as intended.
- canSeekTo() gates every seek on the element's seekable ranges, so an
  unseekable stream is left alone instead of wedging playback.
- A failed play() now resyncs the playing store, so the button can't lie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wifi power save parks the radio between beacons; the latency spikes starve
the 200ms ALSA buffer and come out as crackle, even on a strong signal (the
test Pi sits at -13 dBm and still drained to a single period). The installer
now warns when it detects this and fixes it with --fix-wifi, persisting via a
NetworkManager drop-in and applying it live with iw.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
format=raw fixed seeking for 'Original', but the 320/192/128 kbps options are
still delivered chunked and unseekable. Gonic honours timeOffset on audio, so
a seek on an unseekable stream now re-requests it starting at the target.

- planSeek() picks between seeking the element and reloading at an offset.
- PlayerBar tracks streamOffset as the loaded stream's time base, so position,
  duration and scrobble thresholds stay in track time.
- Track loading is keyed on track id rather than the src URL, so re-requesting
  the same track at an offset doesn't count as a track change.
- awaitingLoad stops the store->element sync from reloading in a loop while a
  new src still reports a stale currentTime and empty seekable ranges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant