Compare commits

...

3 Commits

Author SHA1 Message Date
Glenn Thompson 4ec90c0f27 perf: Reduce reconnect delay from 500ms to 200ms for faster response
Optimized the timeout after stream reconnection to make the pause/unpause
experience more responsive. The 200ms delay is sufficient for the browser
to clear buffers and start the fresh stream while minimizing perceived lag.
2025-12-06 08:36:30 -05:00
Glenn Thompson 7c7b2c921e fix: Prevent stale audio playback after long pause and reorganize spectrum analyzer
- Detect pauses longer than 10 seconds across all player modes
- Intercept 'playing' event to stop stale buffered audio
- Force stream reconnect to get live audio after long pause
- Reset and reinitialize spectrum analyzer during reconnect
- Prevent 'Now Playing' updates while stream is paused
- Move spectrum-analyzer.lisp to parenscript/ directory
- Update all documentation dates to 2025-12-06
- Add comprehensive project statistics (408 commits, 9,300 LOC)
- Add Phase 9 (Visual Audio Features) to project history

Fixes issue where resuming playback after a long pause would play
old buffered audio instead of the current live stream. The fix uses
the 'playing' event to detect when stale audio starts and immediately
stops it, then reconnects to get fresh stream data.

All player modes updated: main player, front page, popout, and frame player.
2025-12-06 08:36:30 -05:00
Glenn Thompson 6e8260172f fix: Reduce Icecast burst size and prevent now-playing updates during pause
- Reduced Icecast burst-size from 64KB to 8KB to minimize buffer accumulation
- Fixed spectrum analyzer to only create MediaElementSource once
- Added resetSpectrumAnalyzer() function to allow reconnection
- Prevent now-playing info updates when stream is paused across all players:
  * Player page
  * Front page
  * Pop-out player
  * Frame player
  * Admin page
- After pause >10s, reconnect stream and reinitialize spectrum analyzer
- Preserves spectrum analyzer functionality after pause/unpause
- Eliminates stuttering and buffer accumulation issues
2025-12-06 08:36:30 -05:00
23 changed files with 343 additions and 57 deletions

View File

@ -1,4 +1,4 @@
* Rundown to Launch. Still to do:
** [#C] Rundown to Launch. Still to do:
* Setup asteroid.radio server at Hetzner [7/7]
- [X] Provision a VPS
@ -25,23 +25,23 @@
1) [X] Liquidsoap is exposing its management console via telnet on the exterior network interface of b612.asteroid.radio
2) [X] icecast is also binding the external interface on b612, which it
should not be. HAproxy is there to mediate this flow.
3) [ ] We're still on the built in i-lambdalite database
3) [X] We're still on the built in i-lambdalite database
4) [X] The templates still advertise the default administrator password,
which is no bueno.
5) [ ] We need to work out the TLS situation with letsencrypt, and
5) [X] We need to work out the TLS situation with letsencrypt, and
integrate it into HAproxy.
6) [ ] The administrative interface should be beefed up.
6.1) [ ] Deactivate users
6.2) [ ] Change user access permissions
6.1) [X] Deactivate users
6.2) [X] Change user access permissions
6.3) [ ] Listener statistics, breakdown by day/hour, new users, % changed &c
7) [ ] When the player is paused, there are pretty serious stream sync issues in the form of stuttering for some time after the stream is unpaused.
8) [ ] User profile pages should probably be fleshed out.
9) [ ] the stream management features aren't there for Admins or DJs.
10) [ ] The "Scan Library" feature is not working in the main branch
11) [ ] The player widget should be styled so it fits the site theme on systems running 'light' thmes.
12) [ ] ensure each info field 'Listeners: ..' &c has only one instance per page.
10) [X] The "Scan Library" feature is not working in the main branch
11) [X] The player widget should be styled so it fits the site theme on systems running 'light' thmes.
12) [X] ensure each info field 'Listeners: ..' &c has only one instance per page.
* Server runtime configuration [0/1]
- [ ] parameterize all configuration for runtime loading [0/2]

View File

@ -41,7 +41,8 @@
(:file "conditions")
(:file "database")
(:file "template-utils")
(:file "spectrum-analyzer")
(:module :parenscript
:components ((:file "spectrum-analyzer")))
(:file "stream-media")
(:file "user-management")
(:file "playlist-management")

View File

@ -10,7 +10,8 @@
<header-timeout>15</header-timeout>
<source-timeout>10</source-timeout>
<burst-on-connect>1</burst-on-connect>
<burst-size>65535</burst-size>
<!-- Reduced from 65535 to minimize buffer accumulation during pause -->
<burst-size>8192</burst-size>
</limits>
<authentication>

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - API Endpoints Reference
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - API Reference
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Current Interfaces

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - Development Guide
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Development Setup

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - Docker Streaming Setup
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Docker Streaming Overview

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - Installation Guide
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Installation Overview

View File

@ -1,6 +1,6 @@
#+TITLE: Playlist System - Complete (MVP)
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -1,6 +1,6 @@
#+TITLE: PostgreSQL Setup for Asteroid Radio
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - Project Development History
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
#+DESCRIPTION: Comprehensive history of the Asteroid Radio project from inception to present
* Project Overview
@ -11,7 +11,8 @@ Asteroid Radio is a web-based internet radio station built with Common Lisp, fea
- *Backend*: Common Lisp (SBCL), Radiance web framework
- *Streaming*: Icecast2, Liquidsoap
- *Database*: PostgreSQL (configured, ready for migration)
- *Frontend*: HTML5, JavaScript, CLIP templating, LASS (CSS in Lisp)
- *Frontend*: HTML5, JavaScript, Parenscript, CLIP templating, LASS (CSS in Lisp)
- *Audio Visualization*: Web Audio API, Canvas
- *Infrastructure*: Docker, Docker Compose
* Project Timeline
@ -231,19 +232,43 @@ Asteroid Radio is a web-based internet radio station built with Common Lisp, fea
- Synchronized with upstream/main
- Prepared comprehensive documentation PR
** Phase 9: Visual Audio Features (December 2025)
*** 2025-12-06: Real-Time Spectrum Analyzer
- *Lead*: Brian O'Reilly (Fade), Glenn Thompson
- Implemented spectrum analyzer using Parenscript
- Web Audio API integration for real-time visualization
- Dynamic JavaScript generation via API endpoint
- Canvas-based frequency display
- Works across all player modes (inline, pop-out, frameset)
- Lisp-to-JavaScript compilation for maintainability
* Development Statistics
** Contributors (by commit count)
1. Glenn Thompson (glenneth/Glenneth) - 135+ commits
2. Brian O'Reilly (Fade) - 55+ commits
3. Luis Pereira (easilok) - 23+ commits
1. Glenn Thompson (glenneth/Glenneth) - 236 commits
2. Brian O'Reilly (Fade) - 109 commits
3. Luis Pereira (easilok) - 63 commits
** Total Commits: 213+ commits
** Total Commits: 408 commits
** Code Statistics
- *Total Lines of Code*: ~9,300 lines
- *Common Lisp*: 2,753 lines (.lisp, .asd)
- *JavaScript*: 2,315 lines (.js)
- *Templates*: 1,505 lines (.ctml)
- *Other*: 2,720 lines (CSS, Shell, Python, etc.)
- *Source Files*: 50 files
** Release Information
- *Current Version*: Development (pre-1.0)
- *Tagged Releases*: None (continuous development)
- *Deployment Status*: Production-ready
** Active Development Period
- Start: August 12, 2025
- Current: November 1, 2025
- Duration: ~2.75 months of active development
- Current: December 6, 2025
- Duration: ~4 months of active development
* Major Features Implemented
@ -266,6 +291,7 @@ Asteroid Radio is a web-based internet radio station built with Common Lisp, fea
- ✅ Multiple quality options (AAC 96k, MP3 128k, MP3 64k)
- ✅ ReplayGain volume normalization
- ✅ Live now-playing information
- ✅ Real-time spectrum analyzer visualization
- ✅ Icecast integration
- ✅ Liquidsoap DJ controls
- ✅ Stream queue management
@ -325,7 +351,7 @@ Asteroid Radio is a web-based internet radio station built with Common Lisp, fea
- Parallel music scanning
- Client-side caching
* Current State (November 2025)
* Current State (December 2025)
** Production Ready Features
- Full music streaming platform
@ -333,6 +359,7 @@ Asteroid Radio is a web-based internet radio station built with Common Lisp, fea
- Admin control panel
- DJ controls
- Multiple player modes
- Real-time spectrum analyzer
- Complete Docker deployment (streams + application)
- Multi-environment support with dynamic URLs
- Comprehensive documentation
@ -392,9 +419,9 @@ Asteroid Radio is a web-based internet radio station built with Common Lisp, fea
* Conclusion
Asteroid Radio has evolved from a simple concept into a full-featured internet radio platform in just 2.75 months of active development. The project demonstrates the power of Common Lisp for web development and the collaborative nature of open-source development.
Asteroid Radio has evolved from a simple concept into a full-featured internet radio platform in just 4 months of active development. The project demonstrates the power of Common Lisp for web development and the collaborative nature of open-source development.
With complete Docker deployment, comprehensive documentation, and a growing feature set, Asteroid Radio is ready for production use while continuing to evolve with regular improvements, bug fixes, and new features based on user needs and technical requirements.
With complete Docker deployment, real-time audio visualization, comprehensive documentation, and a growing feature set, Asteroid Radio is ready for production use while continuing to evolve with regular improvements, bug fixes, and new features based on user needs and technical requirements.
** Project Links
- Repository: https://github.com/fade/asteroid
@ -403,4 +430,4 @@ With complete Docker deployment, comprehensive documentation, and a growing feat
---
*Last Updated: 2025-11-01*
*Last Updated: 2025-12-06*

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio - Project Overview
#+AUTHOR: Glenn Thompson & Brian O'Reilly (Fade)
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* 🎯 Mission
@ -45,6 +45,8 @@ Asteroid Radio is a modern, web-based music streaming platform designed for hack
- **HTML5** with semantic templates
- **CSS3** with dark hacker theme
- **JavaScript** for interactive features
- **Parenscript** - Lisp-to-JavaScript compiler for spectrum analyzer
- **Web Audio API** - Real-time audio visualization
- **VT323 Font** for retro terminal aesthetic
**Streaming:**
@ -81,6 +83,7 @@ Asteroid Radio is a modern, web-based music streaming platform designed for hack
- ✅ **Music Library** - Track management with pagination, search, and filtering
- ✅ **User Playlists** - Create, manage, and play personal music collections
- ✅ **Multiple Player Modes** - Inline, pop-out, and persistent frameset players
- ✅ **Real-Time Spectrum Analyzer** - Visual audio frequency display using Web Audio API and Parenscript
- ✅ **Stream Queue Control** - Admin control over broadcast stream queue (M3U-based)
- ✅ **REST API** - Comprehensive JSON API with 15+ endpoints
- ✅ **Music Streaming** - Multiple quality formats (128k MP3, 96k AAC, 64k MP3)

