Compare commits

..

No commits in common. "ff651e6a362e4f27a5056848c3bfa6450e914f12" and "151f6c5569b267b0560585e3abdb13ac0810f2c7" have entirely different histories.

9 changed files with 530 additions and 209 deletions

View File

@ -853,8 +853,7 @@
(define-page front-page-content #@"/content" () (define-page front-page-content #@"/content" ()
"Front page content (displayed in content frame)" "Front page content (displayed in content frame)"
(clip:process-to-string (clip:process-to-string
(load-template "front-page") (load-template "front-page-content")
:framesetp t
:title "ASTEROID RADIO" :title "ASTEROID RADIO"
:station-name "ASTEROID RADIO" :station-name "ASTEROID RADIO"
:status-message "🟢 LIVE - Broadcasting asteroid music for hackers" :status-message "🟢 LIVE - Broadcasting asteroid music for hackers"
@ -1285,8 +1284,7 @@
(define-page-with-limit player-content #@"/player-content" (:limit-group "public") (define-page-with-limit player-content #@"/player-content" (:limit-group "public")
"Player page content (displayed in content frame)" "Player page content (displayed in content frame)"
(clip:process-to-string (clip:process-to-string
(load-template "player") (load-template "player-content")
:framesetp t
:title "Asteroid Radio - Web Player" :title "Asteroid Radio - Web Player"
:stream-base-url *stream-base-url* :stream-base-url *stream-base-url*
:default-stream-url (format nil "~a/asteroid.aac" *stream-base-url*) :default-stream-url (format nil "~a/asteroid.aac" *stream-base-url*)
@ -1312,22 +1310,14 @@
(define-page-with-limit about-content #@"/about-content" (:limit-group "public") (define-page-with-limit about-content #@"/about-content" (:limit-group "public")
"About page content (displayed in content frame)" "About page content (displayed in content frame)"
(clip:process-to-string (clip:process-to-string
(load-template "about") (load-template "about-content")
:framesetp t
:title "About - Asteroid Radio")) :title "About - Asteroid Radio"))
(define-page-with-limit status-page #@"/status" (:limit-group "public")
"Status page content"
(clip:process-to-string
(load-template "status")
:title "Status - Asteroid Radio"))
;; Status content (for frameset mode) ;; Status content (for frameset mode)
(define-page-with-limit status-content #@"/status-content" (:limit-group "public") (define-page-with-limit status-content #@"/status-content" (:limit-group "public")
"Status page content (displayed in content frame)" "Status page content (displayed in content frame)"
(clip:process-to-string (clip:process-to-string
(load-template "status") (load-template "status-content")
:framesetp t
:title "Status - Asteroid Radio")) :title "Status - Asteroid Radio"))
(define-api-with-limit asteroid/status () () (define-api-with-limit asteroid/status () ()

106
template/about-content.ctml Normal file
View File

@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>About - Asteroid Radio</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
</head>
<body>
<div class="container">
<header>
<h1 style="display: flex; align-items: center; justify-content: center; gap: 15px;">
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
<span>ABOUT ASTEROID RADIO</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
</h1>
<nav class="nav">
<a href="/asteroid/content" target="_self">Home</a>
<a href="/asteroid/player-content" target="_self">Player</a>
<a href="/asteroid/about-content" target="_self">About</a>
<a href="/asteroid/status" target="_self">Status</a>
<a href="/asteroid/profile" target="_self" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="_self" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="_self" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="_self" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</nav>
</header>
<main style="max-width: 800px; margin: 0 auto; padding: 20px;">
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">🎵 Asteroid Music for Hackers</h2>
<p style="line-height: 1.6;">
Asteroid Radio is a community-driven internet radio station born from the SystemCrafters community.
We celebrate the intersection of music, technology, and hacker culture—broadcasting for those who
appreciate both great code and great music.
</p>
<p style="line-height: 1.6;">
We met through a shared set of technical biases and a love for building systems from first principles.
Asteroid Radio embodies that ethos: <strong>music for hackers, built by hackers</strong>.
</p>
</section>
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">🛠️ Built with Common Lisp</h2>
<p style="line-height: 1.6;">
This entire platform is built using <strong>Common Lisp</strong>, demonstrating the power and elegance
of Lisp for modern web applications. We use:
</p>
<ul style="line-height: 1.8; margin-left: 20px;">
<li><strong><a href="https://codeberg.org/shirakumo/radiance" style="color: #00ff00;">Radiance</a></strong> - Web application framework</li>
<li><strong><a href="https://codeberg.org/shinmera/clip" style="color: #00ff00;">Clip</a></strong> - HTML5-compliant template engine</li>
<li><strong><a href="https://codeberg.org/shinmera/LASS" style="color: #00ff00;">LASS</a></strong> - Lisp Augmented Style Sheets</li>
<li><strong><a href="https://gitlab.common-lisp.net/parenscript/parenscript" style="color: #00ff00;">ParenScript</a></strong> - Lisp-to-JavaScript compiler</li>
<li><strong><a href="https://icecast.org/" style="color: #00ff00;">Icecast</a></strong> - Streaming media server</li>
<li><strong><a href="https://www.liquidsoap.info/" style="color: #00ff00;">Liquidsoap</a></strong> - Audio stream generation</li>
</ul>
<p style="line-height: 1.6;">
By building in Common Lisp, we're doubling down on our technical values and creating features
for "our people"—those who appreciate the elegance of Lisp and the power of understanding your tools deeply.
</p>
</section>
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">📖 Open Source & AGPL Licensed</h2>
<p style="line-height: 1.6;">
Asteroid Radio is <strong>free and open source software</strong>, licensed under the
<strong><a href="https://www.gnu.org/licenses/agpl-3.0.en.html" style="color: #00ff00;">GNU Affero General Public License v3.0 (AGPL)</a></strong>.
</p>
<p style="line-height: 1.6;">
The source code is available at:
<a href="https://github.com/Fade/asteroid" style="color: #00ff00; font-weight: bold;">https://github.com/Fade/asteroid</a>
</p>
<p style="line-height: 1.6;">
We believe in transparency, collaboration, and the freedom to study, modify, and share the software we use.
The AGPL ensures that improvements to Asteroid Radio remain free and available to everyone.
</p>
</section>
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">🎧 Features</h2>
<ul style="line-height: 1.8; margin-left: 20px;">
<li><strong>Live Streaming</strong> - Multiple quality options (AAC, MP3)</li>
<li><strong>Persistent Player</strong> - Frameset mode for uninterrupted playback while browsing</li>
<li><strong>Spectrum Analyzer</strong> - Real-time audio visualization with customizable themes</li>
<li><strong>Track Library</strong> - Browse and search the music collection</li>
<li><strong>User Profiles</strong> - Track your listening history</li>
<li><strong>Admin Tools</strong> - Manage tracks, users, and playlists</li>
</ul>
</section>
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">🤝 Community</h2>
<p style="line-height: 1.6;">
We're part of the <strong><a href="https://systemcrafters.net/" style="color: #00ff00;">SystemCrafters</a></strong>
community—a group of developers, hackers, and enthusiasts who believe in building systems from first principles,
understanding our tools deeply, and sharing knowledge freely.
</p>
<p style="line-height: 1.6;">
Join us in celebrating the intersection of great music and great code!
</p>
</section>
</main>
</div>
</body>
</html>

View File

@ -18,32 +18,16 @@
<span>ABOUT ASTEROID RADIO</span> <span>ABOUT ASTEROID RADIO</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;"> <img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
</h1> </h1>
<nav class="nav"> <nav class="nav">
<c:if test="(not framesetp)"> <a href="/asteroid/">Home</a>
<c:then> <a href="/asteroid/player">Player</a>
<a href="/asteroid/">Home</a> <a href="/asteroid/about">About</a>
<a href="/asteroid/player">Player</a> <a href="/asteroid/status">Status</a>
<a href="/asteroid/about">About</a> <a href="/asteroid/profile" data-show-if-logged-in>Profile</a>
<a href="/asteroid/status">Status</a> <a href="/asteroid/admin" data-show-if-admin>Admin</a>
<a href="/asteroid/profile" data-show-if-logged-in>Profile</a> <a href="/asteroid/login" data-show-if-logged-out>Login</a>
<a href="/asteroid/admin" data-show-if-admin>Admin</a> <a href="/asteroid/register" data-show-if-logged-out>Register</a>
<a href="/asteroid/login" data-show-if-logged-out>Login</a> <a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</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>
</c:then>
<c:else>
<a href="/asteroid/content" target="_self">Home</a>
<a href="/asteroid/player-content" target="_self">Player</a>
<a href="/asteroid/about-content" target="_self">About</a>
<a href="/asteroid/status-content" target="_self">Status</a>
<a href="/asteroid/profile" target="_self" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="_self" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="_self" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="_self" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</c:else>
</c:if>
</nav> </nav>
</header> </header>
<main style="max-width: 800px; margin: 0 auto; padding: 20px;"> <main style="max-width: 800px; margin: 0 auto; padding: 20px;">

View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title data-text="title">ASTEROID RADIO</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="/asteroid/static/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/asteroid/static/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/asteroid/static/favicon-16x16.png">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/front-page.js"></script>
<script src="/asteroid/static/js/recently-played.js"></script>
<script src="/api/asteroid/spectrum-analyzer.js"></script>
</head>
<body>
<div class="container">
<header>
<h1 class="page-title">
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 60px; width: auto;">
<span data-text="station-name">ASTEROID RADIO</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 60px; width: auto;">
</h1>
<h3 class="page-subtitle">The Station at the End of Time</h3>
<!-- Spectrum Analyzer Canvas -->
<div style="text-align: center; margin: 15px 0;">
<canvas id="spectrum-canvas" width="800" height="100" style="max-width: 100%; border: 1px solid #00ff00; background: #000; border-radius: 4px;"></canvas>
<div style="margin-top: 8px; font-size: 0.9em;">
<label style="margin-right: 10px;">
Style:
<select id="spectrum-style-selector" onchange="setSpectrumStyle(this.value)" style="padding: 3px; background: #000; color: #00ff00; border: 1px solid #00ff00;">
<option value="bars">Bars</option>
<option value="wave">Wave</option>
<option value="dots">Dots</option>
</select>
</label>
<label>
Theme:
<select id="spectrum-theme-selector" onchange="setSpectrumTheme(this.value)" style="padding: 3px; background: #000; color: #00ff00; border: 1px solid #00ff00;">
<option value="monotone">Monotone</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="purple">Purple</option>
<option value="red">Red</option>
<option value="amber">Amber</option>
<option value="rainbow">Rainbow</option>
</select>
</label>
</div>
</div>
<nav class="nav">
<a href="/asteroid/content" target="_self">Home</a>
<a href="/asteroid/player-content" target="_self">Player</a>
<a href="/asteroid/about-content" target="_self">About</a>
<a href="/asteroid/status-content" target="_self">Status</a>
<a href="/asteroid/profile" target="_self" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="_self" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="_self" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="_self" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</nav>
</header>
<main>
<div class="live-stream">
<h2 style="color: #00ff00; margin: 0;"><span class="live-stream-indicator" style="font-size: 1rem;">🟢</span> LIVE STREAM</h2>
<!-- Channel Selector -->
<div class="live-stream-quality" style="margin-bottom: 15px;">
<label for="stream-channel" class="live-stream-label"><strong>Channel:</strong></label>
<select id="stream-channel" onchange="changeChannel()">
<option value="curated">🎧 <c:splice lquery="(text curated-channel-name)">Curated</c:splice></option>
<option value="shuffle">🎲 Shuffle</option>
</select>
</div>
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
<p><strong class="live-stream-label">Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p>
<p><strong class="live-stream-label">Stream Quality:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p>
<p><strong class="live-stream-label">BROADCASTING:</strong> <span id="stream-status" style="">Asteroid music for Hackers</span></p>
<p class="frame-enable-message"><em>The live stream player is now in the persistent bar at the bottom of the page</em></p>
</div>
<div id="now-playing" class="now-playing"></div>
<!-- Recently Played Tracks -->
<div id="recently-played-panel" class="recently-played-panel">
<h3>Recently Played</h3>
<div id="recently-played-list" class="recently-played-list">
<p class="loading">Loading...</p>
</div>
</div>
<!-- Track Request Section -->
<div class="request-panel">
<h3>🎵 Request a Track</h3>
<p class="request-description">Want to hear something specific? Submit a request and an admin will review it.</p>
<div class="request-form">
<input type="text" id="request-title" class="request-input" placeholder="Suggest a track, artist, or album...">
<input type="text" id="request-message" class="request-input" placeholder="Why do you want to hear this? (optional)">
<button class="btn btn-primary" onclick="submitTrackRequest()">Submit Request</button>
</div>
<div id="request-status" class="request-status" style="display: none;"></div>
<div class="recent-requests">
<h4>Recently Played Requests</h4>
<div id="recent-requests-list">
<p class="no-requests">Loading...</p>
</div>
</div>
</div>
</main>
<footer class="site-footer">
<span>Listed on <a href="http://www.internet-radio.com/stations/ambient/" target="_blank" rel="noopener">Internet Radio</a></span>
<span class="craftering">
<a href="https://craftering.systemcrafters.net/@asteroid/previous">←</a>
<a href="https://craftering.systemcrafters.net/">craftering</a>
<a href="https://craftering.systemcrafters.net/@asteroid/next">→</a>
</span>
</footer>
</div>
</body>
</html>

View File

@ -51,30 +51,15 @@
</div> </div>
<nav class="nav"> <nav class="nav">
<c:if test="(not framesetp)"> <a href="/asteroid/">Home</a>
<c:then> <a href="/asteroid/player">Player</a>
<a href="/asteroid/">Home</a> <a href="/asteroid/about">About</a>
<a href="/asteroid/player">Player</a> <a href="/asteroid/status">Status</a>
<a href="/asteroid/about">About</a> <a href="/asteroid/profile" data-show-if-logged-in>Profile</a>
<a href="/asteroid/status">Status</a> <a href="/asteroid/admin" data-show-if-admin>Admin</a>
<a href="/asteroid/profile" data-show-if-logged-in>Profile</a> <a href="/asteroid/login" data-show-if-logged-out>Login</a>
<a href="/asteroid/admin" data-show-if-admin>Admin</a> <a href="/asteroid/register" data-show-if-logged-out>Register</a>
<a href="/asteroid/login" data-show-if-logged-out>Login</a> <a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</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>
</c:then>
<c:else>
<a href="/asteroid/content" target="_self">Home</a>
<a href="/asteroid/player-content" target="_self">Player</a>
<a href="/asteroid/about-content" target="_self">About</a>
<a href="/asteroid/status-content" target="_self">Status</a>
<a href="/asteroid/profile" target="_self" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="_self" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="_self" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="_self" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</c:else>
</c:if>
</nav> </nav>
</header> </header>
@ -93,53 +78,42 @@
</select> </select>
</div> </div>
<c:if test="(not framesetp)"> <!-- Stream Quality Selector -->
<c:then> <div class="live-stream-quality" style="margin-bottom: 15px;">
<!-- Stream Quality Selector --> <label for="stream-quality" class="live-stream-label" ><strong>Quality:</strong></label>
<div class="live-stream-quality" style="margin-bottom: 15px;"> <select id="stream-quality" onchange="changeStreamQuality()">
<label for="stream-quality" class="live-stream-label" ><strong>Quality:</strong></label> <option value="aac">AAC 96kbps (Recommended)</option>
<select id="stream-quality" onchange="changeStreamQuality()"> <option value="mp3">MP3 128kbps (Compatible)</option>
<option value="aac">AAC 96kbps (Recommended)</option> <option value="low">MP3 64kbps (Low Bandwidth)</option>
<option value="mp3">MP3 128kbps (Compatible)</option> </select>
<option value="low">MP3 64kbps (Low Bandwidth)</option> </div>
</select>
</div>
</c:then>
</c:if>
<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 class="live-stream-label">Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p> <p><strong class="live-stream-label">Stream URL:</strong> <code id="stream-url" lquery="(text default-stream-url)"></code></p>
<p><strong class="live-stream-label">Stream Quality:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p> <p><strong class="live-stream-label">Stream Quality:</strong> <span id="stream-format" lquery="(text default-stream-encoding-desc)"></span></p>
<p><strong class="live-stream-label" class="live-stream-label">BROADCASTING:</strong> <span id="stream-status" style="">Asteroid music for Hackers</span></p> <p><strong class="live-stream-label" class="live-stream-label">BROADCASTING:</strong> <span id="stream-status" style="">Asteroid music for Hackers</span></p>
<c:if test="(not framesetp)"> <div style="display: flex; gap: 10px; justify-content: end; margin-bottom: 20px;">
<c:then> <button id="reconnect-btn" class="btn btn-warning" onclick="reconnectStream()" style="font-size: 0.9em; display: none;">
<div style="display: flex; gap: 10px; justify-content: end; margin-bottom: 20px;">
<button id="reconnect-btn" class="btn btn-warning" onclick="reconnectStream()" style="font-size: 0.9em; display: none;">
🔄 Reconnect Stream 🔄 Reconnect Stream
</button> </button>
<button id="popout-btn" class="btn btn-info" onclick="openPopoutPlayer()" style="font-size: 0.9em;"> <button id="popout-btn" class="btn btn-info" onclick="openPopoutPlayer()" style="font-size: 0.9em;">
🗗 Pop Out Player 🗗 Pop Out Player
</button> </button>
<button id="frameset-btn" class="btn btn-secondary" onclick="enableFramesetMode()" style="font-size: 0.9em;"> <button id="frameset-btn" class="btn btn-secondary" onclick="enableFramesetMode()" style="font-size: 0.9em;">
🖼️ Enable Persistent Player 🖼️ Enable Persistent Player
</button> </button>
</div> </div>
<!-- Stream connection status --> <!-- Stream connection status -->
<div id="stream-status-indicator" style="display: none; padding: 8px; margin-bottom: 10px; border-radius: 4px; text-align: center;"></div> <div id="stream-status-indicator" style="display: none; padding: 8px; margin-bottom: 10px; border-radius: 4px; text-align: center;"></div>
<div id="audio-container"> <div id="audio-container">
<audio id="live-audio" controls crossorigin="anonymous" style="width: 100%; margin: 10px 0;"> <audio id="live-audio" controls crossorigin="anonymous" 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)">
Your browser does not support the audio element. Your browser does not support the audio element.
</audio> </audio>
</div> </div>
</c:then>
<c:else>
<p class="frame-enable-message"><em>The live stream player is now in the persistent bar at the bottom of the page</em></p>
</c:else>
</c:if>
</div> </div>
<div id="now-playing" class="now-playing"></div> <div id="now-playing" class="now-playing"></div>

View File

@ -0,0 +1,157 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title data-text="title">Asteroid Radio - Web Player</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/front-page.js"></script>
<script src="/asteroid/static/js/player.js"></script>
<script src="/api/asteroid/spectrum-analyzer.js"></script>
</head>
<body>
<div class="container">
<h1 style="display: flex; align-items: center; justify-content: center; gap: 15px;">
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
<span>WEB PLAYER</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
</h1>
<!-- Spectrum Analyzer Canvas -->
<div style="text-align: center; margin: 15px 0;">
<canvas id="spectrum-canvas" width="800" height="100" style="max-width: 100%; border: 1px solid #00ff00; background: #000; border-radius: 4px;"></canvas>
<div style="margin-top: 8px; font-size: 0.9em;">
<label style="margin-right: 10px;">
Style:
<select id="spectrum-style-selector" onchange="setSpectrumStyle(this.value)" style="padding: 3px; background: #000; color: #00ff00; border: 1px solid #00ff00;">
<option value="bars">Bars</option>
<option value="wave">Wave</option>
<option value="dots">Dots</option>
</select>
</label>
<label>
Theme:
<select id="spectrum-theme-selector" onchange="setSpectrumTheme(this.value)" style="padding: 3px; background: #000; color: #00ff00; border: 1px solid #00ff00;">
<option value="monotone">Monotone</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="purple">Purple</option>
<option value="red">Red</option>
<option value="amber">Amber</option>
<option value="rainbow">Rainbow</option>
</select>
</label>
</div>
</div>
<div class="nav">
<a href="/asteroid/content" target="content-frame">Home</a>
<a href="/asteroid/profile" target="content-frame" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="content-frame" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="content-frame" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="content-frame" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</div>
<!-- Live Stream Section - Note about persistent player -->
<div class="player-section">
<h2 style="color: #00ff00;">
<span class="live-stream-indicator" style="font-size: 1rem;">🟢 </span>
Live Radio Stream
</h2>
<p><em>The live stream player is now in the persistent bar at the bottom of the page. It will continue playing as you navigate between pages!</em></p>
</div>
<div id="now-playing" class="now-playing"></div>
<!-- Track Browser -->
<div class="player-section">
<h2>Personal Track Library</h2>
<div class="track-browser">
<input type="text" id="search-tracks" placeholder="Search tracks..." class="search-input">
<select id="library-tracks-per-page" class="sort-select" onchange="changeLibraryTracksPerPage()" style="margin: 10px 0px;">
<option value="10">10 per page</option>
<option value="20" selected>20 per page</option>
<option value="50">50 per page</option>
</select>
<div id="track-list" class="track-list">
<div class="loading">Loading tracks...</div>
</div>
<!-- Pagination Controls -->
<div id="library-pagination-controls" style="display: none; margin-top: 20px; text-align: center;">
<button onclick="libraryGoToPage(1)" class="btn btn-secondary">« First</button>
<button onclick="libraryPreviousPage()" class="btn btn-secondary"> Prev</button>
<span id="library-page-info" style="margin: 0 15px; font-weight: bold;">Page 1 of 1</span>
<button onclick="libraryNextPage()" class="btn btn-secondary">Next </button>
<button onclick="libraryGoToLastPage()" class="btn btn-secondary">Last »</button>
</div>
</div>
</div>
<!-- Audio Player Widget -->
<div class="player-section">
<h2>Audio Player</h2>
<div class="audio-player">
<div class="now-playing">
<div class="track-art">🎵</div>
<div class="track-details">
<div class="track-title" id="current-title">No track selected</div>
<div class="track-artist" id="current-artist">Unknown Artist</div>
<div class="track-album" id="current-album">Unknown Album</div>
</div>
</div>
<audio id="audio-player" controls preload="none" style="width: 100%; margin: 20px 0; display: none;">
Your browser does not support the audio element.
</audio>
<div class="player-controls">
<button id="prev-btn" class="btn btn-secondary">⏮️ Previous</button>
<button id="play-pause-btn" class="btn btn-primary">▶️ Play</button>
<button id="next-btn" class="btn btn-secondary">⏭️ Next</button>
<button id="shuffle-btn" class="btn btn-info">🔀 Shuffle</button>
<button id="repeat-btn" class="btn btn-warning">🔁 Repeat</button>
</div>
<div class="player-info">
<div class="time-display">
<span id="current-time">0:00</span> / <span id="total-time">0:00</span>
</div>
<div class="volume-control">
<label for="volume-slider">🔊</label>
<input type="range" id="volume-slider" min="0" max="100" value="50" class="volume-slider">
</div>
</div>
</div>
</div>
<!-- Playlist Management -->
<div class="player-section">
<h2>Playlists</h2>
<div class="playlist-controls">
<input type="text" id="new-playlist-name" placeholder="New playlist name..." class="playlist-input">
<button id="create-playlist" class="btn btn-success"> Create Playlist</button>
</div>
<div class="playlist-list">
<div id="playlists-container">
<div class="no-playlists">No playlists created yet.</div>
</div>
</div>
</div>
<!-- Queue -->
<div class="player-section">
<h2>Play Queue</h2>
<div class="queue-controls">
<button id="clear-queue" class="btn btn-danger">🗑️ Clear Queue</button>
<button id="save-queue" class="btn btn-info">💾 Save as Playlist</button>
</div>
<div id="play-queue" class="play-queue">
<div class="empty-queue">Queue is empty</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -11,11 +11,6 @@
<script src="/asteroid/static/js/auth-ui.js"></script> <script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/front-page.js"></script> <script src="/asteroid/static/js/front-page.js"></script>
<script src="/asteroid/static/js/player.js"></script> <script src="/asteroid/static/js/player.js"></script>
<c:if test="framesetp">
<c:then>
<script src="/api/asteroid/spectrum-analyzer.js"></script>
</c:then>
</c:if>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@ -24,57 +19,13 @@
<span>WEB PLAYER</span> <span>WEB PLAYER</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;"> <img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
</h1> </h1>
<!-- Spectrum Analyzer Canvas -->
<c:if test="framesetp">
<c:then>
<div style="text-align: center; margin: 15px 0;">
<canvas id="spectrum-canvas" width="800" height="100" style="max-width: 100%; border: 1px solid #00ff00; background: #000; border-radius: 4px;"></canvas>
<div style="margin-top: 8px; font-size: 0.9em;">
<label style="margin-right: 10px;">
Style:
<select id="spectrum-style-selector" onchange="setSpectrumStyle(this.value)" style="padding: 3px; background: #000; color: #00ff00; border: 1px solid #00ff00;">
<option value="bars">Bars</option>
<option value="wave">Wave</option>
<option value="dots">Dots</option>
</select>
</label>
<label>
Theme:
<select id="spectrum-theme-selector" onchange="setSpectrumTheme(this.value)" style="padding: 3px; background: #000; color: #00ff00; border: 1px solid #00ff00;">
<option value="monotone">Monotone</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="purple">Purple</option>
<option value="red">Red</option>
<option value="amber">Amber</option>
<option value="rainbow">Rainbow</option>
</select>
</label>
</div>
</div>
</c:then>
</c:if>
<div class="nav"> <div class="nav">
<c:if test="(not framesetp)"> <a href="/asteroid">Home</a>
<c:then> <a href="/asteroid/profile">Profile</a>
<a href="/asteroid">Home</a> <a href="/asteroid/admin" data-show-if-admin>Admin</a>
<a href="/asteroid/profile">Profile</a> <a href="/asteroid/login" data-show-if-logged-out>Login</a>
<a href="/asteroid/admin" data-show-if-admin>Admin</a> <a href="/asteroid/register" data-show-if-logged-out>Register</a>
<a href="/asteroid/login" data-show-if-logged-out>Login</a> <a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</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>
</c:then>
<c:else>
<a href="/asteroid/content" target="content-frame">Home</a>
<a href="/asteroid/profile" target="content-frame" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="content-frame" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="content-frame" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="content-frame" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</c:else>
</c:if>
</div> </div>
<!-- Live Stream Section --> <!-- Live Stream Section -->
@ -83,31 +34,24 @@
<span class="live-stream-indicator" style="font-size: 1rem;">🟢 </span> <span class="live-stream-indicator" style="font-size: 1rem;">🟢 </span>
Live Radio Stream Live Radio Stream
</h2> </h2>
<c:if test="(not framesetp)"> <div class="live-stream">
<c:then> <input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
<div class="live-stream"> <!-- Stream Quality Selector -->
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)"> <div class="live-stream-quality">
<!-- Stream Quality Selector --> <label for="live-stream-quality"><strong>Quality:</strong></label>
<div class="live-stream-quality"> <select id="live-stream-quality" onchange="changeLiveStreamQuality()">
<label for="live-stream-quality"><strong>Quality:</strong></label> <option value="aac">AAC 96kbps (Recommended)</option>
<select id="live-stream-quality" onchange="changeLiveStreamQuality()"> <option value="mp3">MP3 128kbps (Compatible)</option>
<option value="aac">AAC 96kbps (Recommended)</option> <option value="low">MP3 64kbps (Low Bandwidth)</option>
<option value="mp3">MP3 128kbps (Compatible)</option> </select>
<option value="low">MP3 64kbps (Low Bandwidth)</option> </div>
</select>
</div>
<audio id="live-stream-audio" controls style="width: 100%; margin-top: 20px;"> <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>
<p><em>Listen to the live Asteroid Radio stream</em></p> <p><em>Listen to the live Asteroid Radio stream</em></p>
</div> </div>
</c:then>
<c:else>
<p><em>The live stream player is now in the persistent bar at the bottom of the page. It will continue playing as you navigate between pages!</em></p>
</c:else>
</c:if>
</div> </div>
<div id="now-playing" class="now-playing"></div> <div id="now-playing" class="now-playing"></div>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Asteroid Radio - Status</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
</head>
<body>
<div class="container">
<header>
<h1 style="display: flex; align-items: center; justify-content: center; gap: 15px;">
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
<span>📡 SYSTEM STATUS</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
</h1>
<nav class="nav">
<a href="/asteroid/content" target="_self">Home</a>
<a href="/asteroid/player-content" target="_self">Player</a>
<a href="/asteroid/about-content" target="_self">About</a>
<a href="/asteroid/status-content" target="_self">Status</a>
<a href="/asteroid/profile" target="_self" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="_self" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="_self" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="_self" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</nav>
</header>
<main style="max-width: 800px; margin: 0 auto; padding: 20px;">
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">🟢 Server Status</h2>
<p style="line-height: 1.6;">
Asteroid Radio is currently online and broadcasting.
</p>
</section>
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">📊 Stream Information</h2>
<ul style="line-height: 1.8;">
<li><strong>Status:</strong> 🟢 Live</li>
<li><strong>Formats:</strong> AAC 96kbps, MP3 128kbps, MP3 64kbps</li>
<li><strong>Server:</strong> Icecast</li>
</ul>
</section>
<section style="margin-bottom: 30px;">
<h2 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;"> Additional Information</h2>
<p style="line-height: 1.6;">
For detailed system status and administration, please visit the <a href="/asteroid/admin" style="color: #00ff00;" data-show-if-admin>Admin Dashboard</a>.
</p>
</section>
</main>
</div>
</body>
</html>

View File

@ -15,32 +15,16 @@
<span>📡 SYSTEM STATUS</span> <span>📡 SYSTEM STATUS</span>
<img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;"> <img src="/asteroid/static/asteroid.png" alt="Asteroid" style="height: 50px; width: auto;">
</h1> </h1>
<nav class="nav"> <nav class="nav">
<c:if test="(not framesetp)"> <a href="/asteroid/frameset">Home</a>
<c:then> <a href="/asteroid/player">Player</a>
<a href="/asteroid">Home</a> <a href="/asteroid/about">About</a>
<a href="/asteroid/player">Player</a> <a href="/asteroid/status">Status</a>
<a href="/asteroid/about">About</a> <a href="/asteroid/profile" data-show-if-logged-in>Profile</a>
<a href="/asteroid/status">Status</a> <a href="/asteroid/admin" data-show-if-admin>Admin</a>
<a href="/asteroid/profile" data-show-if-logged-in>Profile</a> <a href="/asteroid/login" data-show-if-logged-out>Login</a>
<a href="/asteroid/admin" data-show-if-admin>Admin</a> <a href="/asteroid/register" data-show-if-logged-out>Register</a>
<a href="/asteroid/login" data-show-if-logged-out>Login</a> <a href="/asteroid/logout" data-show-if-logged-in class="btn-logout" onclick="event.preventDefault(); fetch('/asteroid/logout').then(() => window.location.href='/asteroid/frameset');">Logout</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>
</c:then>
<c:else>
<a href="/asteroid/content" target="_self">Home</a>
<a href="/asteroid/player-content" target="_self">Player</a>
<a href="/asteroid/about-content" target="_self">About</a>
<a href="/asteroid/status-content" target="_self">Status</a>
<a href="/asteroid/profile" target="_self" data-show-if-logged-in>Profile</a>
<a href="/asteroid/admin" target="_self" data-show-if-admin>Admin</a>
<a href="/asteroid/login" target="_self" data-show-if-logged-out>Login</a>
<a href="/asteroid/register" target="_self" data-show-if-logged-out>Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout">Logout</a>
</c:else>
</c:if>
</nav> </nav>
</header> </header>