Fix player page now playing updates and spectrum analyzer MUTED indicator

- Execute scripts after AJAX navigation to enable dynamic content updates
- Fix spectrum analyzer audio element reference across navigation
- Stop and restart spectrum analyzer when re-initializing after AJAX load
- Find audio element in parent frame for frameset mode
- Clear all intervals/timeouts before navigation to prevent errors
- Add status-content.ctml placeholder page
- Remove debug logging from spectrum analyzer

Fixes:
- Now playing panel updates on player page after navigation
- MUTED indicator appears correctly across all pages
- No console errors from abandoned intervals
- Clean resource cleanup on page transitions
This commit is contained in:
Glenn Thompson 2025-12-07 08:21:00 +03:00
parent 3a8827f442
commit ca07b6e670
8 changed files with 313 additions and 66 deletions

View File

@ -607,6 +607,13 @@
:top-artist-3 "" :top-artist-3 ""
:top-artist-3-plays "")) :top-artist-3-plays ""))
;; Status content frame (for frameset mode)
(define-page status-content #@"/status-content" ()
"Status page content (displayed in content frame)"
(clip:process-to-string
(load-template "status-content")
:title "📡 Asteroid Radio - Status"))
;; Configure static file serving for other files ;; Configure static file serving for other files
;; BUT exclude ParenScript-compiled JS files ;; BUT exclude ParenScript-compiled JS files
(define-page static #@"/static/(.*)" (:uri-groups (path)) (define-page static #@"/static/(.*)" (:uri-groups (path))

View File

@ -43,24 +43,22 @@
(let ((audio-element nil) (let ((audio-element nil)
(canvas-element (ps:chain document (get-element-by-id "spectrum-canvas")))) (canvas-element (ps:chain document (get-element-by-id "spectrum-canvas"))))
;; Try to find audio element in current frame first ;; Try current document first
(setf audio-element (or (ps:chain document (get-element-by-id "live-audio")) (setf audio-element (or (ps:chain document (get-element-by-id "live-audio"))
(ps:chain document (get-element-by-id "persistent-audio")))) (ps:chain document (get-element-by-id "persistent-audio"))))
;; If not found and we're in a frame, try to access from parent frameset ;; If not found and we're in a frame, try parent frame (frameset mode)
(when (and (not audio-element) (when (and (not audio-element)
(ps:@ window parent) (ps:@ window parent)
(not (eq (ps:@ window parent) window))) (not (eq (ps:@ window parent) window)))
(ps:chain console (log "Trying to access audio from parent frame..."))
(ps:try (ps:try
(progn (let ((player-frame (ps:getprop (ps:@ window parent frames) "player-frame")))
;; Try accessing via parent.frames
(let ((player-frame (ps:getprop (ps:@ window parent) "player-frame")))
(when player-frame (when player-frame
(setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio"))) (setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio")))
(ps:chain console (log "Found audio in player-frame:" audio-element))))) (when audio-element
(ps:chain console (log "Found persistent-audio in player-frame")))))
(:catch (e) (:catch (e)
(ps:chain console (log "Cross-frame access error:" e))))) (ps:chain console (log "Could not access parent frame:" e)))))
(when (and audio-element canvas-element) (when (and audio-element canvas-element)
;; Store current audio element ;; Store current audio element
@ -218,9 +216,13 @@
"Return array of available visualization styles" "Return array of available visualization styles"
(array "bars" "wave" "dots")) (array "bars" "wave" "dots"))
;; Initialize when audio starts playing (defun initialize-spectrum-analyzer ()
(ps:chain document (add-event-listener "DOMContentLoaded" "Initialize or re-initialize the spectrum analyzer (can be called after AJAX navigation)"
(lambda () ;; Stop existing analyzer if running
(when *animation-id*
(ps:chain window (cancel-animation-frame *animation-id*))
(setf *animation-id* nil))
;; Load saved theme and style preferences ;; Load saved theme and style preferences
(let ((saved-theme (ps:chain local-storage (get-item "spectrum-theme"))) (let ((saved-theme (ps:chain local-storage (get-item "spectrum-theme")))
(saved-style (ps:chain local-storage (get-item "spectrum-style")))) (saved-style (ps:chain local-storage (get-item "spectrum-style"))))
@ -247,20 +249,39 @@
(when canvas (when canvas
(setf (ps:@ canvas style border-color) (ps:@ theme top))))) (setf (ps:@ canvas style border-color) (ps:@ theme top)))))
(let ((audio-element (or (ps:chain document (get-element-by-id "live-audio")) (let ((audio-element nil))
(ps:chain document (get-element-by-id "persistent-audio")))))
;; If not found and we're in a frame, try parent ;; Try current document first
(setf audio-element (or (ps:chain document (get-element-by-id "live-audio"))
(ps:chain document (get-element-by-id "persistent-audio"))))
;; If not found and we're in a frame, try parent frame (frameset mode)
(when (and (not audio-element) (when (and (not audio-element)
(ps:@ window parent) (ps:@ window parent)
(not (eq (ps:@ window parent) window))) (not (eq (ps:@ window parent) window)))
(ps:try (ps:try
(let ((player-frame (ps:getprop (ps:@ window parent) "player-frame"))) (let ((player-frame (ps:getprop (ps:@ window parent frames) "player-frame")))
(when player-frame (when player-frame
(setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio"))))) (setf audio-element (ps:chain player-frame document (get-element-by-id "persistent-audio")))
(when audio-element
(ps:chain console (log "Found persistent-audio in player-frame")))))
(:catch (e) (:catch (e)
(ps:chain console (log "Event listener cross-frame error:" e))))) (ps:chain console (log "Could not access parent frame:" e)))))
(when audio-element (when audio-element
;; Store reference for muted detection
(setf *current-audio-element* audio-element)
(ps:chain audio-element (add-event-listener "play" init-spectrum-analyzer)) (ps:chain audio-element (add-event-listener "play" init-spectrum-analyzer))
(ps:chain audio-element (add-event-listener "pause" stop-spectrum-analyzer))))))))) (ps:chain audio-element (add-event-listener "pause" stop-spectrum-analyzer))
;; If audio is already playing, restart the analyzer with new reference
(when (and (not (ps:@ audio-element paused))
(ps:chain document (get-element-by-id "spectrum-canvas")))
(ps:chain console (log "Audio already playing, restarting spectrum analyzer"))
(init-spectrum-analyzer)))))
;; Make initialization function globally accessible
(setf (ps:@ window |initializeSpectrumAnalyzer|) initialize-spectrum-analyzer)
;; Initialize when audio starts playing
(ps:chain document (add-event-listener "DOMContentLoaded" initialize-spectrum-analyzer))))

