215 lines
7.6 KiB
Plaintext
215 lines
7.6 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<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="/api/asteroid/spectrum-analyzer.js"></script>
|
|
</head>
|
|
<body class="persistent-player-container">
|
|
<div class="persistent-player">
|
|
<span class="player-label">
|
|
<span class="live-stream-indicator">🟢 </span>
|
|
LIVE:
|
|
</span>
|
|
|
|
<div class="quality-selector">
|
|
<input type="hidden" id="stream-base-url" lquery="(val stream-base-url)">
|
|
<label for="stream-quality">Quality:</label>
|
|
<select id="stream-quality" onchange="changeStreamQuality()">
|
|
<option value="aac">AAC 96k</option>
|
|
<option value="mp3">MP3 128k</option>
|
|
<option value="low">MP3 64k</option>
|
|
</select>
|
|
</div>
|
|
|
|
<audio id="persistent-audio" controls preload="metadata" crossorigin="anonymous">
|
|
<source id="audio-source" lquery="(attr :src default-stream-url :type default-stream-encoding)">
|
|
</audio>
|
|
|
|
<span class="now-playing-mini" id="mini-now-playing">Loading...</span>
|
|
|
|
<button onclick="disableFramesetMode()" class="persistent-disable-btn">
|
|
✕ Disable
|
|
</button>
|
|
|
|
<!-- Compact Spectrum Analyzer -->
|
|
<div style="display: inline-block; margin-left: 15px;">
|
|
<canvas id="spectrum-canvas" width="400" height="40" style="border: 1px solid #00ff00; background: #000; border-radius: 3px; vertical-align: middle;"></canvas>
|
|
<select id="spectrum-style-selector" onchange="setSpectrumStyle(this.value)" style="margin-left: 5px; padding: 2px; background: #000; color: #00ff00; border: 1px solid #00ff00; font-size: 0.8em; vertical-align: middle;">
|
|
<option value="bars">Bars</option>
|
|
<option value="wave">Wave</option>
|
|
<option value="dots">Dots</option>
|
|
</select>
|
|
<select id="spectrum-theme-selector" onchange="setSpectrumTheme(this.value)" style="margin-left: 3px; padding: 2px; background: #000; color: #00ff00; border: 1px solid #00ff00; font-size: 0.8em; vertical-align: middle;">
|
|
<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>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Track if audio should be playing
|
|
let shouldBePlaying = false;
|
|
let keepAliveInterval = null;
|
|
|
|
// Configure audio element for better streaming
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const audioElement = document.getElementById('persistent-audio');
|
|
|
|
// Try to enable low-latency mode if supported
|
|
if ('mediaSession' in navigator) {
|
|
navigator.mediaSession.metadata = new MediaMetadata({
|
|
title: 'Asteroid Radio Live Stream',
|
|
artist: 'Asteroid Radio',
|
|
album: 'Live Broadcast'
|
|
});
|
|
}
|
|
|
|
// Track when user intentionally plays/pauses
|
|
audioElement.addEventListener('play', function() {
|
|
console.log('Audio play event');
|
|
shouldBePlaying = true;
|
|
startKeepAlive();
|
|
});
|
|
|
|
audioElement.addEventListener('playing', function() {
|
|
console.log('Audio playing');
|
|
shouldBePlaying = true;
|
|
});
|
|
|
|
// Only set shouldBePlaying to false if user clicked pause button
|
|
const originalPause = audioElement.pause.bind(audioElement);
|
|
audioElement.pause = function() {
|
|
console.log('User paused audio');
|
|
shouldBePlaying = false;
|
|
stopKeepAlive();
|
|
originalPause();
|
|
};
|
|
|
|
// Add event listeners for debugging
|
|
audioElement.addEventListener('waiting', function() {
|
|
console.log('Audio buffering...');
|
|
});
|
|
|
|
audioElement.addEventListener('error', function(e) {
|
|
console.error('Audio error:', e);
|
|
});
|
|
|
|
// Monitor for unexpected pauses
|
|
audioElement.addEventListener('pause', function(e) {
|
|
console.log('Audio paused, shouldBePlaying:', shouldBePlaying);
|
|
if (shouldBePlaying && !audioElement.ended) {
|
|
console.log('Unexpected pause detected, resuming...');
|
|
setTimeout(function() {
|
|
if (audioElement.paused && shouldBePlaying) {
|
|
audioElement.play().catch(err => console.log('Resume failed:', err));
|
|
}
|
|
}, 50);
|
|
}
|
|
});
|
|
|
|
const selector = document.getElementById('stream-quality');
|
|
const streamQuality = localStorage.getItem('stream-quality') || 'aac';
|
|
if (selector && selector.value !== streamQuality) {
|
|
selector.value = streamQuality;
|
|
selector.dispatchEvent(new Event('change'));
|
|
}
|
|
});
|
|
|
|
// Stream quality configuration
|
|
function getStreamConfig(streamBaseUrl, encoding) {
|
|
const config = {
|
|
aac: {
|
|
url: streamBaseUrl + '/asteroid.aac',
|
|
type: 'audio/aac'
|
|
},
|
|
mp3: {
|
|
url: streamBaseUrl + '/asteroid.mp3',
|
|
type: 'audio/mpeg'
|
|
},
|
|
low: {
|
|
url: streamBaseUrl + '/asteroid-low.mp3',
|
|
type: 'audio/mpeg'
|
|
}
|
|
};
|
|
return config[encoding];
|
|
}
|
|
|
|
// Change stream quality
|
|
function changeStreamQuality() {
|
|
const selector = document.getElementById('stream-quality');
|
|
const streamBaseUrl = document.getElementById('stream-base-url').value;
|
|
const config = getStreamConfig(streamBaseUrl, selector.value);
|
|
|
|
// Save preference
|
|
localStorage.setItem('stream-quality', selector.value);
|
|
|
|
const audioElement = document.getElementById('persistent-audio');
|
|
const sourceElement = document.getElementById('audio-source');
|
|
|
|
const wasPlaying = !audioElement.paused;
|
|
|
|
sourceElement.src = config.url;
|
|
sourceElement.type = config.type;
|
|
audioElement.load();
|
|
|
|
if (wasPlaying) {
|
|
audioElement.play().catch(e => console.log('Autoplay prevented:', e));
|
|
}
|
|
}
|
|
|
|
// Update mini now playing display
|
|
async function updateMiniNowPlaying() {
|
|
try {
|
|
const response = await fetch('/api/asteroid/partial/now-playing-inline');
|
|
if (response.ok) {
|
|
const text = await response.text();
|
|
document.getElementById('mini-now-playing').textContent = text;
|
|
}
|
|
} catch(error) {
|
|
console.log('Could not fetch now playing:', error);
|
|
}
|
|
}
|
|
|
|
// Update every 10 seconds
|
|
setTimeout(updateMiniNowPlaying, 1000);
|
|
setInterval(updateMiniNowPlaying, 10000);
|
|
|
|
// Keep-alive functions to maintain playback
|
|
function startKeepAlive() {
|
|
if (keepAliveInterval) return;
|
|
console.log('Starting audio keep-alive');
|
|
keepAliveInterval = setInterval(function() {
|
|
const audioElement = document.getElementById('persistent-audio');
|
|
if (shouldBePlaying && audioElement.paused && !audioElement.ended) {
|
|
console.log('Keep-alive: resuming paused audio');
|
|
audioElement.play().catch(err => console.log('Keep-alive resume failed:', err));
|
|
}
|
|
}, 500); // Check every 500ms
|
|
}
|
|
|
|
function stopKeepAlive() {
|
|
if (keepAliveInterval) {
|
|
console.log('Stopping audio keep-alive');
|
|
clearInterval(keepAliveInterval);
|
|
keepAliveInterval = null;
|
|
}
|
|
}
|
|
|
|
// Disable frameset mode function
|
|
function disableFramesetMode() {
|
|
// Clear preference
|
|
localStorage.removeItem('useFrameset');
|
|
// Redirect parent window to player page (non-frameset)
|
|
window.parent.location.href = '/asteroid/player';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|