Multi-pronged attempt to fix cumulative lag between audio playback,
now-playing display, and browser notifications:
== Synchronization (work in progress) ==
- Server-side time-based metadata delay via get-listener-now-playing
with configurable *browser-buffer-seconds* parameter
- First track after restart syncs perfectly; subsequent tracks drift
due to browser audio buffer bloat (buffer grows unbounded over time)
- Added client-side buffer bloat detection: monitors audio.buffered
vs audio.currentTime and auto-reconnects when buffer exceeds 15s
- Added [STREAM-SYNC], [NOTIFY], [BUFFER] diagnostic logging to
browser console for ongoing diagnosis
== Countdown timer ==
- Server exposes remaining seconds via pipeline-track-remaining
- now-playing JSON API includes 'remaining' field adjusted for
browser buffer delay
- Player frame shows [mm:ss] countdown next to track title
- Main page now-playing area also shows countdown with its own
polling (15s interval) and local 1-second ticker
- LASS styles for .track-countdown and .track-countdown-mini
== Playback state fix ==
- Fixed save-playback-state saving one track ahead of what was
actually playing (was saving the track loading during crossfade)
- Uses *pending-save-file* with one-track delay so the saved state
reflects the track that was actually heard
== Notifications ==
- All notification conditions working correctly per diagnostic logs
- show-track-notification logs supported/permission/enabled/last/title
- Notifications fire consistently on track changes
Rate limiter fix (limiter.lisp):
- Override simple-rate::tax-rate with fixed-window implementation
The upstream version updates the timestamp on every request, which
prevents the window from ever resetting while polling is active.
This override only updates the timestamp when the window expires and
the counter resets.
- Override rate:left to correctly report expired windows as full budget,
so with-limitation does not block on stale tracking entries.
- These are monkey-patches on r-simple-rate; the upstream library is
not modified.
Polling normalization:
- front-page.lisp: channel-name polling 10s → 15s (matches stream-player.lisp)
Context: In frameset mode, front-page.js and stream-player.js both poll
the channel-name and now-playing endpoints. The sliding-window bug meant
the rate limit counter never reset as long as requests kept arriving
within the 60s timeout, eventually exhausting the budget and producing
429 errors for all API endpoints.
- Reset *is-reconnecting* flag before play attempt so error handler can
catch failures and trigger retry with exponential backoff
- Always reconnect on stall event - ready-state check was unreliable
- Add retry logic with backoff when reconnect fails (up to 5 attempts)
- Store stalled timeout reference for proper cancellation
Fixes issue where stream would show 'Stream stalled - reconnecting...'
but never actually reconnect, requiring manual intervention.
- Add format-timestamp-for-postgres and normalize-user-timestamps functions in database.lisp
- data-model-save now normalizes timestamp fields for USERS table before saving
- Fix authenticate-user to use data-model-save instead of dm:save directly
- Add global auth state in auth-ui.lisp for other scripts to check login status
- Add auth checks in front-page.lisp and stream-player.lisp to skip API calls when not logged in
- Fix ParenScript parentheses structure in stream-player.lisp DOMContentLoaded handler
Fixes password reset, role update, and last-login update failures caused by
integer timestamps being sent to PostgreSQL TIMESTAMP columns.
- Fix find-track-by-title to parse 'Artist - Title' format from Icecast
and search both artist and title columns in tracks table
- Fix favorites API alist key mismatch (TRACK-TITLE not TRACK_TITLE)
- Fix favorites cache to update UI after loading
- Fix race condition where star reverted after clicking
- Add aget-profile helper for Postmodern uppercase key lookup
- Add user playlist creation, editing, and track management
- Add library browser for adding tracks to playlists
- Add playlist submission workflow for station airing
- Add admin review interface with preview, approve, reject
- Generate M3U files on approval in playlists/user-submissions/
- Include user-submissions in playlist scheduler dropdown
- Use playlist description as PHASE tag in M3U
- Add database migration for user_playlists table
- Update TODO-next-features.org to mark feature complete
Track Requests:
- Database table for user track requests (migration 007)
- API endpoints for submit, approve, reject, play
- Front page UI for submitting requests
- Shows recently played requests section
Listening History:
- Auto-records tracks when playing (with 60s deduplication)
- Recently Played section on profile (has date formatting issues)
- Activity chart showing listening patterns by day
- Load More Tracks pagination
Profile Improvements:
- Fixed 401 errors returning proper JSON
- Fixed PostgreSQL boolean type for completed column
- Added offset parameter to recent-tracks API
Note: Recently Played section has date formatting issues showing
'20397 days ago' - may be removed in future commit if not needed.
The listening history backend works correctly.
For production: run migrations/007-track-requests.sql
Listening History:
- Auto-record tracks when they change (logged-in users only)
- Track stored by title (no tracks table dependency)
- Profile page shows real recent tracks, top artists, listening stats
- APIs: /api/asteroid/user/history, /user/listening-stats, /user/recent-tracks, /user/top-artists
Favorites Fixes:
- Remove favorite now uses title instead of track-id
- Fixed response parsing to show green success message
- Profile page remove button works correctly
Migration Script Updated:
- track_title column added to both tables
- track-id now optional (nullable)
- Unique index on (user-id, track_title)
- No foreign key to tracks table (title-based storage)
For production: run migrations/005-user-favorites-history.sql
- Add user_favorites and listening_history database tables
- Add migration 005-user-favorites-history.sql
- Create user-profile.lisp with favorites/history API endpoints
- Add star button (☆/★) to Now Playing on main page
- Add star button to frame player bar
- Add Favorites section to profile page
- Show login prompt when unauthenticated user clicks star
- Use gold color (#ffcc00) for favorited state (space theme)
- Fix require-authentication to properly detect API routes
- Support title-based favorites (no track DB required)
- Add /api/asteroid/channel-name endpoint for live channel name updates
- Update front-page.js and stream-player.js to poll server for channel name
instead of relying on localStorage (fixes issue where listeners don't see
playlist changes until page refresh)
- Add footer with Internet Radio directory link and craftering webring links
- Fix last-login display bug: use proper ISO 8601 timestamp format and
parse as Date string instead of Unix epoch
- Separate Channel selector (Curated/Shuffle) from Quality selector (bitrate)
- Add channel selector to frame player, front page, and popout player
- Dynamic curated channel name from playlist #PHASE: metadata
- Channel selection syncs across all player contexts via localStorage
- Quality selector disabled when Shuffle channel selected (fixed bitrate)
- Fix reconnectStream to use channel-aware config
- Consistent CSS styling for selector heights
- Add shuffle source to Liquidsoap config (96kbps MP3)
- Add shuffle option to all UI quality selectors (frame, popout, front page)
- Make now-playing APIs mount-aware for correct metadata display
- Implement separate recently-played lists for curated vs shuffle streams
- Speed up now-playing and recently-played refresh on stream change
- Fix clean shutdown of stats polling thread (positional timeout arg)
- Widen quality selector dropdown for shuffle label
- Detect when browser pauses muted stream (throttling)
- Auto-reconnect after 3 seconds when paused while muted
- Apply to all three players for consistency
- Implement isReconnecting flag to prevent duplicate reconnect attempts
- Add exponential backoff for error retries (3s, 6s, 12s, max 30s)
- Retry indefinitely until stream returns
- Handle error, stalled, and ended events consistently
- Reset state on successful playback
- Apply same logic to frame player, popout player, and front-page player
- Add reconnect button (🔄) to frameset player bar
- Recreate audio element on reconnect to fix wedged MediaElementSource
- Properly close and reinitialize AudioContext for spectrum analyzer
- Preserve volume and muted state when reconnecting
- Show status messages for connection issues
- Reduce Now Playing update interval to 5 seconds
- Add front-page.js to player-content.ctml for Now Playing updates
- Create About pages (about.ctml and about-content.ctml)
- Add About link to navigation in both modes
Major Changes:
- Convert all JavaScript files to Parenscript for better maintainability
- Move spectrum-analyzer to parenscript/ directory structure
- Add parenscript-utils.lisp for shared utilities
- Convert admin.js, player.js, front-page.js, auth-ui.js to Parenscript
- Convert profile.js, users.js, recently-played.js to Parenscript
Stream Reconnect Fixes (from merged PR):
- Add reset-spectrum-analyzer function to properly clean up Web Audio API
- Implement reconnect logic for pauses longer than 10 seconds
- Detect stale audio in 'playing' event and force stream reconnection
- Prevent 'Now Playing' updates while stream is paused
- Reduce reconnect delay to 200ms for faster response
- Add proper spectrum analyzer reset/reinit during reconnection
UX Improvements:
- Change live indicator from blink to smooth pulse (2s ease-in-out)
- Pulse animation like old PowerBook/MacBook sleep indicator
- Add MUTED indicator to spectrum analyzer when audio is muted
- Spectrum continues to flow even when muted (data still streaming)
- Red 'MUTED' text displayed in top-right corner of canvas
Technical Details:
- Parenscript files generate JavaScript via API endpoints
- All player modes updated: main player, front page, popout, frame player
- Improved audio context handling to only create once per element
- Added comprehensive error handling and logging
- Updated asteroid.asd to include parenscript module structure
Documentation:
- Updated all documentation dates to 2025-12-06
- Added PARENSCRIPT-EXPERIMENT.org documenting the conversion
- Updated PROJECT-HISTORY.org with Phase 9 (Visual Audio Features)
- Added comprehensive project statistics (408 commits, 9,300 LOC)
This conversion improves code maintainability by using Lisp throughout
the stack and makes it easier to share code between frontend and backend.