View File

@ -65,6 +65,7 @@ Pagination system for efficient browsing of large music libraries.
- **Music Library**: Track management with pagination, search, and filtering
- **Playlists**: User playlists with creation and playback
- **Multiple Player Modes**: Inline, pop-out, and persistent frameset players
- **Real-Time Spectrum Analyzer**: Visual audio frequency display using Web Audio API
- **Stream Queue Control**: Admin control over broadcast stream queue
- **Docker Streaming Infrastructure**: Icecast2 + Liquidsoap containers
- **Three Quality Streams**: 128kbps MP3, 96kbps AAC, 64kbps MP3
@ -147,5 +148,5 @@ For detailed technical information, see the **[[file:PROJECT-OVERVIEW.org][Proje
---
*Last Updated: 2025-10-26*
*Documentation Version: 3.0*
*Last Updated: 2025-12-06*
*Documentation Version: 3.1*

View File

@ -1,6 +1,6 @@
#+TITLE: Stream Queue Control System
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -1,6 +1,6 @@
#+TITLE: Asteroid Radio Testing Guide
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -1,6 +1,6 @@
#+TITLE: Track Pagination System - Complete
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -1,6 +1,6 @@
#+TITLE: User Management System - Complete
#+AUTHOR: Asteroid Radio Development Team
#+DATE: 2025-10-26
#+DATE: 2025-12-06
* Overview

View File

@ -12,6 +12,18 @@
(defvar *canvas* nil)
(defvar *canvas-ctx* nil)
(defvar *animation-id* nil)
(defvar *media-source* nil)
(defvar *current-audio-element* nil)
(defun reset-spectrum-analyzer ()
"Reset the spectrum analyzer to allow reconnection after audio element reload"
(when *animation-id*
(cancel-animation-frame *animation-id*)
(setf *animation-id* nil))
(setf *audio-context* nil)
(setf *analyser* nil)
(setf *media-source* nil)
(ps:chain console (log "Spectrum analyzer reset for reconnection")))
(defun init-spectrum-analyzer ()
"Initialize the spectrum analyzer"
@ -37,27 +49,35 @@
(:catch (e)
(ps:chain console (log "Cross-frame access error:" e)))))
(when (and audio-element canvas-element (not *audio-context*))
;; Create Audio Context
(setf *audio-context* (ps:new (or (ps:@ window |AudioContext|)
(ps:@ window |webkitAudioContext|))))
(when (and audio-element canvas-element)
;; Store current audio element
(setf *current-audio-element* audio-element)
;; Create Analyser Node
(setf *analyser* (ps:chain *audio-context* (create-analyser)))
(setf (ps:@ *analyser* |fftSize|) 256)
(setf (ps:@ *analyser* |smoothingTimeConstant|) 0.8)
;; Connect audio source to analyser
(let ((source (ps:chain *audio-context* (create-media-element-source audio-element))))
(ps:chain source (connect *analyser*))
(ps:chain *analyser* (connect (ps:@ *audio-context* destination))))
;; Only create audio context and media source once
(when (not *audio-context*)
;; Create Audio Context
(setf *audio-context* (ps:new (or (ps:@ window |AudioContext|)
(ps:@ window |webkitAudioContext|))))
;; Create Analyser Node
(setf *analyser* (ps:chain *audio-context* (create-analyser)))
(setf (ps:@ *analyser* |fftSize|) 256)
(setf (ps:@ *analyser* |smoothingTimeConstant|) 0.8)
;; Connect audio source to analyser (can only be done once per element)
(setf *media-source* (ps:chain *audio-context* (create-media-element-source audio-element)))
(ps:chain *media-source* (connect *analyser*))
(ps:chain *analyser* (connect (ps:@ *audio-context* destination)))
(ps:chain console (log "Spectrum analyzer audio context created")))
;; Setup canvas
(setf *canvas* canvas-element)
(setf *canvas-ctx* (ps:chain *canvas* (get-context "2d")))
;; Start visualization
(draw-spectrum))))
;; Start visualization if not already running
(when (not *animation-id*)
(draw-spectrum)))))
(defun draw-spectrum ()
"Draw the spectrum analyzer visualization"

View File

@ -635,6 +635,12 @@ function displayQueueSearchResults(results) {
// Live stream info update
async function updateLiveStreamInfo() {
// Don't update if stream is paused
const audioElement = document.getElementById('live-stream-audio');
if (audioElement && audioElement.paused) {
return;
}
try {
const response = await fetch('/api/asteroid/partial/now-playing-inline');
const contentType = response.headers.get("content-type");

View File

@ -55,6 +55,12 @@ function changeStreamQuality() {
// Update now playing info from Icecast
async function updateNowPlaying() {
// Don't update if stream is paused
const audioElement = document.getElementById('live-audio');
if (audioElement && audioElement.paused) {
return;
}
try {
const response = await fetch('/api/asteroid/partial/now-playing')
const contentType = response.headers.get("content-type")
@ -102,9 +108,60 @@ window.addEventListener('DOMContentLoaded', function() {
// Update playing information right after load
updateNowPlaying();
// Auto-reconnect on stream errors
// Auto-reconnect on stream errors and after long pauses
const audioElement = document.getElementById('live-audio');
if (audioElement) {
// Track pause timestamp to detect long pauses and reconnect
let pauseTimestamp = null;
let isReconnecting = false;
let needsReconnect = false;
const PAUSE_RECONNECT_THRESHOLD = 10000; // 10 seconds
audioElement.addEventListener('pause', function() {
pauseTimestamp = Date.now();
console.log('Stream paused at:', pauseTimestamp);
});
audioElement.addEventListener('play', function() {
// Check if we need to reconnect after long pause
if (!isReconnecting && pauseTimestamp && (Date.now() - pauseTimestamp) > PAUSE_RECONNECT_THRESHOLD) {
needsReconnect = true;
console.log('Long pause detected, will reconnect when playing starts...');
}
pauseTimestamp = null;
});
// Intercept the playing event to stop stale audio
audioElement.addEventListener('playing', function() {
if (needsReconnect && !isReconnecting) {
isReconnecting = true;
needsReconnect = false;
console.log('Reconnecting stream after long pause to clear stale buffers...');
// Stop the stale audio immediately
audioElement.pause();
// Reset spectrum analyzer before reconnect
if (typeof resetSpectrumAnalyzer === 'function') {
resetSpectrumAnalyzer();
}
audioElement.load(); // Force reconnect to clear accumulated buffer
// Start playing the fresh stream and reinitialize spectrum analyzer
setTimeout(function() {
audioElement.play().catch(err => console.log('Reconnect play failed:', err));
if (typeof initSpectrumAnalyzer === 'function') {
initSpectrumAnalyzer();
console.log('Spectrum analyzer reinitialized after reconnect');
}
isReconnecting = false;
}, 200);
}
});
audioElement.addEventListener('error', function(e) {
console.log('Stream error, attempting reconnect in 3 seconds...');
setTimeout(function() {

View File

@ -26,6 +26,57 @@ document.addEventListener('DOMContentLoaded', function() {
if (liveAudio) {
// Reduce buffer to minimize delay
liveAudio.preload = 'none';
// Track pause timestamp to detect long pauses and reconnect
let pauseTimestamp = null;
let isReconnecting = false;
let needsReconnect = false;
const PAUSE_RECONNECT_THRESHOLD = 10000; // 10 seconds
liveAudio.addEventListener('pause', function() {
pauseTimestamp = Date.now();
console.log('Live stream paused at:', pauseTimestamp);
});
liveAudio.addEventListener('play', function() {
// Check if we need to reconnect after long pause
if (!isReconnecting && pauseTimestamp && (Date.now() - pauseTimestamp) > PAUSE_RECONNECT_THRESHOLD) {
needsReconnect = true;
console.log('Long pause detected, will reconnect when playing starts...');
}
pauseTimestamp = null;
});
// Intercept the playing event to stop stale audio
liveAudio.addEventListener('playing', function() {
if (needsReconnect && !isReconnecting) {
isReconnecting = true;
needsReconnect = false;
console.log('Reconnecting live stream after long pause to clear stale buffers...');
// Stop the stale audio immediately
liveAudio.pause();
// Reset spectrum analyzer before reconnect
if (typeof resetSpectrumAnalyzer === 'function') {
resetSpectrumAnalyzer();
}
liveAudio.load(); // Force reconnect to clear accumulated buffer
// Start playing the fresh stream and reinitialize spectrum analyzer
setTimeout(function() {
liveAudio.play().catch(err => console.log('Reconnect play failed:', err));
if (typeof initSpectrumAnalyzer === 'function') {
initSpectrumAnalyzer();
console.log('Spectrum analyzer reinitialized after reconnect');
}
isReconnecting = false;
}, 200);
}
});
}
// Restore user quality preference
const selector = document.getElementById('live-stream-quality');
@ -598,6 +649,12 @@ function changeLiveStreamQuality() {
// Live stream informatio update
async function updateNowPlaying() {
// Don't update if stream is paused
const liveAudio = document.getElementById('live-stream-audio');
if (liveAudio && liveAudio.paused) {
return;
}
try {
const response = await fetch('/api/asteroid/partial/now-playing')
const contentType = response.headers.get("content-type")

View File

@ -47,15 +47,64 @@
});
}
// Track pause timestamp to detect long pauses and reconnect
let pauseTimestamp = null;
let isReconnecting = false;
let needsReconnect = false;
const PAUSE_RECONNECT_THRESHOLD = 10000; // 10 seconds
audioElement.addEventListener('pause', function() {
pauseTimestamp = Date.now();
console.log('Frame player stream paused at:', pauseTimestamp);
});
audioElement.addEventListener('play', function() {
// Check if we need to reconnect after long pause
if (!isReconnecting && pauseTimestamp && (Date.now() - pauseTimestamp) > PAUSE_RECONNECT_THRESHOLD) {
needsReconnect = true;
console.log('Long pause detected, will reconnect when playing starts...');
}
pauseTimestamp = null;
});
// Intercept the playing event to stop stale audio
audioElement.addEventListener('playing', function() {
if (needsReconnect && !isReconnecting) {
isReconnecting = true;
needsReconnect = false;
console.log('Reconnecting frame player stream after long pause to clear stale buffers...');
// Stop the stale audio immediately
audioElement.pause();
// Reset spectrum analyzer before reconnect
if (typeof resetSpectrumAnalyzer === 'function') {
resetSpectrumAnalyzer();
}
audioElement.load(); // Force reconnect to clear accumulated buffer
// Start playing the fresh stream and reinitialize spectrum analyzer
setTimeout(function() {
audioElement.play().catch(err => console.log('Reconnect play failed:', err));
if (typeof initSpectrumAnalyzer === 'function') {
initSpectrumAnalyzer();
console.log('Spectrum analyzer reinitialized after reconnect');
}
isReconnecting = false;
}, 200);
} else {
console.log('Audio playing');
}
});
// Add event listeners for debugging
audioElement.addEventListener('waiting', function() {
console.log('Audio buffering...');
});
audioElement.addEventListener('playing', function() {
console.log('Audio playing');
});
audioElement.addEventListener('error', function(e) {
console.error('Audio error:', e);
});
@ -112,6 +161,12 @@
// Update mini now playing display
async function updateMiniNowPlaying() {
// Don't update if stream is paused
const audioElement = document.getElementById('persistent-audio');
if (audioElement && audioElement.paused) {
return;
}
try {
const response = await fetch('/api/asteroid/partial/now-playing-inline');
if (response.ok) {

View File

@ -97,6 +97,12 @@
// Update now playing info for popout
async function updatePopoutNowPlaying() {
// Don't update if stream is paused
const audioElement = document.getElementById('live-audio');
if (audioElement && audioElement.paused) {
return;
}
try {
const response = await fetch('/api/asteroid/partial/now-playing-inline');
const html = await response.text();
@ -125,8 +131,60 @@
// Initial update
updatePopoutNowPlaying();
// Auto-reconnect on stream errors
// Auto-reconnect on stream errors and after long pauses
const audioElement = document.getElementById('live-audio');
// Track pause timestamp to detect long pauses and reconnect
let pauseTimestamp = null;
let isReconnecting = false;
let needsReconnect = false;
const PAUSE_RECONNECT_THRESHOLD = 10000; // 10 seconds
audioElement.addEventListener('pause', function() {
pauseTimestamp = Date.now();
console.log('Popout stream paused at:', pauseTimestamp);
});
audioElement.addEventListener('play', function() {
// Check if we need to reconnect after long pause
if (!isReconnecting && pauseTimestamp && (Date.now() - pauseTimestamp) > PAUSE_RECONNECT_THRESHOLD) {
needsReconnect = true;
console.log('Long pause detected, will reconnect when playing starts...');
}
pauseTimestamp = null;
});
// Intercept the playing event to stop stale audio
audioElement.addEventListener('playing', function() {
if (needsReconnect && !isReconnecting) {
isReconnecting = true;
needsReconnect = false;
console.log('Reconnecting popout stream after long pause to clear stale buffers...');
// Stop the stale audio immediately
audioElement.pause();
// Reset spectrum analyzer before reconnect
if (typeof resetSpectrumAnalyzer === 'function') {
resetSpectrumAnalyzer();
}
audioElement.load(); // Force reconnect to clear accumulated buffer
// Start playing the fresh stream and reinitialize spectrum analyzer
setTimeout(function() {
audioElement.play().catch(err => console.log('Reconnect play failed:', err));
if (typeof initSpectrumAnalyzer === 'function') {
initSpectrumAnalyzer();
console.log('Spectrum analyzer reinitialized after reconnect');
}
isReconnecting = false;
}, 200);
}
});
audioElement.addEventListener('error', function(e) {
console.log('Stream error, attempting reconnect in 3 seconds...');
setTimeout(function() {