Commit Graph

388 Commits

Author SHA1 Message Date
Glenn Thompson 47e6c5da46 Fix scheduler timezone and taglib type errors
- cl-cron uses local time, not UTC: add utc-hour-to-local-hour
  conversion so schedule hours fire at correct UTC times
  (e.g. 12:00 UTC now fires at 15:00 local on UTC+3)

- Wrap each taglib field read in safe-tag with per-field error
  handling so a type error in one field (e.g. album with non-simple
  string) doesn't crash play-file or skip the track

- Use (coerce ... 'simple-string) instead of copy-seq for
  guaranteed simple-string output from ensure-simple-string
2026-03-05 15:16:21 +03:00
Glenn Thompson 9ae7546466 Fix playlist stalls, FLAC/taglib errors, and playback state resume
- Prevent play-list thread death on scheduler playlist change:
  drain-queue-into-remaining drains full scheduler queue at once,
  updates loop-queue reference so repeat replays correct playlist,
  top-level handler-case prevents thread from dying silently

- Fix taglib SIMPLE-ARRAY CHARACTER type errors:
  ensure-simple-string coerces metadata strings and trims whitespace

- Fix FLAC::FILE slot unbound errors:
  retry once with 200ms delay for transient init failures

- Improve playback state persistence:
  save playlist path alongside track file so restart loads the
  correct playlist instead of always falling back to stream-queue.m3u

- Startup now uses resume-from-saved-state to resolve saved playlist
  and track position, falls back to stream-queue.m3u only if no state
2026-03-05 13:25:15 +03:00
Glenn Thompson 16da880822 Fix playlist repeat: loop playlist when tracks exhaust, scheduler takes priority
- Enable loop-queue in play-list: repeats current playlist when tracks run out
- next-entry checks queue first so scheduler-queued playlists override repeat cycle
- Prevents silent stream / client reconnect loops between scheduled playlist changes
2026-03-04 10:20:57 +03:00
Glenn Thompson d66d4fe46c Crossfade timing, scheduler fix, playback resume, auth noise cleanup
- Delay metadata/track-change notification by 1s after crossfade completes
- Log 'Loading next:' instead of 'Now playing:' during crossfade prep
- Add diagnostic logging: track duration check, crossfade trigger time
- harmony-load-playlist defaults to skip=nil: scheduler queues tracks
  without interrupting current playback
- Save current track to .playback-state.lisp on each track change,
  resume from saved position on restart
- Replace ~50 format-t debug statements in auth with log:info/log:warn
- Remove password hash logging for security
- Add .playback-state.lisp to .gitignore
2026-03-04 00:23:25 +03:00
Glenn Thompson 6e23efe1e4 Fix listener count and recently-played rendering
- Use total listener count across all mounts instead of per-mount
  (asteroid.lisp icecast-status API, stream-harmony.lisp now-playing)
- Fix recently-played.lisp: equal -> = for ParenScript string comparison
- Remove non-existent asteroid-shuffle.mp3 mount from recently-played JS
- Map all mount references to existing asteroid.mp3/asteroid.aac
2026-03-03 23:25:23 +03:00
Glenn Thompson fd1bc504a5 cl-streamer integration fixes: CORS, reconnect, stream config
Server-side fixes (stream-server.lisp):
- Add CORS preflight (OPTIONS) request handler for browser crossorigin audio
- AAC clients start from current buffer position instead of burst to avoid
  ADTS frame alignment issues that caused browser decode errors
- Upgrade client stream error logging from debug to warn for diagnostics
- Add send-cors-preflight function with proper Access-Control headers

Frontend fixes (stream-player.lisp):
- Rewrite reconnect-stream to reuse existing audio element instead of
  creating a new one, preserving browser user gesture context and preventing
  NotAllowedError on autoplay after reconnect
- Unify stream config: both curated and shuffle channels use same mount
  points (asteroid.mp3/asteroid.aac) since cl-streamer has a single pipeline
- Remove non-existent /asteroid-shuffle.mp3 mount reference that caused
  404s and broken pipe cascade when switching to shuffle channel
- Map :low quality to same MP3 mount (asteroid-low.mp3 not yet available)

Note: Channel selector preserved for future multi-stream support.
Recently-played API works correctly; frontend rendering to investigate separately.
2026-03-03 23:17:01 +03:00
Glenn Thompson 77458467c4 Fix integration: CORS, auto-start, mount names, Icecast bypass
CORS fix (icy-protocol.lisp):
- Add Access-Control-Allow-Origin: * to stream response headers
- Browser audio player can now connect cross-origin (port 8080 -> 8000)

Auto-start (asteroid.lisp -main):
- Start cl-streamer pipeline automatically on boot
- Load stream-queue.m3u and begin playback immediately
- Wrapped in handler-case so streaming failure doesn't block web server

Mount names (stream-harmony.lisp):
- Renamed /stream.mp3 -> /asteroid.mp3, /stream.aac -> /asteroid.aac
- Matches existing frontend URLs, zero template changes needed

Icecast bypass (asteroid.lisp, listener-stats.lisp):
- Front page uses get-now-playing-stats instead of icecast-now-playing
- check-icecast-status returns cl-streamer status when pipeline is active
- check-liquidsoap-status returns N/A when using cl-streamer
- asteroid/icecast-status API returns cl-streamer data directly
- poll-and-store-stats uses cl-streamer listener counts directly
- Eliminates hanging HTTP requests to port 8000 for Icecast XML

Tested: full browser streaming working end-to-end
2026-03-03 22:29:21 +03:00
Glenn Thompson dad1418bf8 Integrate cl-streamer into Asteroid Radio (replaces Icecast + Liquidsoap)
New files:
- stream-harmony.lisp: Bridge between cl-streamer pipeline and Asteroid app
  - start-harmony-streaming / stop-harmony-streaming lifecycle
  - on-harmony-track-change callback: feeds recently-played, DB track lookup
  - harmony-now-playing: returns same alist format as icecast-now-playing
  - harmony-load-playlist: loads M3U, converts Docker paths, feeds queue
  - harmony-skip-track / harmony-get-status

Pipeline control (harmony-backend.lisp):
- Add pipeline-current-track, pipeline-on-track-change callback
- Add pipeline-skip, pipeline-queue-files, pipeline-get-queue, pipeline-clear-queue
- play-list now supports skip flag, queue consumption, loop-queue mode
- notify-track-change fires callback after crossfade completes

Graceful fallback - all touch points check *harmony-pipeline*:
- frontend-partials.lisp: now-playing endpoints try Harmony first, fall back to Icecast
- asteroid.lisp: admin APIs (status/skip/reload/restart) try Harmony first
- playlist-scheduler.lisp: load-scheduled-playlist tries Harmony first
- asteroid.asd: added cl-streamer subsystem dependencies

Docker scripts updated:
- start.sh / stop.sh: only start/stop postgres (cl-streamer replaces streaming)
2026-03-03 21:27:29 +03:00
Glenn Thompson edf9326007 Taglib metadata reading, crossfade metadata timing fix
- Add taglib dependency to cl-streamer/harmony system
- Add read-audio-metadata: reads artist/title/album from FLAC/MP3 tags
- Add format-display-title: builds 'Artist - Title' from tags, falls back to filename
- Add update-all-mounts-metadata: updates ICY metadata on all mount points
- Defer metadata update during crossfade until fade completes (listeners hear correct track)
- Fix play-list wait loop: was nested inside crossfade conditional, first track never waited
- Remove filename-derived :title from test playlist (taglib reads real tags now)
2026-03-03 21:10:44 +03:00
Glenn Thompson 2649a8169a AAC encoding fixes, crossfade, buffer tuning, README rewrite
- Fix FDK-AAC C shim: use proper OUT_BITSTREAM_DATA=3 constant and INT types
- Add frame-aligned PCM accumulation buffer to AAC encoder for clean output
- Add fdkaac_encode and fdkaac_close to C shim (all FDK-AAC calls in C)
- Implement crossfade between tracks (3s overlap, 2s fade-in/out)
- Tune buffer: 90% drain sleep for encoding headroom, 64KB burst-on-connect
- Add FFI bindings for new shim functions
- Rewrite README.org with full architecture docs and integration plan
2026-03-03 20:20:43 +03:00
Glenn Thompson fcda723577 feat: Broadcast buffer, sequential playlist, ICY metadata
- Broadcast buffer: single-producer multi-consumer ring buffer with
  per-client read cursors. 32KB burst-on-connect for fast playback.
  Never blocks producer (overwrites old data for slow clients).
- Sequential playlist: play-list runs tracks one at a time using
  Harmony's on-end callback + condition variable for completion.
- ICY metadata: set-now-playing called on each track change.
- Fixed string vs pathname bug in harmony:play (etypecase mismatch).
- Debug logging for client disconnect diagnosis.

Verified: browser plays shuffled FLAC playlist via 128kbps MP3 stream.
2026-03-03 18:15:31 +03:00
Glenn Thompson a9e8276e9a feat: End-to-end streaming working! Custom streaming-drain + fixes
Major changes:
- streaming-drain: Custom drain that captures PCM from Harmony's pack
  buffer (raw IEEE 754 floats in unsigned-byte-8 array), converts to
  signed-16 PCM via CFFI, encodes to MP3 via LAME, and writes to
  stream server's ring buffer
- Fixed ring buffer deadlock: buffer-read/buffer-write held lock then
  called buffer-available which tried to acquire same lock. Created
  internal %buffer-available/%buffer-free-space without locking.
- Fixed ring buffer zero-length guard for unbound variable in finally
- Fixed sleep duration in drain: was dividing raw byte count by
  samplerate, now correctly converts to frames first
- Added flexi-streams wrapper for bivalent HTTP socket I/O
- Exported all public API symbols from cl-streamer package
- Added test-stream.lisp end-to-end test script

Verified: Amon Tobin FLAC -> 128kbps MP3 stream at localhost:8000
  file reports: MPEG ADTS, layer III, v1, 128kbps, 44.1kHz, JntStereo
2026-03-03 17:46:55 +03:00
Glenn Thompson 9d5166c562 feat: Add Harmony backend with dummy drain for streaming pipeline
- harmony-backend.lisp: Audio pipeline connecting Harmony to encoders
- Uses :dummy drain to capture PCM without audio output
- Reads from packer bip-buffer via request-read/finish-read API
- Encodes PCM to MP3 via LAME and feeds to stream server

All three systems compile cleanly:
  cl-streamer, cl-streamer/encoder, cl-streamer/harmony
2026-03-03 16:56:24 +03:00
Glenn Thompson 6c15441a08 feat: Add MP3 and AAC encoder FFI bindings
- LAME FFI (lame-ffi.lisp) for MP3 encoding
- FDK-AAC FFI (fdkaac-ffi.lisp) for AAC encoding
- High-level encoder wrappers (encoder.lisp, aac-encoder.lisp)
- Fix compilation warnings in buffer.lisp and icy-protocol.lisp

Both encoders tested and loading successfully:
- LAME version: 3.101
- FDK-AAC: libfdk-aac.so.2

Next steps: Harmony integration for audio pipeline
2026-03-03 16:46:29 +03:00
Glenn Thompson e1be88a54a feat: Initial cl-streamer skeleton for CL-native streaming
- Architecture document outlining Icecast/Liquidsoap replacement
- Core streaming server with ICY metadata protocol support
- Thread-safe ring buffer for audio data
- Mount point abstraction with metadata updates
- Multi-client connection handling

This is experimental groundwork for integrating with Harmony/cl-mixed
and playlisp/parsector for a pure CL streaming solution.
2026-03-03 16:30:49 +03:00
Luis Pereira 5f9dc80ac8 fix: add parenscript utils file 2026-03-02 17:51:26 -05:00
Luis Pereira 8700724f81 feat: add media session API on now-playing update 2026-03-02 17:51:26 -05:00
Luis Pereira 61266647a9 feat: frontpage now playing default fill 2026-03-02 17:51:26 -05:00
Luis Pereira 6fd8071a05 fix: add favourite count to icecast stats 2026-03-02 17:51:26 -05:00
glenneth 1e5a7e75f3 Replace format t logging with log4cl in playlist-scheduler
Converts all 22 format t print statements to 7 targeted log4cl calls:
- log:info for successful operations (playlist loaded, scheduler started)
- log:warn for recoverable failures (DB fallback, save/delete errors)
- log:error for actionable failures (liquidsoap unresponsive, missing playlist)

Removes redundant startup/shutdown chatter and per-retry debug noise.
2026-02-14 08:01:13 -05:00
glenneth 0da8101f63 fix: Scheduler retry logic + comprehensive documentation update
playlist-scheduler.lisp:
- Add liquidsoap-reload-and-skip with retry mechanism (3 attempts, 2s delay)
- Add liquidsoap-command-succeeded-p to validate telnet responses
- Sends reload before skip for reliability
- Root cause: nc -q1 silently failing, handler-case never triggered

Documentation updates:
- Fix API endpoint count: 15+ → 90+ across all docs
- Fix PROJECT-HISTORY.org Phase 10 heading level (was nested incorrectly)
- Add Phase 11: Scheduler Reliability Fix (February 2026)
- Update PROJECT-HISTORY.org dates and stats to February 2026
- Add Playlist Scheduler section to STREAM-CONTROL.org
- Update Future Enhancements checklist (mark implemented features)
- Fix TEMPLATING_SYSTEM.org missed date update
- New playlists: solar-flare.m3u, ceres-rising.m3u
2026-02-10 07:16:27 -05:00
glenneth 3d595df916 docs: Add complete API endpoint documentation and tidy PROJECT-OVERVIEW
API-ENDPOINTS.org:
- Add documentation for all 90+ API endpoints
- Stream Queue, Scheduler, Track Requests, User Playlists
- Favorites, History, Profile, Account endpoints
- Frontend Partials, Utility endpoints
- Now covers 100% of define-api endpoints in codebase

PROJECT-OVERVIEW.org:
- Tidy up tables and formatting
2026-02-10 07:16:27 -05:00
glenneth 476ec64309 docs: Comprehensive documentation update January 2026
- Update all #+DATE: and Last Updated lines to 2026-01-26
- Bump documentation version to 3.1
- Standardize author metadata (Glenn → Glenn Thompson where applicable)
- Add Phase 9: Production Launch & Feature Expansion (Nov-Dec 2025)
- Add Phase 10: Documentation Overhaul (January 2026)
- Update PROJECT-HISTORY.org with accurate contributor stats:
  - 614 total commits, 88 PRs merged
  - Glenn Thompson: 354 commits, 59 PRs
  - Brian O'Reilly: 148 commits, 3 PRs
  - Luis Pereira: 109 commits, 26 PRs
- Standardize API endpoints to /api/asteroid/ base path
- Update UI routes to reflect root / mounting
- Correct template extensions from .chtml to .ctml
- Fix Docker music library paths (music/library/)
- Update stream queue location to playlists/stream-queue.m3u
- Expand API-ENDPOINTS.org with playlist, stream control, admin APIs
2026-02-10 07:16:27 -05:00
Luis Pereira d894964c20 fix: increase now-playing rate limit to 10 per second 2026-01-23 21:17:44 -05:00
Luis Pereira 6b56a17b4a fix: restore AUTHSTATE object when there is no login 2026-01-23 21:17:44 -05:00
Luis Pereira 0def454077 feat: use navbar partial on all page templates 2026-01-21 17:32:41 -05:00
Luis Pereira 7c6eaa1fe0 feat: adds templating system docs 2026-01-21 17:32:41 -05:00
Luis Pereira 2992822010 feat: replace global auth state variable with template injected object 2026-01-21 17:32:41 -05:00
Luis Pereira 6ab7489f9b feat: allows for navbar menu exclution with exclude list 2026-01-21 17:32:41 -05:00
Luis Pereira cffb3cf384 feat: reusable navbar with auth check in render 2026-01-21 17:32:41 -05:00
Luis Pereira 732ff0858e fix: hide analyzer when in frame player mode 2026-01-21 17:32:41 -05:00
glenneth 55eafa943f Fix rate limiter corruption: cleanup negative amounts on startup
The r-simple-rate library has a bug where rate limit counters can go
negative and never reset. This happens because the reset condition
only triggers when amount >= 0, so negative amounts are permanently
stuck.

This fix adds:
- cleanup-corrupted-rate-limits function to delete corrupted entries
- db:connected trigger to run cleanup automatically on startup

This prevents the 429 error loops that occurred when counters became
corrupted with large negative values.
2026-01-18 12:58:35 -05:00
glenneth a9c48e59c9 Increase rate limits to 180 req/min for polling endpoints 2026-01-18 10:55:33 -05:00
glenneth 4622ae2440 Fix notification icon path to use existing asteroid.png 2026-01-18 10:21:21 -05:00
glenneth 849c1c2716 Increase rate limits on polling endpoints
Fix 429 errors caused by aggressive rate limiting on now-playing APIs.
Changed from 2-3 req/sec to 60 req/min for:
- asteroid/partial/now-playing
- asteroid/partial/now-playing-inline
- asteroid/partial/now-playing-json
- asteroid/channel-name

This fixes notifications not working and may resolve auto-reconnect issues.
2026-01-18 10:09:52 -05:00
glenneth 6d4169b707 Add system notifications for track changes
- Implement Web Notifications API in ParenScript
- Add notification toggle button (🔔/🔕) to player frame
- Show 'Artist - Track' notification when track changes
- Store notification preference in localStorage
- Auto-close notifications after 5 seconds
- Click notification to focus browser window
2026-01-18 08:55:53 -05:00
glenneth 7ed11ae2f4 Add sort dropdown for admin geo stats (minutes vs listeners)
- Add dropdown to admin template to choose sort order
- Update get-geo-stats to accept order-by parameter
- Update API endpoint to pass sort-by parameter
- Update ParenScript to read dropdown and pass to API
- Default sort is by minutes (engagement time)
2026-01-18 08:48:34 -05:00
glenneth b68fce817d Fix geo stats ordering to sort by total_minutes instead of total_listeners
Countries with high engagement (listen time) should appear at the top,
not countries with just high unique listener counts.
2026-01-18 08:48:34 -05:00
Luis Pereira ff651e6a36 fix: merged status and status-content 2026-01-15 12:58:45 -05:00
Luis Pereira 2118f4ed5a fix: merged about and about-content 2026-01-15 12:58:45 -05:00
Luis Pereira b862097ca2 fix: merged player and player-content 2026-01-15 12:58:45 -05:00
Luis Pereira 01cb0366c0 fix: merged frontend and frontend-content 2026-01-15 12:58:45 -05:00
Glenn Thompson 151f6c5569 Replace Autechre tracks with flow-state alternatives, add Underworld
- Replace jarring Autechre tracks with smoother ambient alternatives:
  - Labradford, Biosphere, Tycho, Marconi Union, arovane, Four Tet, Brian Eno, Alva Noto, Proem
- Add Underworld - Everything, Everything to all updated playlists
- Improves listener experience for coding/hacking flow state

Affected playlists:
- afternoon-orbit.m3u
- Asteroid-Low-Orbit.m3u
- Asteroid-Low-Orbit-DOCKER.m3u
- escape-velocity.m3u
- geostationary_stream_queue.m3u
- midnight-ambient.m3u
- new-years-eve-2025.m3u
2026-01-12 20:31:46 -05:00
glenneth 249844160f Fix listener minutes tracking for accurate geo stats
- Add register-web-listener to now-playing-json API endpoint
  This keeps listeners registered during continuous playback instead of
  timing out after 5 minutes of inactivity

- Fix listen_minutes calculation to increment by listener_count per poll
  Previously incremented by 1 regardless of how many listeners, now properly
  tracks listener-minutes (1 minute per listener per 60s poll interval)

- Add migration 009 documenting the calculation fix
2026-01-11 13:25:55 -05:00
glenneth 8b6209e4e0 Add mini spectrum analyzer to player frame with theme/style sync
- Remove cross-frame audio access that was breaking audio on navigation
- Add mini spectrum visualizer to persistent player frame
- Mini analyzer syncs theme and style with main analyzer via localStorage
- Add MusicBrainz search link to player frame (updates with track changes)
- Reduce quality selector width from 140px to 55px
- Add search_url to now-playing-json API response

The main analyzer is disabled in frameset mode due to browser restrictions
on cross-frame MediaElementSource, but the mini analyzer in the player
frame provides visualization that persists across content navigation.
2026-01-11 13:25:55 -05:00
Luis Pereira bad9d4294b fix: add api catch all route with 404 2026-01-07 17:39:26 -05:00
glenneth 8e60563e9d Fix stream stall reconnection failure
- 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.
2026-01-05 08:35:50 -05:00
glenneth f476b83cbe fix: Preserve playlist selection in scheduler dropdown on refresh 2026-01-04 14:11:15 -05:00
Brian O'Reilly f0f2dd93a4 patch bad change to Makefile to restore build.
take 2.
2026-01-02 19:07:43 -05:00
Luis Pereira f4468212de fix: docker image build configured with posgres 2026-01-02 07:43:47 -05:00