;;;; recently-played.lisp - ParenScript version of recently-played.js ;;;; Recently Played Tracks functionality (in-package #:asteroid) (defparameter *recently-played-js* (ps:ps (progn ;; Get current mount from stream quality selection ;; Checks local selector first, then sibling player-frame (for frameset mode) (defun get-current-mount-for-recently-played () (let* ((selector (ps:chain document (get-element-by-id "stream-quality"))) ;; If no local selector, try to get from sibling player-frame (frameset mode) (player-frame-selector (when (and (not selector) (not (= (ps:@ window parent) window))) (ps:try (let ((player-frame (ps:@ (ps:@ window parent) frames "player-frame"))) (when player-frame (ps:chain player-frame document (get-element-by-id "stream-quality")))) (:catch (e) nil)))) (effective-selector (or selector player-frame-selector)) (quality (or (when effective-selector (ps:@ effective-selector value)) (ps:chain local-storage (get-item "stream-quality")) "aac"))) (cond ((= quality "shuffle") "asteroid-shuffle.mp3") ((= quality "low") "asteroid-low.mp3") ((= quality "mp3") "asteroid.mp3") (t "asteroid.aac")))) ;; Update recently played tracks display (defun update-recently-played () (let ((mount (get-current-mount-for-recently-played))) (ps:chain (fetch (+ "/api/asteroid/recently-played?mount=" mount)) (then (lambda (response) (ps:chain response (json)))) (then (lambda (result) ;; Radiance wraps API responses in a data envelope (let ((data (or (ps:@ result data) result))) (if (and (equal (ps:@ data status) "success") (ps:@ data tracks) (> (ps:@ data tracks length) 0)) (let ((list-el (ps:chain document (get-element-by-id "recently-played-list")))) (when list-el ;; Build HTML for tracks (let ((html "
No tracks played yet
"))))))) (catch (lambda (error) (ps:chain console (error "Error fetching recently played:" error)) (let ((list-el (ps:chain document (get-element-by-id "recently-played-list")))) (when list-el (setf (aref list-el "innerHTML") "Error loading recently played tracks
")))))))) ;; Format timestamp as relative time (defun format-time-ago (timestamp) (let* ((now (floor (/ (ps:chain *date (now)) 1000))) (diff (- now timestamp))) (cond ((< diff 60) "Just now") ((< diff 3600) (+ (floor (/ diff 60)) "m ago")) ((< diff 86400) (+ (floor (/ diff 3600)) "h ago")) (t (+ (floor (/ diff 86400)) "d ago"))))) ;; Escape HTML to prevent XSS (defun escape-html (text) (when (ps:@ window document) (let ((div (ps:chain document (create-element "div")))) (setf (ps:@ div text-content) text) (aref div "innerHTML")))) ;; Initialize on page load (when (ps:@ window document) (ps:chain document (add-event-listener "DOMContentLoaded" (lambda () (let ((panel (ps:chain document (get-element-by-id "recently-played-panel")))) (if panel (progn (update-recently-played) ;; Refresh immediately when user switches streams (let ((selector (ps:chain document (get-element-by-id "stream-quality")))) (when (not selector) (setf selector (ps:try (let ((player-frame (ps:@ (ps:@ window parent) frames "player-frame"))) (when player-frame (ps:chain player-frame document (get-element-by-id "stream-quality")))) (:catch (e) nil)))) (when selector (ps:chain selector (add-event-listener "change" (lambda (_ev) ;; Small delay so localStorage / UI state settles (ps:chain window (set-timeout update-recently-played 50))))))) ;; Update every 30 seconds (set-interval update-recently-played 30000)) (let ((list (ps:chain document (get-element-by-id "recently-played-list")))) (when list (update-recently-played) (set-interval update-recently-played 30000)))))))))) ) "Compiled JavaScript for recently played tracks - generated at load time" ) (defun generate-recently-played-js () "Generate JavaScript code for recently played tracks" *recently-played-js*)