Compare commits
5 Commits
ffd178c555
...
2fa72117cd
| Author | SHA1 | Date |
|---|---|---|
|
|
2fa72117cd | |
|
|
d9f2ac58fc | |
|
|
6a3917bb12 | |
|
|
d4fa384204 | |
|
|
62cad7b2ad |
|
|
@ -33,8 +33,8 @@
|
||||||
|
|
||||||
(unless (db:collection-exists-p "playlist_tracks")
|
(unless (db:collection-exists-p "playlist_tracks")
|
||||||
(db:create "playlist_tracks" '((track_id :integer)
|
(db:create "playlist_tracks" '((track_id :integer)
|
||||||
(position :ingeger)
|
(position :integer)
|
||||||
(added_date :timestamp))))
|
(added_date :integer))))
|
||||||
|
|
||||||
;; TODO: the radiance db interface is too basic to contain anything
|
;; TODO: the radiance db interface is too basic to contain anything
|
||||||
;; but strings, integers, booleans, and maybe timestamps... we will
|
;; but strings, integers, booleans, and maybe timestamps... we will
|
||||||
|
|
@ -45,5 +45,5 @@
|
||||||
;; (unless (db:collection-exists-p "sessions")
|
;; (unless (db:collection-exists-p "sessions")
|
||||||
;; (db:create "sessions" '(())))
|
;; (db:create "sessions" '(())))
|
||||||
|
|
||||||
(l:info "~2&Database collections initialized~%"))
|
(format t "~2&Database collections initialized~%"))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
^(in-package :asteroid)
|
(in-package :asteroid)
|
||||||
|
|
||||||
(defun icecast-now-playing (icecast-base-url)
|
(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))
|
(let* ((icecast-url (format nil "~a/admin/stats.xml" icecast-base-url))
|
||||||
(response (drakma:http-request icecast-url
|
(response (drakma:http-request icecast-url
|
||||||
:want-stream nil
|
:want-stream nil
|
||||||
|
|
@ -9,29 +14,32 @@
|
||||||
(let ((xml-string (if (stringp response)
|
(let ((xml-string (if (stringp response)
|
||||||
response
|
response
|
||||||
(babel:octets-to-string response :encoding :utf-8))))
|
(babel:octets-to-string response :encoding :utf-8))))
|
||||||
;; Simple XML parsing to extract source information
|
;; Extract total listener count from root <listeners> tag (sums all mount points)
|
||||||
;; Look for <source mount="/asteroid.mp3"> sections and extract title, listeners, etc.
|
;; Extract title from asteroid.mp3 mount point
|
||||||
(multiple-value-bind (match-start match-end)
|
(let* ((total-listeners (multiple-value-bind (match groups)
|
||||||
(cl-ppcre:scan "<source mount=\"/asteroid\\.mp3\">" xml-string)
|
(cl-ppcre:scan-to-strings "<listeners>(\\d+)</listeners>" xml-string)
|
||||||
|
(if (and match groups)
|
||||||
(if match-start
|
(parse-integer (aref groups 0) :junk-allowed t)
|
||||||
(let* ((source-section (subseq xml-string match-start
|
0)))
|
||||||
(or (cl-ppcre:scan "</source>" xml-string :start match-start)
|
;; Get title from asteroid.mp3 mount point
|
||||||
(length xml-string))))
|
(mount-start (cl-ppcre:scan "<source mount=\"/asteroid\\.mp3\">" xml-string))
|
||||||
(titlep (cl-ppcre:all-matches "<title>" source-section))
|
(title (if mount-start
|
||||||
(listenersp (cl-ppcre:all-matches "<listeners>" source-section))
|
(let* ((source-section (subseq xml-string mount-start
|
||||||
(title (if titlep (cl-ppcre:regex-replace-all ".*<title>(.*?)</title>.*" source-section "\\1") "Unknown"))
|
(or (cl-ppcre:scan "</source>" xml-string :start mount-start)
|
||||||
(listeners (if listenersp (cl-ppcre:regex-replace-all ".*<listeners>(.*?)</listeners>.*" source-section "\\1") "0")))
|
(length xml-string)))))
|
||||||
`((:listenurl . ,(format nil "~a/asteroid.mp3" *stream-base-url*))
|
(multiple-value-bind (match groups)
|
||||||
(:title . ,title)
|
(cl-ppcre:scan-to-strings "<title>(.*?)</title>" source-section)
|
||||||
(:listeners . ,(parse-integer listeners :junk-allowed t))))
|
(if (and match groups)
|
||||||
`((:listenurl . ,(format nil "~a/asteroid.mp3" *stream-base-url*))
|
(aref groups 0)
|
||||||
(:title . "Unknown")
|
"Unknown")))
|
||||||
(:listeners . "Unknown"))))))))
|
"Unknown")))
|
||||||
|
`((:listenurl . ,(format nil "~a/asteroid.mp3" *stream-base-url*))
|
||||||
|
(:title . ,title)
|
||||||
|
(:listeners . ,total-listeners)))))))
|
||||||
|
|
||||||
(define-api asteroid/partial/now-playing () ()
|
(define-api asteroid/partial/now-playing () ()
|
||||||
"Get Partial HTML with live status from Icecast server"
|
"Get Partial HTML with live status from Icecast server"
|
||||||
(handler-case
|
(with-error-handling
|
||||||
(let ((now-playing-stats (icecast-now-playing *stream-base-url*)))
|
(let ((now-playing-stats (icecast-now-playing *stream-base-url*)))
|
||||||
(if now-playing-stats
|
(if now-playing-stats
|
||||||
(progn
|
(progn
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ body .container{
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body strong{
|
||||||
|
letter-spacing: 0.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
body h1{
|
body h1{
|
||||||
color: #4488ff;
|
color: #4488ff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
@ -180,11 +184,17 @@ body .player-section .live-stream{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
body .live-stream p,
|
||||||
|
body .live-stream label{
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .live-stream code{
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
body .live-stream .live-stream-quality label,
|
body .live-stream .live-stream-quality{
|
||||||
body .live-stream .live-stream-quality select{
|
display: flex;
|
||||||
margin: 10px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body .live-stream .live-stream-quality label{
|
body .live-stream .live-stream-quality label{
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
:max-width "1200px"
|
:max-width "1200px"
|
||||||
:margin "0 auto")
|
:margin "0 auto")
|
||||||
|
|
||||||
|
(strong :letter-spacing "0.08rem")
|
||||||
|
|
||||||
(h1
|
(h1
|
||||||
:color "#4488ff"
|
:color "#4488ff"
|
||||||
:text-align center
|
:text-align center
|
||||||
|
|
@ -153,9 +155,13 @@
|
||||||
:overflow auto) )
|
:overflow auto) )
|
||||||
|
|
||||||
(.live-stream
|
(.live-stream
|
||||||
|
((:or p label) :font-size "1.5rem")
|
||||||
|
|
||||||
|
(code :font-size "1rem")
|
||||||
|
|
||||||
(.live-stream-quality
|
(.live-stream-quality
|
||||||
|
|
||||||
((:or label select) :margin "10px 0")
|
:display "flex"
|
||||||
|
|
||||||
(label
|
(label
|
||||||
:margin-right "10px")
|
:margin-right "10px")
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🎛️ ADMIN DASHBOARD</h1>
|
<h1>🎛️ ADMIN DASHBOARD</h1>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<a href="/asteroid" target="content-frame">Home</a>
|
<a href="/asteroid">Home</a>
|
||||||
<a href="/asteroid/player" target="content-frame">Player</a>
|
<a href="/asteroid/player">Player</a>
|
||||||
<a href="/asteroid/profile" target="content-frame">Profile</a>
|
<a href="/asteroid/profile">Profile</a>
|
||||||
<a href="/asteroid/admin/users">👥 Users</a>
|
<a href="/asteroid/admin/users">👥 Users</a>
|
||||||
<a href="/asteroid/logout" class="btn-logout">Logout</a>
|
<a href="/asteroid/logout" class="btn-logout">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,13 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="status">
|
|
||||||
<h2>Station Status</h2>
|
|
||||||
<p data-text="status-message">🟢 LIVE - Broadcasting asteroid music for hackers</p>
|
|
||||||
<p>Current listeners: <span data-text="listeners">0</span></p>
|
|
||||||
<p>Stream quality: <span data-text="stream-quality">AAC 96kbps Stereo</span></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="live-stream">
|
<div class="live-stream">
|
||||||
<h2 style="color: #00ff00;">🟢 LIVE STREAM</h2>
|
<h2 style="color: #00ff00;">🟢 LIVE STREAM</h2>
|
||||||
<p><em>The live stream player is now in the persistent bar at the bottom of the page.</em></p>
|
|
||||||
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
|
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
|
||||||
<p><strong>Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p>
|
<p><strong>Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p>
|
||||||
<p><strong>Format:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p>
|
<p><strong>Stream Quality:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p>
|
||||||
<p><strong>Status:</strong> <span id="stream-status" style="color: #00ff00;">● BROADCASTING</span></p>
|
<p><strong>BROADCASTING:</strong> <span id="stream-status" style="">Asteroid music for Hackers on The Station at the End of Time.</span></p>
|
||||||
|
<p><em>The live stream player is now in the persistent bar at the bottom of the page.</em></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="now-playing" class="now-playing"></div>
|
<div id="now-playing" class="now-playing"></div>
|
||||||
|
|
|
||||||
|
|
@ -25,24 +25,9 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="status">
|
|
||||||
<h2>Station Status</h2>
|
|
||||||
<p data-text="status-message">🟢 LIVE - Broadcasting asteroid music for hackers</p>
|
|
||||||
<p>Current listeners: <span data-text="listeners">0</span></p>
|
|
||||||
<p>Stream quality: <span data-text="stream-quality">AAC 96kbps Stereo</span></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="live-stream">
|
<div class="live-stream">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||||
<h2 style="color: #00ff00; margin: 0;">🟢 LIVE STREAM</h2>
|
<h2 style="color: #00ff00; margin: 0;">🟢 LIVE STREAM</h2>
|
||||||
<div style="display: flex; gap: 10px;">
|
|
||||||
<button id="popout-btn" class="btn btn-info" onclick="openPopoutPlayer()" style="font-size: 0.9em;">
|
|
||||||
🗗 Pop Out Player
|
|
||||||
</button>
|
|
||||||
<button id="frameset-btn" class="btn btn-secondary" onclick="enableFramesetMode()" style="font-size: 0.9em;">
|
|
||||||
🖼️ Enable Persistent Player
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stream Quality Selector -->
|
<!-- Stream Quality Selector -->
|
||||||
|
|
@ -57,8 +42,17 @@
|
||||||
|
|
||||||
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
|
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
|
||||||
<p><strong>Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p>
|
<p><strong>Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p>
|
||||||
<p><strong>Format:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p>
|
<p><strong>Stream Quality:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p>
|
||||||
<p><strong>Status:</strong> <span id="stream-status" style="color: #00ff00;">● BROADCASTING</span></p>
|
<p><strong>BROADCASTING:</strong> <span id="stream-status" style="">Asteroid music for Hackers on The Station at the End of Time.</span></p>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 10px; justify-content: end; margin-bottom: 20px;">
|
||||||
|
<button id="popout-btn" class="btn btn-info" onclick="openPopoutPlayer()" style="font-size: 0.9em;">
|
||||||
|
🗗 Pop Out Player
|
||||||
|
</button>
|
||||||
|
<button id="frameset-btn" class="btn btn-secondary" onclick="enableFramesetMode()" style="font-size: 0.9em;">
|
||||||
|
🖼️ Enable Persistent Player
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<audio id="live-audio" controls style="width: 100%; margin: 10px 0;">
|
<audio id="live-audio" controls style="width: 100%; margin: 10px 0;">
|
||||||
<source id="audio-source" lquery="(attr :src default-stream-url :type default-stream-encoding)">
|
<source id="audio-source" lquery="(attr :src default-stream-url :type default-stream-encoding)">
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
<header>
|
<header>
|
||||||
<h1>🎵 ASTEROID RADIO - LOGIN</h1>
|
<h1>🎵 ASTEROID RADIO - LOGIN</h1>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<a href="/asteroid/content" target="content-frame">Home</a>
|
<a href="/asteroid">Home</a>
|
||||||
<a href="/asteroid/player-content" target="content-frame">Player</a>
|
<a href="/asteroid/player">Player</a>
|
||||||
<a href="/asteroid/status" target="content-frame">Status</a>
|
<a href="/asteroid/status">Status</a>
|
||||||
<a href="/asteroid/register">Register</a>
|
<a href="/asteroid/register">Register</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🎵 WEB PLAYER</h1>
|
<h1>🎵 WEB PLAYER</h1>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<a href="/asteroid/content" target="content-frame">Home</a>
|
<a href="/asteroid">Home</a>
|
||||||
<a href="/asteroid/profile" target="content-frame" data-show-if-logged-in>Profile</a>
|
<a href="/asteroid/profile">Profile</a>
|
||||||
<a href="/asteroid/admin" target="content-frame" data-show-if-admin>Admin</a>
|
<a href="/asteroid/admin">Admin</a>
|
||||||
<a href="/asteroid/login" data-show-if-logged-out>Login</a>
|
<a href="/asteroid/login" data-show-if-logged-out>Login</a>
|
||||||
<a href="/asteroid/register" data-show-if-logged-out>Register</a>
|
<a href="/asteroid/register" data-show-if-logged-out>Register</a>
|
||||||
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
|
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<audio id="live-stream-audio" controls style="width: 100%; margin: 10px 0;">
|
<audio id="live-stream-audio" controls style="width: 100%; margin-top: 20px;">
|
||||||
<source id="live-stream-source" lquery="(attr :src default-stream-url)" type="audio/aac">
|
<source id="live-stream-source" lquery="(attr :src default-stream-url)" type="audio/aac">
|
||||||
Your browser does not support the audio element.
|
Your browser does not support the audio element.
|
||||||
</audio>
|
</audio>
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>👤 USER PROFILE</h1>
|
<h1>👤 USER PROFILE</h1>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<a href="/asteroid" target="content-frame">Home</a>
|
<a href="/asteroid">Home</a>
|
||||||
<a href="/asteroid/player" target="content-frame">Player</a>
|
<a href="/asteroid/player">Player</a>
|
||||||
<a href="/asteroid/admin" target="content-frame" data-show-if-admin>Admin</a>
|
<a href="/asteroid/admin">Admin</a>
|
||||||
<a href="/asteroid/logout" class="btn-logout">Logout</a>
|
<a href="/asteroid/logout" class="btn-logout">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
<header>
|
<header>
|
||||||
<h1>🎵 ASTEROID RADIO - REGISTER</h1>
|
<h1>🎵 ASTEROID RADIO - REGISTER</h1>
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<a href="/asteroid/content" target="content-frame">Home</a>
|
<a href="/asteroid">Home</a>
|
||||||
<a href="/asteroid/player-content" target="content-frame">Player</a>
|
<a href="/asteroid/player">Player</a>
|
||||||
<a href="/asteroid/status" target="content-frame">Status</a>
|
<a href="/asteroid/status">Status</a>
|
||||||
<a href="/asteroid/login">Login</a>
|
<a href="/asteroid/login">Login</a>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>👥 USER MANAGEMENT</h1>
|
<h1>👥 USER MANAGEMENT</h1>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<a href="/asteroid" target="content-frame">Home</a>
|
<a href="/asteroid">Home</a>
|
||||||
<a href="/asteroid/admin" target="content-frame">Admin</a>
|
<a href="/asteroid/admin">Admin</a>
|
||||||
<a href="/asteroid/logout" class="btn-logout">Logout</a>
|
<a href="/asteroid/logout" class="btn-logout">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue