asteroid/template/player.chtml

369 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<title data-text="title">Asteroid Radio - Web Player</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/static/asteroid.css">
</head>
<body>
<div class="container">
<h1>🎵 WEB PLAYER</h1>
<div class="nav">
<a href="/">← Back to Main</a>
<a href="/admin">Admin Dashboard</a>
</div>
<!-- Track Browser -->
<div class="player-section">
<h2>Track Library</h2>
<div class="track-browser">
<input type="text" id="search-tracks" placeholder="Search tracks..." class="search-input">
<div id="track-list" class="track-list">
<div class="loading">Loading tracks...</div>
</div>
</div>
</div>
<!-- Audio Player Widget -->
<div class="player-section">
<h2>Audio Player</h2>
<div class="audio-player">
<div class="now-playing">
<div class="track-art">🎵</div>
<div class="track-details">
<div class="track-title" id="current-title">No track selected</div>
<div class="track-artist" id="current-artist">Unknown Artist</div>
<div class="track-album" id="current-album">Unknown Album</div>
</div>
</div>
<audio id="audio-player" controls preload="none" style="width: 100%; margin: 20px 0;">
Your browser does not support the audio element.
</audio>
<div class="player-controls">
<button id="prev-btn" class="btn btn-secondary">⏮️ Previous</button>
<button id="play-pause-btn" class="btn btn-primary">▶️ Play</button>
<button id="next-btn" class="btn btn-secondary">⏭️ Next</button>
<button id="shuffle-btn" class="btn btn-info">🔀 Shuffle</button>
<button id="repeat-btn" class="btn btn-warning">🔁 Repeat</button>
</div>
<div class="player-info">
<div class="time-display">
<span id="current-time">0:00</span> / <span id="total-time">0:00</span>
</div>
<div class="volume-control">
<label for="volume-slider">🔊</label>
<input type="range" id="volume-slider" min="0" max="100" value="50" class="volume-slider">
</div>
</div>
</div>
</div>
<!-- Playlist Management -->
<div class="player-section">
<h2>Playlists</h2>
<div class="playlist-controls">
<input type="text" id="new-playlist-name" placeholder="New playlist name..." class="playlist-input">
<button id="create-playlist" class="btn btn-success"> Create Playlist</button>
</div>
<div class="playlist-list">
<div id="playlists-container">
<div class="no-playlists">No playlists created yet.</div>
</div>
</div>
</div>
<!-- Queue -->
<div class="player-section">
<h2>Play Queue</h2>
<div class="queue-controls">
<button id="clear-queue" class="btn btn-danger">🗑️ Clear Queue</button>
<button id="save-queue" class="btn btn-info">💾 Save as Playlist</button>
</div>
<div id="play-queue" class="play-queue">
<div class="empty-queue">Queue is empty</div>
</div>
</div>
</div>
<script>
// Web Player JavaScript
let tracks = [];
let currentTrack = null;
let currentTrackIndex = -1;
let playQueue = [];
let isShuffled = false;
let isRepeating = false;
let audioPlayer = null;
document.addEventListener('DOMContentLoaded', function() {
audioPlayer = document.getElementById('audio-player');
loadTracks();
setupEventListeners();
updatePlayerDisplay();
});
function setupEventListeners() {
// Search
document.getElementById('search-tracks').addEventListener('input', filterTracks);
// Player controls
document.getElementById('play-pause-btn').addEventListener('click', togglePlayPause);
document.getElementById('prev-btn').addEventListener('click', playPrevious);
document.getElementById('next-btn').addEventListener('click', playNext);
document.getElementById('shuffle-btn').addEventListener('click', toggleShuffle);
document.getElementById('repeat-btn').addEventListener('click', toggleRepeat);
// Volume control
document.getElementById('volume-slider').addEventListener('input', updateVolume);
// Audio player events
audioPlayer.addEventListener('loadedmetadata', updateTimeDisplay);
audioPlayer.addEventListener('timeupdate', updateTimeDisplay);
audioPlayer.addEventListener('ended', handleTrackEnd);
audioPlayer.addEventListener('play', () => updatePlayButton('⏸️ Pause'));
audioPlayer.addEventListener('pause', () => updatePlayButton('▶️ Play'));
// Playlist controls
document.getElementById('create-playlist').addEventListener('click', createPlaylist);
document.getElementById('clear-queue').addEventListener('click', clearQueue);
document.getElementById('save-queue').addEventListener('click', saveQueueAsPlaylist);
}
async function loadTracks() {
try {
const response = await fetch('/admin/tracks');
const data = await response.json();
if (data.status === 'success') {
tracks = data.tracks || [];
displayTracks(tracks);
}
} catch (error) {
console.error('Error loading tracks:', error);
document.getElementById('track-list').innerHTML = '<div class="error">Error loading tracks</div>';
}
}
function displayTracks(trackList) {
const container = document.getElementById('track-list');
if (trackList.length === 0) {
container.innerHTML = '<div class="no-tracks">No tracks found</div>';
return;
}
const tracksHtml = trackList.map((track, index) => `
<div class="track-item" data-track-id="${track.id}" data-index="${index}">
<div class="track-info">
<div class="track-title">${track.title[0] || 'Unknown Title'}</div>
<div class="track-meta">${track.artist[0] || 'Unknown Artist'} • ${track.album[0] || 'Unknown Album'}</div>
</div>
<div class="track-actions">
<button onclick="playTrack(${index})" class="btn btn-sm btn-success">▶️</button>
<button onclick="addToQueue(${index})" class="btn btn-sm btn-info"></button>
</div>
</div>
`).join('');
container.innerHTML = tracksHtml;
}
function filterTracks() {
const query = document.getElementById('search-tracks').value.toLowerCase();
const filtered = tracks.filter(track =>
(track.title[0] || '').toLowerCase().includes(query) ||
(track.artist[0] || '').toLowerCase().includes(query) ||
(track.album[0] || '').toLowerCase().includes(query)
);
displayTracks(filtered);
}
function playTrack(index) {
if (index < 0 || index >= tracks.length) return;
currentTrack = tracks[index];
currentTrackIndex = index;
// Load track into audio player
audioPlayer.src = `/asteroid/tracks/${currentTrack.id}/stream`;
audioPlayer.load();
audioPlayer.play().catch(error => {
console.error('Playback error:', error);
alert('Error playing track. The track may not be available.');
});
updatePlayerDisplay();
// Update server-side player state
fetch(`/api/play?track-id=${currentTrack.id}`, { method: 'POST' })
.catch(error => console.error('API update error:', error));
}
function togglePlayPause() {
if (!currentTrack) {
alert('Please select a track to play');
return;
}
if (audioPlayer.paused) {
audioPlayer.play();
} else {
audioPlayer.pause();
}
}
function playPrevious() {
if (playQueue.length > 0) {
// Play from queue
const prevIndex = Math.max(0, currentTrackIndex - 1);
playTrack(prevIndex);
} else {
// Play previous track in library
const prevIndex = currentTrackIndex > 0 ? currentTrackIndex - 1 : tracks.length - 1;
playTrack(prevIndex);
}
}
function playNext() {
if (playQueue.length > 0) {
// Play from queue
const nextTrack = playQueue.shift();
playTrack(tracks.findIndex(t => t.id === nextTrack.id));
updateQueueDisplay();
} else {
// Play next track in library
const nextIndex = isShuffled ?
Math.floor(Math.random() * tracks.length) :
(currentTrackIndex + 1) % tracks.length;
playTrack(nextIndex);
}
}
function handleTrackEnd() {
if (isRepeating) {
audioPlayer.currentTime = 0;
audioPlayer.play();
} else {
playNext();
}
}
function toggleShuffle() {
isShuffled = !isShuffled;
const btn = document.getElementById('shuffle-btn');
btn.textContent = isShuffled ? '🔀 Shuffle ON' : '🔀 Shuffle';
btn.classList.toggle('active', isShuffled);
}
function toggleRepeat() {
isRepeating = !isRepeating;
const btn = document.getElementById('repeat-btn');
btn.textContent = isRepeating ? '🔁 Repeat ON' : '🔁 Repeat';
btn.classList.toggle('active', isRepeating);
}
function updateVolume() {
const volume = document.getElementById('volume-slider').value / 100;
audioPlayer.volume = volume;
}
function updateTimeDisplay() {
const current = formatTime(audioPlayer.currentTime);
const total = formatTime(audioPlayer.duration);
document.getElementById('current-time').textContent = current;
document.getElementById('total-time').textContent = total;
}
function formatTime(seconds) {
if (isNaN(seconds)) return '0:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
function updatePlayButton(text) {
document.getElementById('play-pause-btn').textContent = text;
}
function updatePlayerDisplay() {
if (currentTrack) {
document.getElementById('current-title').textContent = currentTrack.title[0] || 'Unknown Title';
document.getElementById('current-artist').textContent = currentTrack.artist[0] || 'Unknown Artist';
document.getElementById('current-album').textContent = currentTrack.album[0] || 'Unknown Album';
}
}
function addToQueue(index) {
if (index < 0 || index >= tracks.length) return;
playQueue.push(tracks[index]);
updateQueueDisplay();
}
function updateQueueDisplay() {
const container = document.getElementById('play-queue');
if (playQueue.length === 0) {
container.innerHTML = '<div class="empty-queue">Queue is empty</div>';
return;
}
const queueHtml = playQueue.map((track, index) => `
<div class="queue-item">
<div class="track-info">
<div class="track-title">${track.title[0] || 'Unknown Title'}</div>
<div class="track-meta">${track.artist[0] || 'Unknown Artist'}</div>
</div>
<button onclick="removeFromQueue(${index})" class="btn btn-sm btn-danger">✖️</button>
</div>
`).join('');
container.innerHTML = queueHtml;
}
function removeFromQueue(index) {
playQueue.splice(index, 1);
updateQueueDisplay();
}
function clearQueue() {
playQueue = [];
updateQueueDisplay();
}
function createPlaylist() {
const name = document.getElementById('new-playlist-name').value.trim();
if (!name) {
alert('Please enter a playlist name');
return;
}
// TODO: Implement playlist creation API
alert('Playlist creation not yet implemented');
document.getElementById('new-playlist-name').value = '';
}
function saveQueueAsPlaylist() {
if (playQueue.length === 0) {
alert('Queue is empty');
return;
}
const name = prompt('Enter playlist name:');
if (name) {
// TODO: Implement save queue as playlist
alert('Save queue as playlist not yet implemented');
}
}
// Initialize volume
updateVolume();
</script>
</body>
</html>