207 lines
8.2 KiB
Plaintext
207 lines
8.2 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>🎵 Asteroid Radio - 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/front-page.js"></script>
|
|
</head>
|
|
<body class="popout-body">
|
|
<div class="popout-container">
|
|
<div class="popout-header">
|
|
<div class="popout-title">
|
|
🎵 Asteroid Radio
|
|
<br/>
|
|
<span class="popout-subtitle">The Station at the End of Time</span>
|
|
</div>
|
|
<button class="close-btn" onclick="window.close()">✖ Close</button>
|
|
</div>
|
|
|
|
<div class="now-playing-mini">
|
|
<div class="track-info-mini">
|
|
<div class="track-title-mini" id="popout-track-title">Loading...</div>
|
|
<div class="track-artist-mini" id="popout-track-artist">Please wait</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="quality-selector">
|
|
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
|
|
<label for="popout-stream-quality"><strong>Quality:</strong></label>
|
|
<select id="popout-stream-quality" onchange="changeStreamQuality()">
|
|
<option value="aac">AAC 96kbps</option>
|
|
<option value="mp3">MP3 128kbps</option>
|
|
<option value="low">MP3 64kbps</option>
|
|
</select>
|
|
</div>
|
|
|
|
<audio id="live-audio" controls autoplay style="width: 100%;">
|
|
<source id="audio-source" lquery="(attr :src default-stream-url :type default-stream-encoding)">
|
|
Your browser does not support the audio element.
|
|
</audio>
|
|
|
|
<div class="status-mini">
|
|
<span style="color: #00ff00;">
|
|
<span class="live-stream-indicator">●</span>
|
|
LIVE</span>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Stream quality configuration for popout
|
|
function getStreamConfig(streamBaseUrl, encoding) {
|
|
const config = {
|
|
aac: {
|
|
url: `${streamBaseUrl}/asteroid.aac`,
|
|
format: 'AAC 96kbps Stereo',
|
|
type: 'audio/aac',
|
|
mount: 'asteroid.aac'
|
|
},
|
|
mp3: {
|
|
url: `${streamBaseUrl}/asteroid.mp3`,
|
|
format: 'MP3 128kbps Stereo',
|
|
type: 'audio/mpeg',
|
|
mount: 'asteroid.mp3'
|
|
},
|
|
low: {
|
|
url: `${streamBaseUrl}/asteroid-low.mp3`,
|
|
format: 'MP3 64kbps Stereo',
|
|
type: 'audio/mpeg',
|
|
mount: 'asteroid-low.mp3'
|
|
}
|
|
};
|
|
return config[encoding];
|
|
}
|
|
|
|
// Change stream quality in popout
|
|
function changeStreamQuality() {
|
|
const selector = document.getElementById('popout-stream-quality');
|
|
const streamBaseUrl = document.getElementById('stream-base-url');
|
|
const config = getStreamConfig(streamBaseUrl.value, selector.value);
|
|
|
|
// Update audio player
|
|
const audioElement = document.getElementById('live-audio');
|
|
const sourceElement = document.getElementById('audio-source');
|
|
|
|
const wasPlaying = !audioElement.paused;
|
|
|
|
sourceElement.src = config.url;
|
|
sourceElement.type = config.type;
|
|
audioElement.load();
|
|
|
|
// Resume playback if it was playing
|
|
if (wasPlaying) {
|
|
audioElement.play().catch(e => console.log('Autoplay prevented:', e));
|
|
}
|
|
}
|
|
|
|
// Update now playing info for popout
|
|
async function updatePopoutNowPlaying() {
|
|
try {
|
|
const response = await fetch('/api/asteroid/partial/now-playing-inline');
|
|
const html = await response.text();
|
|
|
|
// Parse the HTML to extract track info
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(html, 'text/html');
|
|
const trackText = doc.body.textContent || doc.body.innerText || '';
|
|
|
|
// Try to split artist - title format
|
|
const parts = trackText.split(' - ');
|
|
if (parts.length >= 2) {
|
|
document.getElementById('popout-track-artist').textContent = parts[0].trim();
|
|
document.getElementById('popout-track-title').textContent = parts.slice(1).join(' - ').trim();
|
|
} else {
|
|
document.getElementById('popout-track-title').textContent = trackText.trim();
|
|
document.getElementById('popout-track-artist').textContent = 'Asteroid Radio';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating now playing:', error);
|
|
}
|
|
}
|
|
|
|
// Update every 10 seconds
|
|
setInterval(updatePopoutNowPlaying, 10000);
|
|
// Initial update
|
|
updatePopoutNowPlaying();
|
|
|
|
// Auto-reconnect on stream errors
|
|
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) {
|
|
console.error('Audio error:', e);
|
|
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.play().catch(err => console.log('Reconnect failed:', err));
|
|
}, delay);
|
|
});
|
|
|
|
audioElement.addEventListener('stalled', function() {
|
|
if (isReconnecting) return;
|
|
console.log('Stream stalled, will auto-reconnect in 5 seconds...');
|
|
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);
|
|
});
|
|
|
|
// Handle pause event - detect browser throttling muted streams
|
|
audioElement.addEventListener('pause', function() {
|
|
if (audioElement.muted && !isReconnecting) {
|
|
console.log('Stream paused while muted (possible browser throttling), reconnecting...');
|
|
isReconnecting = true;
|
|
setTimeout(function() {
|
|
audioElement.load();
|
|
audioElement.play().catch(err => console.log('Reconnect failed:', err));
|
|
}, 3000);
|
|
}
|
|
});
|
|
|
|
// Notify parent window that popout is open
|
|
if (window.opener && !window.opener.closed) {
|
|
window.opener.postMessage({ type: 'popout-opened' }, '*');
|
|
}
|
|
|
|
// Notify parent when closing
|
|
window.addEventListener('beforeunload', function() {
|
|
if (window.opener && !window.opener.closed) {
|
|
window.opener.postMessage({ type: 'popout-closed' }, '*');
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|