diff --git a/frontend-partials.lisp b/frontend-partials.lisp index a38aa63..aad4d31 100644 --- a/frontend-partials.lisp +++ b/frontend-partials.lisp @@ -1,56 +1,50 @@ (in-package :asteroid) (defun icecast-now-playing (icecast-base-url) + "Fetch now-playing information from Icecast server. + + ICECAST-BASE-URL - Base URL of the Icecast server (e.g. http://localhost:8000) + + Returns a plist with :listenurl, :title, and :listeners, or NIL on error." (let* ((icecast-url (format nil "~a/admin/stats.xml" icecast-base-url)) (response (drakma:http-request icecast-url :want-stream nil :basic-authorization '("admin" "asteroid_admin_2024")))) (format t "DEBUG: Fetching Icecast stats from ~a~%" icecast-url) (when response - (let ((xml-string (if (stringp response) - response - (babel:octets-to-string response :encoding :utf-8)))) - ;; Simple XML parsing to extract source information and aggregate listeners - ;; Get title from main mp3 stream - (let* ((mp3-match (cl-ppcre:scan "" xml-string)) - (title (if mp3-match - (let* ((source-section (subseq xml-string mp3-match - (or (cl-ppcre:scan "" xml-string :start mp3-match) - (length xml-string)))) - (titlep (cl-ppcre:all-matches "" source-section))) - (if titlep - (cl-ppcre:regex-replace-all ".*<title>(.*?).*" source-section "\\1") - "Unknown")) - "Unknown")) - ;; Aggregate listeners from all three streams - (total-listeners 0)) - ;; Count listeners from each mount point - (dolist (mount '("/asteroid\\.mp3" "/asteroid\\.aac" "/asteroid-low\\.mp3")) - (let ((match-pos (cl-ppcre:scan (format nil "" mount) xml-string))) - (when match-pos - (let* ((source-section (subseq xml-string match-pos - (or (cl-ppcre:scan "" xml-string :start match-pos) - (length xml-string)))) - (listenersp (cl-ppcre:all-matches "" source-section))) - (when listenersp - (let ((listener-count-str (cl-ppcre:regex-replace-all ".*(.*?).*" source-section "\\1")) - (count (parse-integer (cl-ppcre:regex-replace-all ".*(.*?).*" source-section "\\1") :junk-allowed t))) - (format t "DEBUG: Mount ~a has ~a listeners~%" mount count) - (incf total-listeners count))))))) - (let ((result `((:listenurl . ,(format nil "~a/asteroid.mp3" *stream-base-url*)) - (:title . ,title) - (:listeners . ,total-listeners)))) - (format t "DEBUG: Parsed title=~a, total-listeners=~a~%" title total-listeners) - result)))))) + (let ((xml-string (if (stringp response) + response + (babel:octets-to-string response :encoding :utf-8)))) + ;; Extract total listener count from root tag (sums all mount points) + ;; Extract title from asteroid.mp3 mount point + (let* ((total-listeners (multiple-value-bind (match groups) + (cl-ppcre:scan-to-strings "(\\d+)" xml-string) + (if (and match groups) + (parse-integer (aref groups 0) :junk-allowed t) + 0))) + ;; Get title from asteroid.mp3 mount point + (mount-start (cl-ppcre:scan "" xml-string)) + (title (if mount-start + (let* ((source-section (subseq xml-string mount-start + (or (cl-ppcre:scan "" xml-string :start mount-start) + (length xml-string))))) + (multiple-value-bind (match groups) + (cl-ppcre:scan-to-strings "(.*?)" source-section) + (if (and match groups) + (aref groups 0) + "Unknown"))) + "Unknown"))) + (format t "DEBUG: Parsed title=~a, total-listeners=~a~%" title total-listeners) + `((:listenurl . ,(format nil "~a/asteroid.mp3" *stream-base-url*)) + (:title . ,title) + (:listeners . ,total-listeners))))))) (define-api asteroid/partial/now-playing () () "Get Partial HTML with live status from Icecast server" - (handler-case + (with-error-handling (let ((now-playing-stats (icecast-now-playing *stream-base-url*))) (if now-playing-stats (progn - ;; TODO: it should be able to define a custom api-output for this - ;; (api-output :format "html")) (setf (header "Content-Type") "text/html") (clip:process-to-string (load-template "partial/now-playing") @@ -60,14 +54,7 @@ (clip:process-to-string (load-template "partial/now-playing") :connection-error t - :stats nil)))) - (error () - (format t "Error in now-playing endpoint~%") - (setf (header "Content-Type") "text/html") - (clip:process-to-string - (load-template "partial/now-playing") - :connection-error t - :stats nil)))) + :stats nil)))))) (define-api asteroid/partial/now-playing-inline () () "Get inline text with now playing info (for admin dashboard and widgets)"