Reorganize project structure
- Move development and test files to dev/ directory - Update .gitignore to exclude development files - Update paths in configuration files - Add new audio-player.js for frontend
This commit is contained in:
424
static/audio-player.js
Normal file
424
static/audio-player.js
Normal file
@ -0,0 +1,424 @@
|
||||
/**
|
||||
* Audio Player Module
|
||||
* A shared audio player implementation based on the working "Your Stream" player
|
||||
*/
|
||||
|
||||
export class AudioPlayer {
|
||||
constructor() {
|
||||
// Audio state
|
||||
this.audioElement = null;
|
||||
this.currentUid = null;
|
||||
this.isPlaying = false;
|
||||
this.currentButton = null;
|
||||
this.audioUrl = '';
|
||||
this.lastPlayTime = 0;
|
||||
this.isLoading = false;
|
||||
this.loadTimeout = null; // For tracking loading timeouts
|
||||
|
||||
// Create a single audio element that we'll reuse
|
||||
this.audioElement = new Audio();
|
||||
this.audioElement.preload = 'none';
|
||||
this.audioElement.crossOrigin = 'anonymous';
|
||||
|
||||
// Bind methods
|
||||
this.loadAndPlay = this.loadAndPlay.bind(this);
|
||||
this.stop = this.stop.bind(this);
|
||||
this.cleanup = this.cleanup.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and play audio for a specific UID
|
||||
* @param {string} uid - The user ID for the audio stream
|
||||
* @param {HTMLElement} button - The play/pause button element
|
||||
*/
|
||||
/**
|
||||
* Validates that a UID is in the correct UUID format
|
||||
* @param {string} uid - The UID to validate
|
||||
* @returns {boolean} True if valid, false otherwise
|
||||
*/
|
||||
isValidUuid(uid) {
|
||||
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
return uuidRegex.test(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error and updates the button state
|
||||
* @param {HTMLElement} button - The button to update
|
||||
* @param {string} message - Error message to log
|
||||
*/
|
||||
handleError(button, message) {
|
||||
console.error(message);
|
||||
if (button) {
|
||||
this.updateButtonState(button, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async loadAndPlay(uid, button) {
|
||||
// Validate UID exists and is in correct format
|
||||
if (!uid) {
|
||||
this.handleError(button, 'No UID provided for audio playback');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isValidUuid(uid)) {
|
||||
this.handleError(button, `Invalid UID format: ${uid}. Expected UUID v4 format.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in the middle of loading, check if it's for the same UID
|
||||
if (this.isLoading) {
|
||||
// If same UID, ignore duplicate request
|
||||
if (this.currentUid === uid) {
|
||||
console.log('Already loading this UID, ignoring duplicate request:', uid);
|
||||
return;
|
||||
}
|
||||
// If different UID, queue the new request
|
||||
console.log('Already loading, queuing request for UID:', uid);
|
||||
setTimeout(() => this.loadAndPlay(uid, button), 500);
|
||||
return;
|
||||
}
|
||||
|
||||
// If already playing this stream, just toggle pause/play
|
||||
if (this.currentUid === uid && this.audioElement) {
|
||||
try {
|
||||
if (this.isPlaying) {
|
||||
console.log('Pausing current playback');
|
||||
try {
|
||||
this.audioElement.pause();
|
||||
this.lastPlayTime = this.audioElement.currentTime;
|
||||
this.isPlaying = false;
|
||||
this.updateButtonState(button, 'paused');
|
||||
} catch (pauseError) {
|
||||
console.warn('Error pausing audio, continuing with state update:', pauseError);
|
||||
this.isPlaying = false;
|
||||
this.updateButtonState(button, 'paused');
|
||||
}
|
||||
} else {
|
||||
console.log('Resuming playback from time:', this.lastPlayTime);
|
||||
try {
|
||||
// If we have a last play time, seek to it
|
||||
if (this.lastPlayTime > 0) {
|
||||
this.audioElement.currentTime = this.lastPlayTime;
|
||||
}
|
||||
await this.audioElement.play();
|
||||
this.isPlaying = true;
|
||||
this.updateButtonState(button, 'playing');
|
||||
} catch (playError) {
|
||||
console.error('Error resuming playback, reloading source:', playError);
|
||||
// If resume fails, try reloading the source
|
||||
this.currentUid = null; // Force reload of the source
|
||||
return this.loadAndPlay(uid, button);
|
||||
}
|
||||
}
|
||||
return; // Exit after handling pause/resume
|
||||
} catch (error) {
|
||||
console.error('Error toggling playback:', error);
|
||||
this.updateButtonState(button, 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we're loading a new stream
|
||||
this.isLoading = true;
|
||||
this.currentUid = uid;
|
||||
this.currentButton = button;
|
||||
this.isPlaying = true;
|
||||
this.updateButtonState(button, 'loading');
|
||||
|
||||
try {
|
||||
// Only clean up if switching streams
|
||||
if (this.currentUid !== uid) {
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
// Store the current button reference
|
||||
this.currentButton = button;
|
||||
this.currentUid = uid;
|
||||
|
||||
// Create a new audio element if we don't have one
|
||||
if (!this.audioElement) {
|
||||
this.audioElement = new Audio();
|
||||
} else if (this.audioElement.readyState > 0) {
|
||||
// If we already have a loaded source, just play it
|
||||
try {
|
||||
await this.audioElement.play();
|
||||
this.isPlaying = true;
|
||||
this.updateButtonState(button, 'playing');
|
||||
return;
|
||||
} catch (playError) {
|
||||
console.warn('Error playing existing source, will reload:', playError);
|
||||
// Continue to load a new source
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any existing sources
|
||||
while (this.audioElement.firstChild) {
|
||||
this.audioElement.removeChild(this.audioElement.firstChild);
|
||||
}
|
||||
|
||||
// Set the source URL with proper encoding and cache-busting timestamp
|
||||
// Using the format: /audio/{uid}/stream.opus?t={timestamp}
|
||||
const timestamp = new Date().getTime();
|
||||
this.audioUrl = `/audio/${encodeURIComponent(uid)}/stream.opus?t=${timestamp}`;
|
||||
console.log('Loading audio from URL:', this.audioUrl);
|
||||
this.audioElement.src = this.audioUrl;
|
||||
|
||||
// Load the new source (don't await, let canplay handle it)
|
||||
try {
|
||||
this.audioElement.load();
|
||||
// If load() doesn't throw, we'll wait for canplay event
|
||||
} catch (e) {
|
||||
// Ignore abort errors as they're expected during rapid toggling
|
||||
if (e.name !== 'AbortError') {
|
||||
console.error('Error loading audio source:', e);
|
||||
this.isLoading = false;
|
||||
this.updateButtonState(button, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the current time when loading a new source
|
||||
this.audioElement.currentTime = 0;
|
||||
this.lastPlayTime = 0;
|
||||
|
||||
// Set up error handling
|
||||
this.audioElement.onerror = (e) => {
|
||||
console.error('Audio element error:', e, this.audioElement.error);
|
||||
this.isLoading = false;
|
||||
this.updateButtonState(button, 'error');
|
||||
};
|
||||
|
||||
// Handle when audio is ready to play
|
||||
const onCanPlay = () => {
|
||||
this.audioElement.removeEventListener('canplay', onCanPlay);
|
||||
this.isLoading = false;
|
||||
if (this.lastPlayTime > 0) {
|
||||
this.audioElement.currentTime = this.lastPlayTime;
|
||||
}
|
||||
this.audioElement.play().then(() => {
|
||||
this.isPlaying = true;
|
||||
this.updateButtonState(button, 'playing');
|
||||
}).catch(e => {
|
||||
console.error('Error playing after load:', e);
|
||||
this.updateButtonState(button, 'error');
|
||||
});
|
||||
};
|
||||
|
||||
// Define the error handler
|
||||
const errorHandler = (e) => {
|
||||
console.error('Audio element error:', e, this.audioElement.error);
|
||||
this.isLoading = false;
|
||||
this.updateButtonState(button, 'error');
|
||||
};
|
||||
|
||||
// Define the play handler
|
||||
const playHandler = () => {
|
||||
// Clear any pending timeouts
|
||||
if (this.loadTimeout) {
|
||||
clearTimeout(this.loadTimeout);
|
||||
this.loadTimeout = null;
|
||||
}
|
||||
|
||||
this.audioElement.removeEventListener('canplay', playHandler);
|
||||
this.isLoading = false;
|
||||
|
||||
if (this.lastPlayTime > 0) {
|
||||
this.audioElement.currentTime = this.lastPlayTime;
|
||||
}
|
||||
|
||||
this.audioElement.play().then(() => {
|
||||
this.isPlaying = true;
|
||||
this.updateButtonState(button, 'playing');
|
||||
}).catch(e => {
|
||||
console.error('Error playing after load:', e);
|
||||
this.isPlaying = false;
|
||||
this.updateButtonState(button, 'error');
|
||||
});
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
this.audioElement.addEventListener('error', errorHandler, { once: true });
|
||||
this.audioElement.addEventListener('canplay', playHandler, { once: true });
|
||||
|
||||
// Load and play the new source
|
||||
try {
|
||||
await this.audioElement.load();
|
||||
// Don't await play() here, let the canplay handler handle it
|
||||
|
||||
// Set a timeout to handle cases where canplay doesn't fire
|
||||
this.loadTimeout = setTimeout(() => {
|
||||
if (this.isLoading) {
|
||||
console.warn('Audio loading timed out for UID:', uid);
|
||||
this.isLoading = false;
|
||||
this.updateButtonState(button, 'error');
|
||||
}
|
||||
}, 10000); // 10 second timeout
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error loading audio:', e);
|
||||
this.isLoading = false;
|
||||
this.updateButtonState(button, 'error');
|
||||
|
||||
// Clear any pending timeouts
|
||||
if (this.loadTimeout) {
|
||||
clearTimeout(this.loadTimeout);
|
||||
this.loadTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in loadAndPlay:', error);
|
||||
|
||||
// Only cleanup and show error if we're still on the same track
|
||||
if (this.currentUid === uid) {
|
||||
this.cleanup();
|
||||
this.updateButtonState(button, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playback and clean up resources
|
||||
*/
|
||||
stop() {
|
||||
try {
|
||||
if (this.audioElement) {
|
||||
console.log('Stopping audio playback');
|
||||
this.audioElement.pause();
|
||||
this.lastPlayTime = this.audioElement.currentTime;
|
||||
this.isPlaying = false;
|
||||
if (this.currentButton) {
|
||||
this.updateButtonState(this.currentButton, 'paused');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error stopping audio:', error);
|
||||
// Don't throw, just log the error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
cleanup() {
|
||||
// Update button state if we have a reference to the current button
|
||||
if (this.currentButton) {
|
||||
this.updateButtonState(this.currentButton, 'paused');
|
||||
}
|
||||
|
||||
// Pause the audio and store the current time
|
||||
if (this.audioElement) {
|
||||
try {
|
||||
try {
|
||||
this.audioElement.pause();
|
||||
this.lastPlayTime = this.audioElement.currentTime;
|
||||
} catch (e) {
|
||||
console.warn('Error pausing audio during cleanup:', e);
|
||||
}
|
||||
|
||||
try {
|
||||
// Clear any existing sources
|
||||
while (this.audioElement.firstChild) {
|
||||
this.audioElement.removeChild(this.audioElement.firstChild);
|
||||
}
|
||||
|
||||
// Clear the source and reset the audio element
|
||||
this.audioElement.removeAttribute('src');
|
||||
try {
|
||||
this.audioElement.load();
|
||||
} catch (e) {
|
||||
console.warn('Error in audio load during cleanup:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error cleaning up audio sources:', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error during audio cleanup:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset state
|
||||
this.currentUid = null;
|
||||
this.currentButton = null;
|
||||
this.audioUrl = '';
|
||||
this.isPlaying = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of a play/pause button
|
||||
* @param {HTMLElement} button - The button to update
|
||||
* @param {string} state - The state to set ('playing', 'paused', 'loading', 'error')
|
||||
*/
|
||||
updateButtonState(button, state) {
|
||||
if (!button) return;
|
||||
|
||||
// Only update the current button's state
|
||||
if (state === 'playing') {
|
||||
// If this button is now playing, update all buttons
|
||||
document.querySelectorAll('.play-pause-btn').forEach(btn => {
|
||||
btn.classList.remove('playing', 'paused', 'loading', 'error');
|
||||
if (btn === button) {
|
||||
btn.classList.add('playing');
|
||||
} else {
|
||||
btn.classList.add('paused');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// For other states, just update the target button
|
||||
button.classList.remove('playing', 'paused', 'loading', 'error');
|
||||
if (state) {
|
||||
button.classList.add(state);
|
||||
}
|
||||
}
|
||||
|
||||
// Update button icon and aria-label for the target button
|
||||
const icon = button.querySelector('i');
|
||||
if (icon) {
|
||||
if (state === 'playing') {
|
||||
icon.className = 'fas fa-pause';
|
||||
button.setAttribute('aria-label', 'Pause');
|
||||
} else {
|
||||
icon.className = 'fas fa-play';
|
||||
button.setAttribute('aria-label', 'Play');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a singleton instance
|
||||
export const audioPlayer = new AudioPlayer();
|
||||
|
||||
// Export utility functions for direct use
|
||||
export function initAudioPlayer(container = document) {
|
||||
// Set up event delegation for play/pause buttons
|
||||
container.addEventListener('click', (e) => {
|
||||
const playButton = e.target.closest('.play-pause-btn');
|
||||
if (!playButton) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const uid = playButton.dataset.uid;
|
||||
if (!uid) return;
|
||||
|
||||
audioPlayer.loadAndPlay(uid, playButton);
|
||||
});
|
||||
|
||||
// Set up event delegation for stop buttons if they exist
|
||||
container.addEventListener('click', (e) => {
|
||||
const stopButton = e.target.closest('.stop-btn');
|
||||
if (!stopButton) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
audioPlayer.stop();
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-initialize if this is the main module
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initAudioPlayer();
|
||||
});
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audio Player Test</title>
|
||||
<style>
|
||||
:root {
|
||||
--success: #2e8b57;
|
||||
--error: #ff4444;
|
||||
--border: #444;
|
||||
--text-color: #f0f0f0;
|
||||
--surface: #2a2a2a;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
background: #1a1a1a;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.test-case {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 5px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.success { color: var(--success); }
|
||||
.error { color: var(--error); }
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
background: #4a6fa5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button:hover {
|
||||
background: #3a5a8c;
|
||||
}
|
||||
#log {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.audio-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
audio {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Audio Player Test</h1>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Test 1: Direct Audio Element</h2>
|
||||
<div class="audio-container">
|
||||
<audio id="direct-audio" controls>
|
||||
<source src="/audio/devuser/stream.opus" type="audio/ogg; codecs=opus">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="document.getElementById('direct-audio').play()">Play</button>
|
||||
<button onclick="document.getElementById('direct-audio').pause()">Pause</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Test 2: Dynamic Audio Element</h2>
|
||||
<div id="dynamic-audio-container">
|
||||
<button onclick="setupDynamicAudio()">Initialize Dynamic Audio</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Test 3: Using loadProfileStream</h2>
|
||||
<div id="load-profile-container">
|
||||
<button onclick="testLoadProfileStream()">Test loadProfileStream</button>
|
||||
<div id="test3-status">Not started</div>
|
||||
<div class="audio-container">
|
||||
<audio id="profile-audio" controls></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Browser Audio Support</h2>
|
||||
<div id="codec-support">Testing codec support...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Console Log</h2>
|
||||
<div id="log"></div>
|
||||
<button onclick="document.getElementById('log').innerHTML = ''">Clear Log</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Logging function
|
||||
function log(message, type = 'info') {
|
||||
const logDiv = document.getElementById('log');
|
||||
const entry = document.createElement('div');
|
||||
entry.className = type;
|
||||
entry.textContent = `[${new Date().toISOString()}] ${message}`;
|
||||
logDiv.appendChild(entry);
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||||
}
|
||||
|
||||
// Test 2: Dynamic Audio Element
|
||||
function setupDynamicAudio() {
|
||||
log('Setting up dynamic audio element...');
|
||||
const container = document.getElementById('dynamic-audio-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
const audio = document.createElement('audio');
|
||||
audio.controls = true;
|
||||
audio.preload = 'auto';
|
||||
audio.crossOrigin = 'anonymous';
|
||||
|
||||
const source = document.createElement('source');
|
||||
source.src = '/audio/devuser/stream.opus';
|
||||
source.type = 'audio/ogg; codecs=opus';
|
||||
|
||||
audio.appendChild(source);
|
||||
container.appendChild(audio);
|
||||
container.appendChild(document.createElement('br'));
|
||||
|
||||
const playBtn = document.createElement('button');
|
||||
playBtn.textContent = 'Play';
|
||||
playBtn.onclick = () => {
|
||||
audio.play().catch(e => log(`Play error: ${e}`, 'error'));
|
||||
};
|
||||
container.appendChild(playBtn);
|
||||
|
||||
const pauseBtn = document.createElement('button');
|
||||
pauseBtn.textContent = 'Pause';
|
||||
pauseBtn.onclick = () => audio.pause();
|
||||
container.appendChild(pauseBtn);
|
||||
|
||||
log('Dynamic audio element created successfully');
|
||||
} catch (e) {
|
||||
log(`Error creating dynamic audio: ${e}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: loadProfileStream
|
||||
async function testLoadProfileStream() {
|
||||
const status = document.getElementById('test3-status');
|
||||
status.textContent = 'Loading...';
|
||||
status.className = '';
|
||||
|
||||
try {
|
||||
// Import the loadProfileStream function from app.js
|
||||
const { loadProfileStream } = await import('./app.js');
|
||||
|
||||
if (typeof loadProfileStream !== 'function') {
|
||||
throw new Error('loadProfileStream function not found');
|
||||
}
|
||||
|
||||
// Call loadProfileStream with test user
|
||||
const audio = await loadProfileStream('devuser');
|
||||
|
||||
if (audio) {
|
||||
status.textContent = 'Audio loaded successfully!';
|
||||
status.className = 'success';
|
||||
log('Audio loaded successfully', 'success');
|
||||
|
||||
// Add the audio element to the page
|
||||
const audioContainer = document.querySelector('#load-profile-container .audio-container');
|
||||
audioContainer.innerHTML = '';
|
||||
audio.controls = true;
|
||||
audioContainer.appendChild(audio);
|
||||
} else {
|
||||
status.textContent = 'No audio available for test user';
|
||||
status.className = '';
|
||||
log('No audio available for test user', 'info');
|
||||
}
|
||||
} catch (e) {
|
||||
status.textContent = `Error: ${e.message}`;
|
||||
status.className = 'error';
|
||||
log(`Error in loadProfileStream: ${e}`, 'error');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Check browser audio support
|
||||
function checkAudioSupport() {
|
||||
const supportDiv = document.getElementById('codec-support');
|
||||
const audio = document.createElement('audio');
|
||||
|
||||
const codecs = {
|
||||
'audio/ogg; codecs=opus': 'Opus (OGG)',
|
||||
'audio/webm; codecs=opus': 'Opus (WebM)',
|
||||
'audio/mp4; codecs=mp4a.40.2': 'AAC (MP4)',
|
||||
'audio/mpeg': 'MP3'
|
||||
};
|
||||
|
||||
let results = [];
|
||||
|
||||
for (const [type, name] of Object.entries(codecs)) {
|
||||
const canPlay = audio.canPlayType(type);
|
||||
results.push(`${name}: ${canPlay || 'Not supported'}`);
|
||||
}
|
||||
|
||||
supportDiv.innerHTML = results.join('<br>');
|
||||
}
|
||||
|
||||
// Initialize tests
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
log('Test page loaded');
|
||||
checkAudioSupport();
|
||||
|
||||
// Log audio element events for debugging
|
||||
const audioElements = document.getElementsByTagName('audio');
|
||||
Array.from(audioElements).forEach((audio, index) => {
|
||||
['play', 'pause', 'error', 'stalled', 'suspend', 'abort', 'emptied', 'ended'].forEach(event => {
|
||||
audio.addEventListener(event, (e) => {
|
||||
log(`Audio ${index + 1} ${event} event: ${e.type}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,210 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audio Player Test</title>
|
||||
<style>
|
||||
:root {
|
||||
--success: #2e8b57;
|
||||
--error: #ff4444;
|
||||
--border: #444;
|
||||
--text-color: #f0f0f0;
|
||||
--surface: #2a2a2a;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
line-height: 1.6;
|
||||
background: #1a1a1a;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.test-case {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 5px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.success { color: var(--success); }
|
||||
.error { color: var(--error); }
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
background: #4a6fa5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button:hover {
|
||||
background: #3a5a8c;
|
||||
}
|
||||
#log {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Audio Player Test</h1>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Test 1: Basic Audio Element</h2>
|
||||
<audio id="test1" controls>
|
||||
<source src="/static/test-audio.opus" type="audio/ogg; codecs=opus">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
<div>
|
||||
<button onclick="document.getElementById('test1').play()">Play</button>
|
||||
<button onclick="document.getElementById('test1').pause()">Pause</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Test 2: Dynamic Audio Element</h2>
|
||||
<div id="test2-container">
|
||||
<button onclick="setupTest2()">Initialize Audio</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Test 3: Using loadProfileStream</h2>
|
||||
<div id="test3-container">
|
||||
<button onclick="testLoadProfileStream()">Test loadProfileStream</button>
|
||||
<div id="test3-status">Not started</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Browser Audio Support</h2>
|
||||
<div id="codec-support">Testing codec support...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-case">
|
||||
<h2>Console Log</h2>
|
||||
<div id="log"></div>
|
||||
<button onclick="document.getElementById('log').innerHTML = ''">Clear Log</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Logging function
|
||||
function log(message, type = 'info') {
|
||||
const logDiv = document.getElementById('log');
|
||||
const entry = document.createElement('div');
|
||||
entry.className = type;
|
||||
entry.textContent = `[${new Date().toISOString()}] ${message}`;
|
||||
logDiv.appendChild(entry);
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||||
}
|
||||
|
||||
// Test 2: Dynamic Audio Element
|
||||
function setupTest2() {
|
||||
log('Setting up dynamic audio element...');
|
||||
const container = document.getElementById('test2-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
const audio = document.createElement('audio');
|
||||
audio.controls = true;
|
||||
audio.preload = 'auto';
|
||||
|
||||
const source = document.createElement('source');
|
||||
source.src = '/static/test-audio.opus';
|
||||
source.type = 'audio/ogg; codecs=opus';
|
||||
|
||||
audio.appendChild(source);
|
||||
container.appendChild(audio);
|
||||
container.appendChild(document.createElement('br'));
|
||||
|
||||
const playBtn = document.createElement('button');
|
||||
playBtn.textContent = 'Play';
|
||||
playBtn.onclick = () => audio.play().catch(e => log(`Play error: ${e}`, 'error'));
|
||||
container.appendChild(playBtn);
|
||||
|
||||
const pauseBtn = document.createElement('button');
|
||||
pauseBtn.textContent = 'Pause';
|
||||
pauseBtn.onclick = () => audio.pause();
|
||||
container.appendChild(pauseBtn);
|
||||
|
||||
log('Dynamic audio element created successfully');
|
||||
} catch (e) {
|
||||
log(`Error creating dynamic audio: ${e}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Test 3: loadProfileStream
|
||||
async function testLoadProfileStream() {
|
||||
const status = document.getElementById('test3-status');
|
||||
status.textContent = 'Loading...';
|
||||
status.className = '';
|
||||
|
||||
try {
|
||||
// Create a test user ID
|
||||
const testUid = 'test-user-' + Math.random().toString(36).substr(2, 8);
|
||||
log(`Testing with user: ${testUid}`);
|
||||
|
||||
// Call loadProfileStream
|
||||
const audio = await window.loadProfileStream(testUid);
|
||||
|
||||
if (audio) {
|
||||
status.textContent = 'Audio loaded successfully!';
|
||||
status.className = 'success';
|
||||
log('Audio loaded successfully', 'success');
|
||||
} else {
|
||||
status.textContent = 'No audio available for test user';
|
||||
status.className = '';
|
||||
log('No audio available for test user', 'info');
|
||||
}
|
||||
} catch (e) {
|
||||
status.textContent = `Error: ${e.message}`;
|
||||
status.className = 'error';
|
||||
log(`Error in loadProfileStream: ${e}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Check browser audio support
|
||||
function checkAudioSupport() {
|
||||
const supportDiv = document.getElementById('codec-support');
|
||||
const audio = document.createElement('audio');
|
||||
|
||||
const codecs = {
|
||||
'audio/ogg; codecs=opus': 'Opus (OGG)',
|
||||
'audio/webm; codecs=opus': 'Opus (WebM)',
|
||||
'audio/mp4; codecs=mp4a.40.2': 'AAC (MP4)',
|
||||
'audio/mpeg': 'MP3'
|
||||
};
|
||||
|
||||
let results = [];
|
||||
|
||||
for (const [type, name] of Object.entries(codecs)) {
|
||||
const canPlay = audio.canPlayType(type);
|
||||
results.push(`${name}: ${canPlay || 'Not supported'}`);
|
||||
}
|
||||
|
||||
supportDiv.innerHTML = results.join('<br>');
|
||||
}
|
||||
|
||||
// Initialize tests
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
log('Test page loaded');
|
||||
checkAudioSupport();
|
||||
|
||||
// Expose loadProfileStream for testing
|
||||
if (!window.loadProfileStream) {
|
||||
log('Warning: loadProfileStream not found in global scope', 'warning');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Reference in New Issue
Block a user