Add robust auto-reconnect to all audio players
- Implement isReconnecting flag to prevent duplicate reconnect attempts - Add exponential backoff for error retries (3s, 6s, 12s, max 30s) - Retry indefinitely until stream returns - Handle error, stalled, and ended events consistently - Reset state on successful playback - Apply same logic to frame player, popout player, and front-page player
This commit is contained in:
parent
8b0f7e7705
commit
2cd128260c
|
|
@ -311,38 +311,50 @@
|
||||||
(defun attach-audio-event-listeners (audio-element)
|
(defun attach-audio-event-listeners (audio-element)
|
||||||
"Attach all necessary event listeners to an audio element"
|
"Attach all necessary event listeners to an audio element"
|
||||||
|
|
||||||
;; Error handler
|
;; Error handler - retry indefinitely with exponential backoff
|
||||||
(ps:chain audio-element
|
(ps:chain audio-element
|
||||||
(add-event-listener "error"
|
(add-event-listener "error"
|
||||||
(lambda (err)
|
(lambda (err)
|
||||||
(incf *stream-error-count*)
|
|
||||||
(ps:chain console (log "Stream error:" err))
|
(ps:chain console (log "Stream error:" err))
|
||||||
|
(when *is-reconnecting*
|
||||||
(if (< *stream-error-count* 3)
|
(return))
|
||||||
;; Auto-retry for first few errors
|
(incf *stream-error-count*)
|
||||||
(progn
|
;; Calculate delay with exponential backoff (3s, 6s, 12s, max 30s)
|
||||||
(show-stream-status (+ "⚠️ Stream error. Reconnecting... (attempt " *stream-error-count* ")") "warning")
|
(let ((delay (ps:chain |Math| (min (* 3000 (ps:chain |Math| (pow 2 (- *stream-error-count* 1)))) 30000))))
|
||||||
(setf *reconnect-timeout*
|
(show-stream-status (+ "⚠️ Stream error. Reconnecting in " (/ delay 1000) "s... (attempt " *stream-error-count* ")") "warning")
|
||||||
(set-timeout reconnect-stream 3000)))
|
(setf *is-reconnecting* t)
|
||||||
;; Too many errors, show manual reconnect
|
(setf *reconnect-timeout*
|
||||||
(progn
|
(set-timeout reconnect-stream delay))))))
|
||||||
(show-stream-status "❌ Stream connection lost. Click Reconnect to try again." "error")
|
|
||||||
(show-reconnect-button))))))
|
|
||||||
|
|
||||||
;; Stalled handler
|
;; Stalled handler
|
||||||
(ps:chain audio-element
|
(ps:chain audio-element
|
||||||
(add-event-listener "stalled"
|
(add-event-listener "stalled"
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(ps:chain console (log "Stream stalled"))
|
(when *is-reconnecting*
|
||||||
(show-stream-status "⚠️ Stream stalled. Attempting to recover..." "warning")
|
(return))
|
||||||
|
(ps:chain console (log "Stream stalled, will auto-reconnect in 5 seconds..."))
|
||||||
|
(show-stream-status "⚠️ Stream stalled - reconnecting..." "warning")
|
||||||
|
(setf *is-reconnecting* t)
|
||||||
(setf *reconnect-timeout*
|
(setf *reconnect-timeout*
|
||||||
(set-timeout
|
(set-timeout
|
||||||
(lambda ()
|
(lambda ()
|
||||||
;; Only reconnect if still stalled
|
;; Only reconnect if still stalled
|
||||||
(when (ps:@ audio-element paused)
|
(if (< (ps:@ audio-element ready-state) 3)
|
||||||
(reconnect-stream)))
|
(reconnect-stream)
|
||||||
|
(setf *is-reconnecting* false)))
|
||||||
5000)))))
|
5000)))))
|
||||||
|
|
||||||
|
;; Ended handler - stream shouldn't end, so reconnect
|
||||||
|
(ps:chain audio-element
|
||||||
|
(add-event-listener "ended"
|
||||||
|
(lambda ()
|
||||||
|
(when *is-reconnecting*
|
||||||
|
(return))
|
||||||
|
(ps:chain console (log "Stream ended unexpectedly, reconnecting..."))
|
||||||
|
(show-stream-status "⚠️ Stream ended - reconnecting..." "warning")
|
||||||
|
(setf *is-reconnecting* t)
|
||||||
|
(set-timeout reconnect-stream 2000))))
|
||||||
|
|
||||||
;; Waiting handler (buffering)
|
;; Waiting handler (buffering)
|
||||||
(ps:chain audio-element
|
(ps:chain audio-element
|
||||||
(add-event-listener "waiting"
|
(add-event-listener "waiting"
|
||||||
|
|
@ -355,6 +367,7 @@
|
||||||
(add-event-listener "playing"
|
(add-event-listener "playing"
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(setf *stream-error-count* 0)
|
(setf *stream-error-count* 0)
|
||||||
|
(setf *is-reconnecting* false)
|
||||||
(hide-stream-status)
|
(hide-stream-status)
|
||||||
(hide-reconnect-button)
|
(hide-reconnect-button)
|
||||||
(when *reconnect-timeout*
|
(when *reconnect-timeout*
|
||||||
|
|
|
||||||
|
|
@ -54,18 +54,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add event listeners for debugging
|
// Note: Main event listeners are attached via attachAudioListeners()
|
||||||
audioElement.addEventListener('waiting', function() {
|
// which is called at the bottom of this script
|
||||||
console.log('Audio buffering...');
|
|
||||||
});
|
|
||||||
|
|
||||||
audioElement.addEventListener('playing', function() {
|
|
||||||
console.log('Audio playing');
|
|
||||||
});
|
|
||||||
|
|
||||||
audioElement.addEventListener('error', function(e) {
|
|
||||||
console.error('Audio error:', e);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selector = document.getElementById('stream-quality');
|
const selector = document.getElementById('stream-quality');
|
||||||
const streamQuality = localStorage.getItem('stream-quality') || 'aac';
|
const streamQuality = localStorage.getItem('stream-quality') || 'aac';
|
||||||
|
|
@ -251,6 +241,11 @@
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error retry counter and reconnect state
|
||||||
|
var streamErrorCount = 0;
|
||||||
|
var reconnectTimeout = null;
|
||||||
|
var isReconnecting = false;
|
||||||
|
|
||||||
// Attach event listeners to audio element
|
// Attach event listeners to audio element
|
||||||
function attachAudioListeners(audioElement) {
|
function attachAudioListeners(audioElement) {
|
||||||
audioElement.addEventListener('waiting', function() {
|
audioElement.addEventListener('waiting', function() {
|
||||||
|
|
@ -260,16 +255,50 @@
|
||||||
audioElement.addEventListener('playing', function() {
|
audioElement.addEventListener('playing', function() {
|
||||||
console.log('Audio playing');
|
console.log('Audio playing');
|
||||||
hideStatus();
|
hideStatus();
|
||||||
|
streamErrorCount = 0; // Reset error count on successful play
|
||||||
|
isReconnecting = false; // Reset reconnecting flag
|
||||||
|
if (reconnectTimeout) {
|
||||||
|
clearTimeout(reconnectTimeout);
|
||||||
|
reconnectTimeout = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
audioElement.addEventListener('error', function(e) {
|
audioElement.addEventListener('error', function(e) {
|
||||||
console.error('Audio error:', e);
|
console.error('Audio error:', e);
|
||||||
showStatus('⚠️ Stream error - click 🔄 to reconnect', true);
|
if (isReconnecting) return; // Already reconnecting, skip
|
||||||
|
streamErrorCount++;
|
||||||
|
// Calculate delay with exponential backoff (3s, 6s, 12s, max 30s)
|
||||||
|
var delay = Math.min(3000 * Math.pow(2, streamErrorCount - 1), 30000);
|
||||||
|
showStatus('⚠️ Stream error. Reconnecting in ' + (delay/1000) + 's... (attempt ' + streamErrorCount + ')', true);
|
||||||
|
isReconnecting = true;
|
||||||
|
reconnectTimeout = setTimeout(function() {
|
||||||
|
reconnectStream();
|
||||||
|
}, delay);
|
||||||
});
|
});
|
||||||
|
|
||||||
audioElement.addEventListener('stalled', function() {
|
audioElement.addEventListener('stalled', function() {
|
||||||
console.log('Audio stalled');
|
if (isReconnecting) return; // Already reconnecting, skip
|
||||||
showStatus('⚠️ Stream stalled - click 🔄 if no audio', true);
|
console.log('Audio stalled, will auto-reconnect in 5 seconds...');
|
||||||
|
showStatus('⚠️ Stream stalled - reconnecting...', true);
|
||||||
|
isReconnecting = true;
|
||||||
|
setTimeout(function() {
|
||||||
|
if (audioElement.readyState < 3) {
|
||||||
|
reconnectStream();
|
||||||
|
} else {
|
||||||
|
isReconnecting = false;
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle ended event - stream shouldn't end, so reconnect
|
||||||
|
audioElement.addEventListener('ended', function() {
|
||||||
|
if (isReconnecting) return; // Already reconnecting, skip
|
||||||
|
console.log('Stream ended unexpectedly, reconnecting...');
|
||||||
|
showStatus('⚠️ Stream ended - reconnecting...', true);
|
||||||
|
isReconnecting = true;
|
||||||
|
setTimeout(function() {
|
||||||
|
reconnectStream();
|
||||||
|
}, 2000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,18 +127,55 @@
|
||||||
|
|
||||||
// Auto-reconnect on stream errors
|
// Auto-reconnect on stream errors
|
||||||
const audioElement = document.getElementById('live-audio');
|
const audioElement = document.getElementById('live-audio');
|
||||||
|
var streamErrorCount = 0;
|
||||||
|
var reconnectTimeout = null;
|
||||||
|
var isReconnecting = false;
|
||||||
|
|
||||||
|
audioElement.addEventListener('playing', function() {
|
||||||
|
console.log('Audio playing');
|
||||||
|
streamErrorCount = 0;
|
||||||
|
isReconnecting = false;
|
||||||
|
if (reconnectTimeout) {
|
||||||
|
clearTimeout(reconnectTimeout);
|
||||||
|
reconnectTimeout = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
audioElement.addEventListener('error', function(e) {
|
audioElement.addEventListener('error', function(e) {
|
||||||
console.log('Stream error, attempting reconnect in 3 seconds...');
|
console.error('Audio error:', e);
|
||||||
setTimeout(function() {
|
if (isReconnecting) return;
|
||||||
|
streamErrorCount++;
|
||||||
|
var delay = Math.min(3000 * Math.pow(2, streamErrorCount - 1), 30000);
|
||||||
|
console.log('Stream error. Reconnecting in ' + (delay/1000) + 's... (attempt ' + streamErrorCount + ')');
|
||||||
|
isReconnecting = true;
|
||||||
|
reconnectTimeout = setTimeout(function() {
|
||||||
audioElement.load();
|
audioElement.load();
|
||||||
audioElement.play().catch(err => console.log('Reconnect failed:', err));
|
audioElement.play().catch(err => console.log('Reconnect failed:', err));
|
||||||
}, 3000);
|
}, delay);
|
||||||
});
|
});
|
||||||
|
|
||||||
audioElement.addEventListener('stalled', function() {
|
audioElement.addEventListener('stalled', function() {
|
||||||
console.log('Stream stalled, reloading...');
|
if (isReconnecting) return;
|
||||||
audioElement.load();
|
console.log('Stream stalled, will auto-reconnect in 5 seconds...');
|
||||||
audioElement.play().catch(err => console.log('Reload failed:', err));
|
isReconnecting = true;
|
||||||
|
setTimeout(function() {
|
||||||
|
if (audioElement.readyState < 3) {
|
||||||
|
audioElement.load();
|
||||||
|
audioElement.play().catch(err => console.log('Reload failed:', err));
|
||||||
|
} else {
|
||||||
|
isReconnecting = false;
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
audioElement.addEventListener('ended', function() {
|
||||||
|
if (isReconnecting) return;
|
||||||
|
console.log('Stream ended unexpectedly, reconnecting...');
|
||||||
|
isReconnecting = true;
|
||||||
|
setTimeout(function() {
|
||||||
|
audioElement.load();
|
||||||
|
audioElement.play().catch(err => console.log('Reconnect failed:', err));
|
||||||
|
}, 2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify parent window that popout is open
|
// Notify parent window that popout is open
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue