Update authentication system, database models, and UI components

This commit is contained in:
oib
2025-08-07 19:39:22 +02:00
parent d497492186
commit 72f79b1059
48 changed files with 5328 additions and 1642 deletions

View File

@ -1,81 +1,57 @@
import { showToast } from "./toast.js";
import { globalAudioManager } from './global-audio-manager.js';
import { SharedAudioPlayer } from './shared-audio-player.js';
// Module-level state for the personal player
let audio = null;
function getPersonalStreamUrl(uid) {
return `/audio/${encodeURIComponent(uid)}/stream.opus`;
}
function updatePlayPauseButton(button, isPlaying) {
if (button) button.textContent = isPlaying ? '⏸️' : '▶️';
// Optionally, update other UI elements here
}
const personalPlayer = new SharedAudioPlayer({
playerType: 'personal',
getStreamUrl: getPersonalStreamUrl,
onUpdateButton: updatePlayPauseButton
});
/**
* Finds or creates the audio element for the personal stream.
* @returns {HTMLAudioElement | null}
*/
function getOrCreateAudioElement() {
if (audio) {
return audio;
}
audio = document.createElement('audio');
audio.id = 'me-audio';
audio.preload = 'metadata';
audio.crossOrigin = 'use-credentials';
document.body.appendChild(audio);
// --- Setup Event Listeners (only once) ---
audio.addEventListener('error', (e) => {
console.error('Personal Player: Audio Element Error', e);
const error = audio.error;
let errorMessage = 'An unknown audio error occurred.';
if (error) {
switch (error.code) {
case error.MEDIA_ERR_ABORTED:
errorMessage = 'Audio playback was aborted.';
break;
case error.MEDIA_ERR_NETWORK:
errorMessage = 'A network error caused the audio to fail.';
break;
case error.MEDIA_ERR_DECODE:
errorMessage = 'The audio could not be decoded.';
break;
case error.MEDIA_ERR_SRC_NOT_SUPPORTED:
errorMessage = 'The audio format is not supported by your browser.';
break;
default:
errorMessage = `An unexpected error occurred (Code: ${error.code}).`;
break;
function cleanupPersonalAudio() {
if (audioElement) {
try {
if (audioElement._eventHandlers) {
const { onPlay, onPause, onEnded, onError } = audioElement._eventHandlers;
if (onPlay) audioElement.removeEventListener('play', onPlay);
if (onPause) audioElement.removeEventListener('pause', onPause);
if (onEnded) audioElement.removeEventListener('ended', onEnded);
if (onError) audioElement.removeEventListener('error', onError);
}
audioElement.pause();
audioElement.removeAttribute('src');
audioElement.load();
if (audioElement._eventHandlers) delete audioElement._eventHandlers;
// Remove from DOM
if (audioElement.parentNode) audioElement.parentNode.removeChild(audioElement);
} catch (e) {
console.warn('[personal-player.js] Error cleaning up audio element:', e);
}
showToast(errorMessage, 'error');
});
audio.addEventListener('play', () => updatePlayPauseButton(true));
audio.addEventListener('pause', () => updatePlayPauseButton(false));
audio.addEventListener('ended', () => updatePlayPauseButton(false));
// The canplaythrough listener is removed as it violates autoplay policies.
// The user will perform a second click to play the media after it's loaded.
return audio;
}
/**
* Updates the play/pause button icon based on audio state.
* @param {boolean} isPlaying - Whether the audio is currently playing.
*/
function updatePlayPauseButton(isPlaying) {
const playPauseBtn = document.querySelector('#me-page .play-pause-btn');
if (playPauseBtn) {
playPauseBtn.textContent = isPlaying ? '⏸️' : '▶️';
audioElement = null;
}
}
/**
* Loads the user's personal audio stream into the player.
* @param {string} uid - The user's unique ID.
*/
export async function loadProfileStream(uid) {
const audioElement = getOrCreateAudioElement();
const audioSrc = `/audio/${uid}/stream.opus?t=${Date.now()}`;
console.log(`[personal-player.js] Setting personal audio source to: ${audioSrc}`);
audioElement.src = audioSrc;
// Use the shared player for loading and playing the personal stream
export function loadProfileStream(uid, playPauseBtn) {
if (!uid) {
showToast('No UID provided for profile stream', 'error');
return;
}
personalPlayer.play(uid, playPauseBtn);
}
/**
@ -91,50 +67,19 @@ export function initPersonalPlayer() {
if (!playPauseBtn) return;
e.stopPropagation();
const audio = getOrCreateAudioElement();
if (!audio) return;
try {
if (audio.paused) {
if (!audio.src || audio.src.endsWith('/#')) {
showToast('No audio file available. Please upload one first.', 'info');
return;
}
console.log('Attempting to play...');
globalAudioManager.startPlayback('personal', localStorage.getItem('uid') || 'personal');
const playPromise = audio.play();
if (playPromise !== undefined) {
playPromise.catch(error => {
console.error(`Initial play() failed: ${error.name}. This is expected on first load.`);
// If play fails, it's because the content isn't loaded.
// The recovery is to call load(). The user will need to click play again.
console.log('Calling load() to fetch media...');
audio.load();
showToast('Stream is loading. Please click play again in a moment.', 'info');
});
}
} else {
console.log('Attempting to pause...');
audio.pause();
}
} catch (err) {
console.error('A synchronous error occurred in handlePlayPause:', err);
showToast('An unexpected error occurred with the audio player.', 'error');
const uid = localStorage.getItem('uid');
if (!uid) {
showToast('Please log in to play audio.', 'error');
return;
}
// Toggle play/pause
if (personalPlayer.audioElement && !personalPlayer.audioElement.paused && !personalPlayer.audioElement.ended) {
personalPlayer.pause();
} else {
loadProfileStream(uid, playPauseBtn);
}
});
// Listen for stop requests from the global manager
globalAudioManager.addListener('personal', () => {
console.log('[personal-player.js] Received stop request from global audio manager.');
const audio = getOrCreateAudioElement();
if (audio && !audio.paused) {
console.log('[personal-player.js] Pausing personal audio player.');
audio.pause();
}
});
// Initial setup
getOrCreateAudioElement();
// Make loadProfileStream globally accessible for upload.js
window.loadProfileStream = loadProfileStream;
}