Compare commits

..

No commits in common. "a9c48e59c951b5e5251d1b61fdcd2e33c597a174" and "7ed11ae2f4086d5cda1d8fd6efff429eda64c520" have entirely different histories.

6 changed files with 20 additions and 167 deletions

1
.gitignore vendored
View File

@ -65,4 +65,3 @@ playlists/stream-queue.m3u
/radiance-bootstrap.lisp
/test-postgres-db.lisp
/userdump.csv
.envrc

View File

@ -91,7 +91,7 @@
(:listeners . ,total-listeners)
(:track-id . ,(find-track-by-title title))))))))
(define-api-with-limit asteroid/partial/now-playing (&optional mount) (:limit 180 :timeout 60)
(define-api-with-limit asteroid/partial/now-playing (&optional mount) (:limit 3 :timeout 1)
"Get Partial HTML with live status from Icecast server.
Optional MOUNT parameter specifies which stream to get metadata from.
Always polls both streams to keep recently played lists updated."
@ -121,7 +121,7 @@
:connection-error t
:stats nil))))))
(define-api-with-limit asteroid/partial/now-playing-inline (&optional mount) (:limit 180 :timeout 60)
(define-api-with-limit asteroid/partial/now-playing-inline (&optional mount) (:limit 3 :timeout 1)
"Get inline text with now playing info (for admin dashboard and widgets).
Optional MOUNT parameter specifies which stream to get metadata from."
(with-error-handling
@ -135,7 +135,7 @@
(setf (header "Content-Type") "text/plain")
"Stream Offline")))))
(define-api-with-limit asteroid/partial/now-playing-json (&optional mount) (:limit 180 :timeout 60)
(define-api-with-limit asteroid/partial/now-playing-json (&optional mount) (:limit 2 :timeout 1)
"Get JSON with now playing info including track ID for favorites.
Optional MOUNT parameter specifies which stream to get metadata from."
;; Register web listener for geo stats (keeps listener active during playback)
@ -160,7 +160,7 @@
("title" . "Stream Offline")
("track_id" . nil)))))))
(define-api-with-limit asteroid/channel-name () (:limit 180 :timeout 60)
(define-api-with-limit asteroid/channel-name () (:limit 2 :timeout 1)
"Get the current curated channel name for live updates.
Returns JSON with the channel name from the current playlist's PHASE header."
(with-error-handling

View File

@ -337,117 +337,9 @@
;; Track the last recorded title to avoid duplicate history entries
(defvar *last-recorded-title* nil)
;; Track last notified title to avoid duplicate notifications
(defvar *last-notified-title* nil)
;; Check if notifications are enabled in localStorage
(defun notifications-enabled-p ()
(= (ps:chain local-storage (get-item "notifications-enabled")) "true"))
;; Check if browser supports notifications
(defun notifications-supported-p ()
(not (= (typeof (ps:@ window -notification)) "undefined")))
;; Get notification permission status
(defun get-notification-permission ()
(if (notifications-supported-p)
(ps:@ -notification permission)
"denied"))
;; Request notification permission from user
(defun request-notification-permission ()
(when (notifications-supported-p)
(ps:chain -notification (request-permission)
(then (lambda (permission)
(if (= permission "granted")
(progn
(ps:chain local-storage (set-item "notifications-enabled" "true"))
(update-notification-toggle-ui)
(show-track-notification "Notifications Enabled" "You'll now receive track change notifications"))
(progn
(ps:chain local-storage (set-item "notifications-enabled" "false"))
(update-notification-toggle-ui))))))))
;; Toggle notifications on/off
(defun toggle-notifications ()
(let ((permission (get-notification-permission)))
(cond
;; Not supported
((not (notifications-supported-p))
(alert "Your browser does not support notifications"))
;; Permission denied - can't do anything
((= permission "denied")
(alert "Notifications are blocked. Please enable them in your browser settings."))
;; Permission not yet requested
((= permission "default")
(request-notification-permission))
;; Permission granted - toggle the setting
((= permission "granted")
(if (notifications-enabled-p)
(progn
(ps:chain local-storage (set-item "notifications-enabled" "false"))
(update-notification-toggle-ui))
(progn
(ps:chain local-storage (set-item "notifications-enabled" "true"))
(update-notification-toggle-ui)
(show-track-notification "Notifications Enabled" "You'll now receive track change notifications")))))))
;; Update the notification toggle button UI
(defun update-notification-toggle-ui ()
(let ((btn (ps:chain document (get-element-by-id "notification-toggle"))))
(when btn
(let ((permission (get-notification-permission))
(enabled (notifications-enabled-p)))
(cond
((not (notifications-supported-p))
(setf (ps:@ btn text-content) "🔕")
(setf (ps:@ btn title) "Notifications not supported"))
((= permission "denied")
(setf (ps:@ btn text-content) "🔕")
(setf (ps:@ btn title) "Notifications blocked - enable in browser settings"))
((and (= permission "granted") enabled)
(setf (ps:@ btn text-content) "🔔")
(setf (ps:@ btn title) "Track notifications ON - click to disable"))
(t
(setf (ps:@ btn text-content) "🔕")
(setf (ps:@ btn title) "Track notifications OFF - click to enable")))))))
;; Show a system notification for track change
(defun show-track-notification (title body)
(when (and (notifications-supported-p)
(= (get-notification-permission) "granted")
(notifications-enabled-p)
(not (= title *last-notified-title*)))
(setf *last-notified-title* title)
(ps:try
(let ((notification (ps:new (-notification title
(ps:create :body body
:icon "/asteroid/static/asteroid.png"
:tag "asteroid-track-change"
:renotify true
:silent false)))))
;; Auto-close after 5 seconds
(set-timeout (lambda () (ps:chain notification (close))) 5000)
;; Click to focus the window
(setf (ps:@ notification onclick)
(lambda ()
(ps:chain window (focus))
(ps:chain notification (close)))))
(:catch (e)
(ps:chain console (log "Notification error:" e))))))
;; Notify track change (called from update-mini-now-playing)
(defun notify-track-change (title)
(when (and title
(not (= title ""))
(not (= title "Loading..."))
(not (= title *last-notified-title*)))
;; Show full "Artist - Track" in title, "Now Playing" as body
(show-track-notification title "Now Playing on Asteroid Radio")))
;; Cache of user's favorite track titles for quick lookup (mini player)
(defvar *user-favorites-cache-mini* (array))
;; Cache of user's favorite track titles for quick lookup (mini player)
(defvar *user-favorites-cache-mini* (array))
;; Load user's favorites into cache (mini player - only if logged in)
(defun load-favorites-cache-mini ()
@ -517,10 +409,9 @@
(track-id-el (ps:chain document (get-element-by-id "current-track-id-mini")))
(title (or (ps:@ data data title) (ps:@ data title) "Loading...")))
(when el
;; Check if track changed and record to history + notify
;; Check if track changed and record to history
(when (not (= (ps:@ el text-content) title))
(record-track-listen title)
(notify-track-change title))
(record-track-listen title))
(setf (ps:@ el text-content) title)
;; Check if this track is in user's favorites
(check-favorite-status-mini))
@ -965,9 +856,6 @@
;; Update quality selector state based on channel
(update-quality-selector-state)
;; Initialize notification toggle UI
(update-notification-toggle-ui)
;; Poll server for channel name changes (works across all listeners)
(let ((last-channel-name nil))
(set-interval
@ -1043,21 +931,17 @@
(ps:chain window (add-event-listener "beforeunload" notify-popout-closing)))))
;; Make functions globally accessible
(setf (ps:@ window get-stream-config) get-stream-config)
(setf (ps:@ window change-channel) change-channel)
(setf (ps:@ window sync-channel-from-storage) sync-channel-from-storage)
(setf (ps:@ window change-stream-quality) change-stream-quality)
(setf (ps:@ window reconnect-stream) reconnect-stream)
(setf (ps:@ window disable-frameset-mode) disable-frameset-mode)
(setf (ps:@ window init-persistent-player) init-persistent-player)
(setf (ps:@ window init-popout-player) init-popout-player)
(setf (ps:@ window update-mini-now-playing) update-mini-now-playing)
(setf (ps:@ window update-popout-now-playing) update-popout-now-playing)
(setf (ps:@ window toggle-favorite-mini) toggle-favorite-mini)
(setf (ps:@ window toggle-notifications) toggle-notifications)
(setf (ps:@ window update-notification-toggle-ui) update-notification-toggle-ui)
(setf (ps:@ window notify-popout-opened) notify-popout-opened)
(setf (ps:@ window notify-popout-closing) notify-popout-closing)
(setf (ps:@ window get-stream-config) get-stream-config)
(setf (ps:@ window change-channel) change-channel)
(setf (ps:@ window sync-channel-from-storage) sync-channel-from-storage)
(setf (ps:@ window change-stream-quality) change-stream-quality)
(setf (ps:@ window reconnect-stream) reconnect-stream)
(setf (ps:@ window disable-frameset-mode) disable-frameset-mode)
(setf (ps:@ window init-persistent-player) init-persistent-player)
(setf (ps:@ window init-popout-player) init-popout-player)
(setf (ps:@ window update-mini-now-playing) update-mini-now-playing)
(setf (ps:@ window update-popout-now-playing) update-popout-now-playing)
(setf (ps:@ window toggle-favorite-mini) toggle-favorite-mini)
;; Auto-initialize on DOMContentLoaded based on which elements exist
(ps:chain document

View File

@ -1388,21 +1388,6 @@ body.persistent-player-container .persistent-reconnect-btn:hover{
background: #2a3441;
}
body.persistent-player-container .persistent-notification-btn{
background: transparent;
color: #00ff00;
border: 1px solid #00ff00;
padding: 5px 8px;
cursor: pointer;
font-size: 1em;
white-space: nowrap;
margin-right: 5px;
}
body.persistent-player-container .persistent-notification-btn:hover{
background: #2a3441;
}
body.persistent-player-container .persistent-disable-btn{
background: transparent;
color: #00ff00;

View File

@ -1122,19 +1122,6 @@
((:and .persistent-reconnect-btn :hover)
:background "#2a3441")
(.persistent-notification-btn
:background transparent
:color "#00ff00"
:border "1px solid #00ff00"
:padding "5px 8px"
:cursor "pointer"
:font-size "1em"
:white-space nowrap
:margin-right "5px")
((:and .persistent-notification-btn :hover)
:background "#2a3441")
(.persistent-disable-btn
:background transparent
:color "#00ff00"

View File

@ -46,8 +46,6 @@
</button>
<input type="hidden" id="current-track-id-mini" value="">
<button id="notification-toggle" onclick="toggleNotifications()" class="persistent-notification-btn" title="Track notifications OFF - click to enable">🔕</button>
<button id="reconnect-btn" onclick="reconnectStream()" class="persistent-reconnect-btn" title="Reconnect if audio stops working">
<img src="/asteroid/static/icons/sync.png" alt="Reconnect" style="width: 18px; height: 18px; vertical-align: middle; filter: invert(48%) sepia(79%) saturate(2476%) hue-rotate(86deg) brightness(118%) contrast(119%);">
</button>