From 18f6851f276336a588908c7e7ef38cfd19ed8a96 Mon Sep 17 00:00:00 2001 From: Glenn Thompson Date: Mon, 15 Dec 2025 01:17:09 +0300 Subject: [PATCH] Dynamic channel name updates and playlist crossfade transition - Fix channel name not updating in frame player when playlist changes - Use localStorage polling (2s interval) for cross-frame communication - Fix API response access: use bracket notation for 'channel-name' property - Add skip command after playlist load to trigger crossfade to new playlist - Add #PHASE metadata to Asteroid-Low-Orbit.m3u playlist --- asteroid.lisp | 9 ++++++++- parenscript/admin.lisp | 30 +++++++++++++++++++++++++++++- parenscript/stream-player.lisp | 28 ++++++++++++++++++++++++++++ playlists/Asteroid-Low-Orbit.m3u | 5 ++++- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/asteroid.lisp b/asteroid.lisp index 31c7bbf..4ae53e2 100644 --- a/asteroid.lisp +++ b/asteroid.lisp @@ -429,10 +429,17 @@ ;; Copy playlist to stream-queue.m3u (copy-playlist-to-stream-queue playlist-path) ;; Load into in-memory queue - (let ((count (load-queue-from-m3u-file))) + (let ((count (load-queue-from-m3u-file)) + (channel-name (get-curated-channel-name))) + ;; Skip current track to trigger crossfade to new playlist + (handler-case + (liquidsoap-command "stream-queue_m3u.skip") + (error (e) + (format *error-output* "Warning: Could not skip track: ~a~%" e))) (api-output `(("status" . "success") ("message" . ,(format nil "Loaded playlist: ~a" name)) ("count" . ,count) + ("channel-name" . ,channel-name) ("paths" . ,(read-m3u-file-paths stream-queue-path)))))) (api-output `(("status" . "error") ("message" . "Playlist file not found")) diff --git a/parenscript/admin.lisp b/parenscript/admin.lisp index 6c66296..d64b04f 100644 --- a/parenscript/admin.lisp +++ b/parenscript/admin.lisp @@ -653,7 +653,12 @@ (if (= (ps:@ data status) "success") (progn (show-toast (+ "✓ Loaded " (ps:@ data count) " tracks from " name)) - (load-current-queue)) + (load-current-queue) + ;; Update channel name in all channel selectors + ;; Use bracket notation because API returns "channel-name" with hyphen + (let ((channel-name (aref data "channel-name"))) + (when channel-name + (update-channel-selector-name channel-name)))) (alert (+ "Error loading playlist: " (or (ps:@ data message) "Unknown error"))))))) (catch (lambda (error) (ps:chain console (error "Error loading playlist:" error)) @@ -705,6 +710,29 @@ (setf html (+ html "")) (setf (ps:@ container inner-h-t-m-l) html)))))) + ;; Update channel selector name in UI after loading a new playlist + (defun update-channel-selector-name (channel-name) + "Update the curated channel option text in all channel selectors" + (ps:chain console (log "Updating channel name to:" channel-name)) + + ;; Store in localStorage so popout player can pick it up + (ps:chain local-storage (set-item "curated-channel-name" channel-name)) + + ;; Update in current document + (let ((channel-selector (ps:chain document (get-element-by-id "stream-channel")))) + (when channel-selector + (let ((curated-option (ps:chain channel-selector (query-selector "option[value='curated']")))) + (when curated-option + (setf (ps:@ curated-option text-content) (+ "🎧 " channel-name)))))) + + ;; Use postMessage to notify all frames about the channel name change + (when (and (ps:@ window top) + (not (= (ps:@ window top) window))) + ;; Post to top window which will relay to all frames + (ps:chain window top (post-message + (ps:create :type "channel-name-update" :channel-name channel-name) + "*")))) + ;; Save current queue to stream-queue.m3u (defun save-stream-queue () (ps:chain diff --git a/parenscript/stream-player.lisp b/parenscript/stream-player.lisp index b430417..ea00f71 100644 --- a/parenscript/stream-player.lisp +++ b/parenscript/stream-player.lisp @@ -556,6 +556,24 @@ ;; Update quality selector state based on channel (update-quality-selector-state) + ;; Check for channel name changes from localStorage periodically + (let ((last-channel-name (ps:chain local-storage (get-item "curated-channel-name")))) + (ps:chain console (log "Frame player: initial channel name from localStorage:" last-channel-name)) + (set-interval + (lambda () + (let ((current-channel-name (ps:chain local-storage (get-item "curated-channel-name")))) + (when (and current-channel-name + (not (= current-channel-name last-channel-name))) + (ps:chain console (log "Frame player: channel name changed from" last-channel-name "to" current-channel-name)) + (setf last-channel-name current-channel-name) + (let ((channel-selector (ps:chain document (get-element-by-id "stream-channel")))) + (when channel-selector + (let ((curated-option (ps:chain channel-selector (query-selector "option[value='curated']")))) + (when curated-option + (ps:chain console (log "Frame player: updating curated option text")) + (setf (ps:@ curated-option text-content) (+ "🎧 " current-channel-name))))))))) + 2000)) + ;; Start now playing updates (set-timeout update-mini-now-playing 1000) (set-interval update-mini-now-playing 5000)))) @@ -582,6 +600,16 @@ ;; Update quality selector state based on channel (update-quality-selector-state) + ;; Listen for channel name changes from localStorage + (ps:chain window (add-event-listener "storage" + (lambda (e) + (when (= (ps:@ e key) "curated-channel-name") + (let ((channel-selector (ps:chain document (get-element-by-id "popout-stream-channel")))) + (when channel-selector + (let ((curated-option (ps:chain channel-selector (query-selector "option[value='curated']")))) + (when curated-option + (setf (ps:@ curated-option text-content) (+ "🎧 " (ps:@ e new-value))))))))))) + ;; Start now playing updates (update-popout-now-playing) (set-interval update-popout-now-playing 5000) diff --git a/playlists/Asteroid-Low-Orbit.m3u b/playlists/Asteroid-Low-Orbit.m3u index 4950729..8856768 100644 --- a/playlists/Asteroid-Low-Orbit.m3u +++ b/playlists/Asteroid-Low-Orbit.m3u @@ -1,6 +1,9 @@ #EXTM3U -#PLAYLIST:Asteroid Low Orbit - Ambient Electronic Journey +#PLAYLIST:Low Orbit +#PHASE:Low Orbit +#DURATION:12 hours (approx) #CURATOR:Asteroid Radio +#DESCRIPTION:A 12-hour voyage through ambient, IDM, and space music #EXTINF:-1,Brian Eno - Emerald And Lime /app/music/Brian Eno/2011 - Small Craft On a Milk Sea/A1 Emerald And Lime.flac