asteroid/template/audio-player-frame.ctml

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>