View File

@ -34,6 +34,13 @@
const url = link.href; const url = link.href;
console.log('Loading via AJAX:', url); console.log('Loading via AJAX:', url);
// Clear all intervals to prevent old page scripts from running
const highestId = window.setTimeout(() => {}, 0);
for (let i = 0; i < highestId; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
fetch(url) fetch(url)
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
@ -41,6 +48,23 @@
document.write(html); document.write(html);
document.close(); document.close();
// Execute scripts in the new content
const scripts = document.querySelectorAll('script');
scripts.forEach(oldScript => {
const newScript = document.createElement('script');
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
// Re-initialize spectrum analyzer after navigation
if (window.initializeSpectrumAnalyzer) {
setTimeout(() => window.initializeSpectrumAnalyzer(), 100);
}
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
window.history.pushState({}, '', url); window.history.pushState({}, '', url);
} }

View File

@ -39,6 +39,13 @@
const url = link.href; const url = link.href;
console.log('Loading via AJAX:', url); console.log('Loading via AJAX:', url);
// Clear all intervals to prevent old page scripts from running
const highestId = window.setTimeout(() => {}, 0);
for (let i = 0; i < highestId; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
fetch(url) fetch(url)
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
@ -47,6 +54,23 @@
document.write(html); document.write(html);
document.close(); document.close();
// Execute scripts in the new content
const scripts = document.querySelectorAll('script');
scripts.forEach(oldScript => {
const newScript = document.createElement('script');
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
// Re-initialize spectrum analyzer after navigation
if (window.initializeSpectrumAnalyzer) {
setTimeout(() => window.initializeSpectrumAnalyzer(), 100);
}
// Update browser history // Update browser history
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
window.history.pushState({}, '', url); window.history.pushState({}, '', url);
@ -101,13 +125,13 @@
</div> </div>
<nav class="nav"> <nav class="nav">
<a href="/asteroid/content" target="content-frame">Home</a> <a href="/asteroid/content" target="content-frame" onclick="return loadInFrame(this)">Home</a>
<a href="/asteroid/player-content" target="content-frame">Player</a> <a href="/asteroid/player-content" target="content-frame" onclick="return loadInFrame(this)">Player</a>
<a href="/asteroid/status" target="content-frame">Status</a> <a href="/asteroid/status-content" target="content-frame" onclick="return loadInFrame(this)">Status</a>
<a href="/asteroid/profile" target="content-frame" data-show-if-logged-in>Profile</a> <a href="/asteroid/profile-content" target="content-frame" data-show-if-logged-in onclick="return loadInFrame(this)">Profile</a>
<a href="/asteroid/admin" target="content-frame" data-show-if-admin>Admin</a> <a href="/asteroid/admin-content" target="content-frame" data-show-if-admin onclick="return loadInFrame(this)">Admin</a>
<a href="/asteroid/login-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Login</a> <a href="/asteroid/login-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Login</a>
<a href="/asteroid/register" target="content-frame" data-show-if-logged-out>Register</a> <a href="/asteroid/register-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout" onclick="handleLogout(); return false;">Logout</a> <a href="/asteroid/logout" data-show-if-logged-in class="btn-logout" onclick="handleLogout(); return false;">Logout</a>
</nav> </nav>
</header> </header>

View File

@ -14,6 +14,13 @@
const url = link.href; const url = link.href;
console.log('Loading via AJAX:', url); console.log('Loading via AJAX:', url);
// Clear all intervals to prevent old page scripts from running
const highestId = window.setTimeout(() => {}, 0);
for (let i = 0; i < highestId; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
fetch(url) fetch(url)
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
@ -21,6 +28,23 @@
document.write(html); document.write(html);
document.close(); document.close();
// Execute scripts in the new content
const scripts = document.querySelectorAll('script');
scripts.forEach(oldScript => {
const newScript = document.createElement('script');
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
// Re-initialize spectrum analyzer after navigation
if (window.initializeSpectrumAnalyzer) {
setTimeout(() => window.initializeSpectrumAnalyzer(), 100);
}
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
window.history.pushState({}, '', url); window.history.pushState({}, '', url);
} }
@ -74,9 +98,9 @@
<span>ASTEROID RADIO - LOGIN</span> <span>ASTEROID RADIO - LOGIN</span>
</h1> </h1>
<nav class="nav"> <nav class="nav">
<a href="/asteroid/content" target="content-frame">Home</a> <a href="/asteroid/content" target="content-frame" onclick="return loadInFrame(this)">Home</a>
<a href="/asteroid/status" target="content-frame">Status</a> <a href="/asteroid/status-content" target="content-frame" onclick="return loadInFrame(this)">Status</a>
<a href="/asteroid/register-content" target="content-frame">Register</a> <a href="/asteroid/register-content" target="content-frame" onclick="return loadInFrame(this)">Register</a>
</nav> </nav>
</header> </header>

View File

@ -35,6 +35,13 @@
const url = link.href; const url = link.href;
console.log('Loading via AJAX:', url); console.log('Loading via AJAX:', url);
// Clear all intervals to prevent old page scripts from running
const highestId = window.setTimeout(() => {}, 0);
for (let i = 0; i < highestId; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
fetch(url) fetch(url)
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
@ -43,6 +50,23 @@
document.write(html); document.write(html);
document.close(); document.close();
// Execute scripts in the new content
const scripts = document.querySelectorAll('script');
scripts.forEach(oldScript => {
const newScript = document.createElement('script');
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
// Re-initialize spectrum analyzer after navigation
if (window.initializeSpectrumAnalyzer) {
setTimeout(() => window.initializeSpectrumAnalyzer(), 100);
}
// Update browser history // Update browser history
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
window.history.pushState({}, '', url); window.history.pushState({}, '', url);
@ -95,11 +119,11 @@
</div> </div>
<div class="nav"> <div class="nav">
<a href="/asteroid/content" target="content-frame">Home</a> <a href="/asteroid/content" target="content-frame" onclick="return loadInFrame(this)">Home</a>
<a href="/asteroid/profile" target="content-frame" data-show-if-logged-in>Profile</a> <a href="/asteroid/profile-content" target="content-frame" data-show-if-logged-in onclick="return loadInFrame(this)">Profile</a>
<a href="/asteroid/admin" target="content-frame" data-show-if-admin>Admin</a> <a href="/asteroid/admin-content" target="content-frame" data-show-if-admin onclick="return loadInFrame(this)">Admin</a>
<a href="/asteroid/login-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Login</a> <a href="/asteroid/login-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Login</a>
<a href="/asteroid/register" target="content-frame" data-show-if-logged-out>Register</a> <a href="/asteroid/register-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Register</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout" onclick="handleLogout(); return false;">Logout</a> <a href="/asteroid/logout" data-show-if-logged-in class="btn-logout" onclick="handleLogout(); return false;">Logout</a>
</div> </div>

View File

@ -34,6 +34,13 @@
const url = link.href; const url = link.href;
console.log('Loading via AJAX:', url); console.log('Loading via AJAX:', url);
// Clear all intervals to prevent old page scripts from running
const highestId = window.setTimeout(() => {}, 0);
for (let i = 0; i < highestId; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
fetch(url) fetch(url)
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
@ -41,6 +48,23 @@
document.write(html); document.write(html);
document.close(); document.close();
// Execute scripts in the new content
const scripts = document.querySelectorAll('script');
scripts.forEach(oldScript => {
const newScript = document.createElement('script');
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
// Re-initialize spectrum analyzer after navigation
if (window.initializeSpectrumAnalyzer) {
setTimeout(() => window.initializeSpectrumAnalyzer(), 100);
}
if (window.history && window.history.pushState) { if (window.history && window.history.pushState) {
window.history.pushState({}, '', url); window.history.pushState({}, '', url);
} }

View File

@ -0,0 +1,99 @@
<!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>
<script>
// Handle logout without navigation
function handleLogout() {
fetch('/asteroid/logout', {
method: 'GET',
redirect: 'manual'
})
.then(() => {
// Reload the current page content to show logged-out state
fetch(window.location.href)
.then(response => response.text())
.then(html => {
document.open();
document.write(html);
document.close();
});
})
.catch(error => {
console.error('Logout failed:', error);
});
}
// Load content via AJAX to prevent audio interruption
function loadInFrame(link) {
const url = link.href;
console.log('Loading via AJAX:', url);
// Clear all intervals to prevent old page scripts from running
const highestId = window.setTimeout(() => {}, 0);
for (let i = 0; i < highestId; i++) {
window.clearInterval(i);
window.clearTimeout(i);
}
fetch(url)
.then(response => response.text())
.then(html => {
document.open();
document.write(html);
document.close();
// Execute scripts in the new content
const scripts = document.querySelectorAll('script');
scripts.forEach(oldScript => {
const newScript = document.createElement('script');
if (oldScript.src) {
newScript.src = oldScript.src;
} else {
newScript.textContent = oldScript.textContent;
}
oldScript.parentNode.replaceChild(newScript, oldScript);
});
// Re-initialize spectrum analyzer after navigation
if (window.initializeSpectrumAnalyzer) {
setTimeout(() => window.initializeSpectrumAnalyzer(), 100);
}
if (window.history && window.history.pushState) {
window.history.pushState({}, '', url);
}
})
.catch(error => {
console.error('Failed to load content:', error);
return true;
});
return false;
}
</script>
</head>
<body>
<div class="container">
<h1>📡 SYSTEM STATUS</h1>
<div class="nav">
<a href="/asteroid/content" target="content-frame" onclick="return loadInFrame(this)">Home</a>
<a href="/asteroid/player-content" target="content-frame" onclick="return loadInFrame(this)">Player</a>
<a href="/asteroid/profile-content" target="content-frame" data-show-if-logged-in onclick="return loadInFrame(this)">Profile</a>
<a href="/asteroid/admin-content" target="content-frame" data-show-if-admin onclick="return loadInFrame(this)">Admin</a>
<a href="/asteroid/login-content" target="content-frame" data-show-if-logged-out onclick="return loadInFrame(this)">Login</a>
<a href="/asteroid/logout" data-show-if-logged-in class="btn-logout" onclick="handleLogout(); return false;">Logout</a>
</div>
<div class="admin-section">
<h2>Server Status</h2>
<p>Status page coming soon...</p>
<p>For now, administrators can view detailed status information in the <a href="/asteroid/admin-content" target="content-frame" onclick="return loadInFrame(this)">Admin Dashboard</a>.</p>
</div>
</div>
</body>
</html>