This commit is contained in:
Flatlogic Bot 2026-02-26 21:02:37 +00:00
parent 8e65ab42ed
commit f9592521da

View File

@ -30,6 +30,8 @@ import {
mdiCog, mdiCog,
mdiRefresh, mdiRefresh,
mdiTrashCan, mdiTrashCan,
mdiAlertCircle,
mdiTune,
} from '@mdi/js'; } from '@mdi/js';
import BaseIcon from '../components/BaseIcon'; import BaseIcon from '../components/BaseIcon';
import { useAppDispatch } from '../stores/hooks'; import { useAppDispatch } from '../stores/hooks';
@ -129,6 +131,7 @@ const ObservationPage = () => {
const dryGainRef = useRef<GainNode | null>(null); const dryGainRef = useRef<GainNode | null>(null);
const wetGainRef = useRef<GainNode | null>(null); const wetGainRef = useRef<GainNode | null>(null);
const monitorGainRef = useRef<GainNode | null>(null); const monitorGainRef = useRef<GainNode | null>(null);
const masterGainRef = useRef<GainNode | null>(null);
const [isCameraActive, setIsCameraActive] = useState(false); const [isCameraActive, setIsCameraActive] = useState(false);
const [isKaraokeActive, setIsKaraokeActive] = useState(false); const [isKaraokeActive, setIsKaraokeActive] = useState(false);
@ -139,6 +142,8 @@ const ObservationPage = () => {
const [isMonitorOn, setIsMonitorOn] = useState(false); const [isMonitorOn, setIsMonitorOn] = useState(false);
const [reverbLevel, setReverbLevel] = useState(0.5); const [reverbLevel, setReverbLevel] = useState(0.5);
const [playbackProgress, setPlaybackProgress] = useState(0); const [playbackProgress, setPlaybackProgress] = useState(0);
const [audioStatus, setAudioStatus] = useState<'IDLE' | 'LOADING' | 'ACTIVE' | 'ERROR'>('IDLE');
const [volumeLevel, setVolumeLevel] = useState(1.0);
const [audienceCount, setAudienceCount] = useState(0); const [audienceCount, setAudienceCount] = useState(0);
const [isSimActive, setIsSimActive] = useState(false); const [isSimActive, setIsSimActive] = useState(false);
@ -167,11 +172,27 @@ const ObservationPage = () => {
return [...userSongs, ...INITIAL_SONG_DATABASE]; return [...userSongs, ...INITIAL_SONG_DATABASE];
}, [userSongs]); }, [userSongs]);
// Global Audio Enabler
useEffect(() => {
const handleInteraction = () => {
if (audioCtxRef.current?.state === 'suspended') {
audioCtxRef.current.resume().catch(console.error);
}
};
window.addEventListener('click', handleInteraction);
window.addEventListener('touchstart', handleInteraction);
return () => {
window.removeEventListener('click', handleInteraction);
window.removeEventListener('touchstart', handleInteraction);
};
}, []);
// Persistent karaoke audio setup // Persistent karaoke audio setup
useEffect(() => { useEffect(() => {
const audio = new Audio(); const audio = new Audio();
audio.crossOrigin = "anonymous"; audio.crossOrigin = "anonymous";
audio.preload = "auto"; audio.preload = "auto";
audio.volume = 1.0;
karaokeAudioRef.current = audio; karaokeAudioRef.current = audio;
const updateLyrics = () => { const updateLyrics = () => {
@ -191,13 +212,18 @@ const ObservationPage = () => {
]; ];
const index = Math.floor(audio.currentTime / 5) % texts.length; const index = Math.floor(audio.currentTime / 5) % texts.length;
setCurrentLyrics(texts[index]); setCurrentLyrics(texts[index]);
setAudioStatus('ACTIVE');
} }
}; };
audio.addEventListener('timeupdate', updateLyrics); audio.addEventListener('timeupdate', updateLyrics);
audio.addEventListener('play', () => setAudioStatus('ACTIVE'));
audio.addEventListener('waiting', () => setAudioStatus('LOADING'));
audio.addEventListener('error', () => setAudioStatus('ERROR'));
audio.addEventListener('ended', () => { audio.addEventListener('ended', () => {
setIsKaraokeActive(false); setIsKaraokeActive(false);
setCurrentSong(null); setCurrentSong(null);
setAudioStatus('IDLE');
}); });
return () => { return () => {
@ -217,49 +243,57 @@ const ObservationPage = () => {
}, [fullSongDatabase, searchQuery, selectedGenre]); }, [fullSongDatabase, searchQuery, selectedGenre]);
const initAudioContext = async (stream: MediaStream) => { const initAudioContext = async (stream: MediaStream) => {
if (!audioCtxRef.current) { try {
audioCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)(); if (!audioCtxRef.current) {
} audioCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)();
}
if (audioCtxRef.current.state === 'suspended') { if (audioCtxRef.current.state === 'suspended') {
await audioCtxRef.current.resume(); await audioCtxRef.current.resume();
} }
if (!audioDestRef.current) { if (!audioDestRef.current) {
audioDestRef.current = audioCtxRef.current.createMediaStreamDestination(); audioDestRef.current = audioCtxRef.current.createMediaStreamDestination();
masterGainRef.current = audioCtxRef.current.createGain();
micNodeRef.current = audioCtxRef.current.createMediaStreamSource(stream); masterGainRef.current.gain.value = 1.0;
dryGainRef.current = audioCtxRef.current.createGain(); micNodeRef.current = audioCtxRef.current.createMediaStreamSource(stream);
wetGainRef.current = audioCtxRef.current.createGain();
monitorGainRef.current = audioCtxRef.current.createGain(); dryGainRef.current = audioCtxRef.current.createGain();
reverbNodeRef.current = audioCtxRef.current.createConvolver(); wetGainRef.current = audioCtxRef.current.createGain();
monitorGainRef.current = audioCtxRef.current.createGain();
const length = 2 * audioCtxRef.current.sampleRate; reverbNodeRef.current = audioCtxRef.current.createConvolver();
const impulse = audioCtxRef.current.createBuffer(2, length, audioCtxRef.current.sampleRate);
for (let i = 0; i < 2; i++) { const length = 2 * audioCtxRef.current.sampleRate;
const channel = impulse.getChannelData(i); const impulse = audioCtxRef.current.createBuffer(2, length, audioCtxRef.current.sampleRate);
for (let j = 0; j < length; j++) { for (let i = 0; i < 2; i++) {
channel[j] = (Math.random() * 2 - 1) * Math.pow(1 - j / length, 2); const channel = impulse.getChannelData(i);
for (let j = 0; j < length; j++) {
channel[j] = (Math.random() * 2 - 1) * Math.pow(1 - j / length, 2);
}
}
reverbNodeRef.current.buffer = impulse;
micNodeRef.current.connect(dryGainRef.current);
micNodeRef.current.connect(reverbNodeRef.current);
micNodeRef.current.connect(monitorGainRef.current);
dryGainRef.current.connect(audioDestRef.current);
reverbNodeRef.current.connect(wetGainRef.current);
wetGainRef.current.connect(audioDestRef.current);
monitorGainRef.current.connect(audioCtxRef.current.destination);
masterGainRef.current.connect(audioCtxRef.current.destination);
if (karaokeAudioRef.current && !karaokeNodeRef.current) {
karaokeNodeRef.current = audioCtxRef.current.createMediaElementSource(karaokeAudioRef.current);
karaokeNodeRef.current.connect(audioDestRef.current);
karaokeNodeRef.current.connect(masterGainRef.current);
} }
} }
reverbNodeRef.current.buffer = impulse; } catch (err) {
console.error("Audio Context Init Error:", err);
micNodeRef.current.connect(dryGainRef.current); setAudioStatus('ERROR');
micNodeRef.current.connect(reverbNodeRef.current);
micNodeRef.current.connect(monitorGainRef.current);
micNodeRef.current.connect(audioDestRef.current);
reverbNodeRef.current.connect(wetGainRef.current);
wetGainRef.current.connect(audioDestRef.current);
monitorGainRef.current.connect(audioCtxRef.current.destination);
if (karaokeAudioRef.current && !karaokeNodeRef.current) {
karaokeNodeRef.current = audioCtxRef.current.createMediaElementSource(karaokeAudioRef.current);
karaokeNodeRef.current.connect(audioDestRef.current);
karaokeNodeRef.current.connect(audioCtxRef.current.destination);
}
} }
}; };
@ -287,7 +321,10 @@ const ObservationPage = () => {
wetGainRef.current.gain.value = isMicOn ? reverbLevel : 0; wetGainRef.current.gain.value = isMicOn ? reverbLevel : 0;
monitorGainRef.current.gain.value = isMonitorOn ? 1.0 : 0; monitorGainRef.current.gain.value = isMonitorOn ? 1.0 : 0;
} }
}, [isMicOn, reverbLevel, isMonitorOn]); if (masterGainRef.current) {
masterGainRef.current.gain.value = volumeLevel;
}
}, [isMicOn, reverbLevel, isMonitorOn, volumeLevel]);
const getCachedImage = useCallback((url: string) => { const getCachedImage = useCallback((url: string) => {
if (imageCache.current.has(url)) return imageCache.current.get(url); if (imageCache.current.has(url)) return imageCache.current.get(url);
@ -307,30 +344,42 @@ const ObservationPage = () => {
setIsKaraokeActive(true); setIsKaraokeActive(true);
setCurrentSong(song); setCurrentSong(song);
setPlaybackProgress(0); setPlaybackProgress(0);
setCurrentLyrics("INICIANDO PLAYBACK..."); setAudioStatus('LOADING');
setCurrentLyrics("SINCRONIZANDO PLAYBACK...");
setIsPaused(false); setIsPaused(false);
setIsSimActive(true); setIsSimActive(true);
setShowPlaylist(false); setShowPlaylist(false);
audio.pause(); audio.pause();
audio.muted = false;
audio.volume = 1.0;
// Prioritize user blob URL for this specific song ID // Prioritize user blob URL for this specific song ID
audio.src = customAudioMap[song.id] || song.url; const targetUrl = customAudioMap[song.id] || song.url;
audio.src = targetUrl;
audio.load(); audio.load();
try { setTimeout(async () => {
await audio.play(); try {
} catch (err) { await audio.play();
console.error("Playback error:", err); setAudioStatus('ACTIVE');
setCurrentLyrics("CLIQUE NO PLAY PARA INICIAR"); } catch (err) {
setIsPaused(true); console.error("Playback error:", err);
} setCurrentLyrics("ERRO NO ÁUDIO - CLIQUE PARA REENTRAR");
setAudioStatus('ERROR');
setIsPaused(true);
}
}, 200);
}; };
const togglePlayback = () => { const togglePlayback = () => {
const audio = karaokeAudioRef.current; const audio = karaokeAudioRef.current;
if (!audio) return; if (!audio) return;
if (audio.paused) { if (audio.paused) {
audio.play().catch(e => console.error(e)); audio.play().catch(e => {
console.error(e);
setAudioStatus('ERROR');
});
setIsPaused(false); setIsPaused(false);
} else { } else {
audio.pause(); audio.pause();
@ -461,14 +510,14 @@ const ObservationPage = () => {
ctx.save(); ctx.save();
ctx.fillStyle = 'rgba(0,0,0,0.85)'; ctx.fillStyle = 'rgba(0,0,0,0.85)';
ctx.fillRect(canvas.width/2 - 500, canvas.height - 250, 1000, 180); ctx.fillRect(canvas.width/2 - 500, canvas.height - 250, 1000, 180);
ctx.strokeStyle = '#E3B341'; ctx.lineWidth = 4; ctx.strokeStyle = audioStatus === 'ERROR' ? '#FF0000' : '#E3B341'; ctx.lineWidth = 4;
ctx.strokeRect(canvas.width/2 - 500, canvas.height - 250, 1000, 180); ctx.strokeRect(canvas.width/2 - 500, canvas.height - 250, 1000, 180);
ctx.fillStyle = '#E3B341'; ctx.font = 'bold 20px monospace'; ctx.textAlign = 'center'; ctx.fillStyle = '#E3B341'; ctx.font = 'bold 20px monospace'; ctx.textAlign = 'center';
ctx.fillText(`🎤 ${currentSong.title.toUpperCase()} - ${currentSong.artist.toUpperCase()} (${currentSong.genre})`, canvas.width/2, canvas.height - 210); ctx.fillText(`🎤 ${currentSong.title.toUpperCase()} - ${currentSong.artist.toUpperCase()} (${currentSong.genre})`, canvas.width/2, canvas.height - 210);
ctx.fillStyle = 'white'; ctx.font = 'bold 48px sans-serif'; ctx.fillStyle = 'white'; ctx.font = 'bold 48px sans-serif';
ctx.fillText(currentLyrics || "SOLTA O SOM!", canvas.width/2, canvas.height - 140); ctx.fillText(currentLyrics || "SOLTA O SOM!", canvas.width/2, canvas.height - 140);
ctx.fillStyle = 'rgba(255,255,255,0.1)'; ctx.fillRect(canvas.width/2 - 400, canvas.height - 100, 800, 10); ctx.fillStyle = 'rgba(255,255,255,0.1)'; ctx.fillRect(canvas.width/2 - 400, canvas.height - 100, 800, 10);
ctx.fillStyle = '#E3B341'; ctx.fillRect(canvas.width/2 - 400, canvas.height - 100, (playbackProgress / (karaokeAudioRef.current?.duration || 1)) * 800, 10); ctx.fillStyle = audioStatus === 'ACTIVE' ? '#E3B341' : '#666666'; ctx.fillRect(canvas.width/2 - 400, canvas.height - 100, (playbackProgress / (karaokeAudioRef.current?.duration || 1)) * 800, 10);
ctx.restore(); ctx.restore();
} }
@ -480,7 +529,7 @@ const ObservationPage = () => {
}; };
render(); render();
return () => cancelAnimationFrame(animationFrame); return () => cancelAnimationFrame(animationFrame);
}, [isCameraActive, isSimActive, activeViewers, featuredViewer, isKaraokeActive, currentLyrics, currentSong, getCachedImage, audienceCount, playbackProgress]); }, [isCameraActive, isSimActive, activeViewers, featuredViewer, isKaraokeActive, currentLyrics, currentSong, getCachedImage, audienceCount, playbackProgress, audioStatus]);
useEffect(() => { useEffect(() => {
let interval: any; let interval: any;
@ -526,7 +575,7 @@ const ObservationPage = () => {
<br /><span className="text-xl font-light tracking-widest text-white/60">BRASIL & MUNDO</span> <br /><span className="text-xl font-light tracking-widest text-white/60">BRASIL & MUNDO</span>
</h1> </h1>
<p className="text-white/80 font-bold uppercase tracking-widest max-w-lg text-center text-xs"> <p className="text-white/80 font-bold uppercase tracking-widest max-w-lg text-center text-xs">
Configure cada música com seu áudio local ou use nossa biblioteca cloud de alta fidelidade OS AUDIOS ESTÃO ATIVOS - CONFIGURE CADA MÚSICA COM SEU ÁUDIO LOCAL OU USE NOSSA BIBLIOTECA CLOUD
</p> </p>
<button onClick={startCamera} className="bg-[#E3B341] text-black px-16 py-6 rounded-full font-black text-2xl uppercase tracking-tighter hover:scale-110 transition-all shadow-2xl">Entrar no Palco</button> <button onClick={startCamera} className="bg-[#E3B341] text-black px-16 py-6 rounded-full font-black text-2xl uppercase tracking-tighter hover:scale-110 transition-all shadow-2xl">Entrar no Palco</button>
</div> </div>
@ -566,10 +615,15 @@ const ObservationPage = () => {
{isKaraokeActive && currentSong && isInterfaceVisible && ( {isKaraokeActive && currentSong && isInterfaceVisible && (
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 z-50 w-full max-w-4xl px-8 pointer-events-none animate-slide-up"> <div className="absolute bottom-10 left-1/2 -translate-x-1/2 z-50 w-full max-w-4xl px-8 pointer-events-none animate-slide-up">
<div className="bg-black/90 border-t-8 border-[#E3B341] p-10 rounded-3xl backdrop-blur-3xl shadow-[0_-20px_100px_rgba(0,0,0,0.8)] text-center pointer-events-auto"> <div className="bg-black/90 border-t-8 border-[#E3B341] p-10 rounded-3xl backdrop-blur-3xl shadow-[0_-20px_100px_rgba(0,0,0,0.8)] text-center pointer-events-auto">
<div className="flex items-center justify-center space-x-3 mb-6"> <div className="flex items-center justify-between mb-6">
<BaseIcon path={customAudioMap[currentSong.id] ? mdiCheckCircle : mdiMusicNote} size={24} className={customAudioMap[currentSong.id] ? "text-green-500" : "text-[#E3B341]"} /> <div className="flex items-center space-x-3">
<span className="text-[#E3B341] text-lg font-black uppercase tracking-widest">{currentSong.title} - {currentSong.artist}</span> <BaseIcon path={customAudioMap[currentSong.id] ? mdiCheckCircle : mdiMusicNote} size={24} className={customAudioMap[currentSong.id] ? "text-green-500" : "text-[#E3B341]"} />
{customAudioMap[currentSong.id] && <span className="text-[10px] bg-green-500 text-black px-2 py-0.5 rounded font-black">ÁUDIO LOCAL ATIVO</span>} <span className="text-[#E3B341] text-lg font-black uppercase tracking-widest">{currentSong.title} - {currentSong.artist}</span>
</div>
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 rounded-full animate-pulse ${audioStatus === 'ACTIVE' ? 'bg-green-500' : audioStatus === 'LOADING' ? 'bg-yellow-500' : 'bg-red-500'}`}></div>
<span className="text-[10px] text-white/60 font-black uppercase">{audioStatus}</span>
</div>
</div> </div>
<div className="text-white text-5xl font-black leading-tight drop-shadow-[0_4px_4px_rgba(0,0,0,1)] transition-all duration-300"> <div className="text-white text-5xl font-black leading-tight drop-shadow-[0_4px_4px_rgba(0,0,0,1)] transition-all duration-300">
{currentLyrics || "VAI COMEÇAR..."} {currentLyrics || "VAI COMEÇAR..."}
@ -581,9 +635,17 @@ const ObservationPage = () => {
<button onClick={togglePlayback} className="bg-[#E3B341] text-black w-20 h-20 flex items-center justify-center rounded-full hover:scale-110 transition-all shadow-[0_0_30px_rgba(227,179,65,0.4)]"> <button onClick={togglePlayback} className="bg-[#E3B341] text-black w-20 h-20 flex items-center justify-center rounded-full hover:scale-110 transition-all shadow-[0_0_30px_rgba(227,179,65,0.4)]">
<BaseIcon path={isPaused ? mdiPlay : mdiPause} size={48} /> <BaseIcon path={isPaused ? mdiPlay : mdiPause} size={48} />
</button> </button>
<button onClick={() => setIsInterfaceVisible(false)} className="bg-white/10 text-white p-4 rounded-full hover:bg-white/20 transition-all"> <div className="flex flex-col items-center space-y-2">
<BaseIcon path={mdiEyeOff} size={24} /> <button onClick={() => setIsInterfaceVisible(false)} className="bg-white/10 text-white p-4 rounded-full hover:bg-white/20 transition-all">
</button> <BaseIcon path={mdiEyeOff} size={24} />
</button>
<span className="text-[8px] text-white/40 font-black">OCULTAR</span>
</div>
{audioStatus === 'ERROR' && (
<button onClick={() => selectSong(currentSong)} className="bg-red-600 text-white p-4 rounded-full animate-bounce">
<BaseIcon path={mdiRefresh} size={24} />
</button>
)}
</div> </div>
</div> </div>
</div> </div>
@ -607,7 +669,7 @@ const ObservationPage = () => {
<button onClick={() => setIsMicOn(!isMicOn)} className={`p-4 rounded-full transition-all ${isMicOn ? 'bg-green-600 shadow-[0_0_20px_rgba(34,197,94,0.4)]' : 'bg-red-600'}`}> <button onClick={() => setIsMicOn(!isMicOn)} className={`p-4 rounded-full transition-all ${isMicOn ? 'bg-green-600 shadow-[0_0_20px_rgba(34,197,94,0.4)]' : 'bg-red-600'}`}>
<BaseIcon path={isMicOn ? mdiMicrophone : mdiMicrophoneOff} size={32} className="text-white" /> <BaseIcon path={isMicOn ? mdiMicrophone : mdiMicrophoneOff} size={32} className="text-white" />
</button> </button>
<span className="text-[10px] font-black text-white mt-2">MICROFONE</span> <span className="text-[10px] font-black text-white mt-2">MIC</span>
</div> </div>
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<button onClick={() => setIsMonitorOn(!isMonitorOn)} className={`p-4 rounded-full transition-all ${isMonitorOn ? 'bg-[#00F2FF] text-black shadow-[0_0_20px_rgba(0,242,255,0.4)]' : 'bg-white/10 text-white'}`}> <button onClick={() => setIsMonitorOn(!isMonitorOn)} className={`p-4 rounded-full transition-all ${isMonitorOn ? 'bg-[#00F2FF] text-black shadow-[0_0_20px_rgba(0,242,255,0.4)]' : 'bg-white/10 text-white'}`}>
@ -616,10 +678,20 @@ const ObservationPage = () => {
<span className="text-[10px] font-black text-white mt-2">RETORNO</span> <span className="text-[10px] font-black text-white mt-2">RETORNO</span>
</div> </div>
<div className="w-px h-12 bg-white/20"></div> <div className="w-px h-12 bg-white/20"></div>
<div className="flex flex-col space-y-2 min-w-[150px]"> <div className="flex flex-col space-y-2 min-w-[120px]">
<div className="flex justify-between text-[10px] font-black text-[#E3B341]"><span>EFEITO (REVERB)</span><span>{(reverbLevel * 100).toFixed(0)}%</span></div> <div className="flex justify-between text-[10px] font-black text-[#E3B341]"><span>EFEITO</span><span>{(reverbLevel * 100).toFixed(0)}%</span></div>
<input type="range" min="0" max="1" step="0.1" value={reverbLevel} onChange={(e) => setReverbLevel(parseFloat(e.target.value))} className="w-full h-1 bg-white/10 accent-[#E3B341] rounded-lg appearance-none cursor-pointer" /> <input type="range" min="0" max="1" step="0.1" value={reverbLevel} onChange={(e) => setReverbLevel(parseFloat(e.target.value))} className="w-full h-1 bg-white/10 accent-[#E3B341] rounded-lg appearance-none cursor-pointer" />
</div> </div>
<div className="flex flex-col space-y-2 min-w-[120px]">
<div className="flex justify-between text-[10px] font-black text-[#00F2FF]"><span>VOLUME</span><span>{(volumeLevel * 100).toFixed(0)}%</span></div>
<input type="range" min="0" max="1" step="0.1" value={volumeLevel} onChange={(e) => setVolumeLevel(parseFloat(e.target.value))} className="w-full h-1 bg-white/10 accent-[#00F2FF] rounded-lg appearance-none cursor-pointer" />
</div>
<div className="flex flex-col items-center">
<button onClick={() => { if(audioCtxRef.current) audioCtxRef.current.resume(); }} className="p-4 bg-white/5 rounded-full text-[#E3B341] hover:bg-white/10 transition-all">
<BaseIcon path={mdiTune} size={32} />
</button>
<span className="text-[10px] font-black text-white mt-2 text-center uppercase">Ativar<br/>Audio</span>
</div>
</div> </div>
<div className="bg-red-600 text-white px-6 py-3 rounded-full font-black text-sm shadow-2xl flex items-center space-x-3 w-fit"> <div className="bg-red-600 text-white px-6 py-3 rounded-full font-black text-sm shadow-2xl flex items-center space-x-3 w-fit">
@ -653,7 +725,10 @@ const ObservationPage = () => {
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div className="flex flex-col"> <div className="flex flex-col">
<h3 className="text-[#E3B341] font-black text-2xl italic uppercase tracking-tighter">Estúdio 10.000+</h3> <h3 className="text-[#E3B341] font-black text-2xl italic uppercase tracking-tighter">Estúdio 10.000+</h3>
<p className="text-[10px] text-white/40 font-bold uppercase tracking-widest">Configuração Avançada de Playbacks</p> <div className="flex items-center space-x-2">
<div className={`w-2 h-2 rounded-full ${audioCtxRef.current?.state === 'running' ? 'bg-green-500' : 'bg-red-500'}`}></div>
<p className="text-[10px] text-white/40 font-bold uppercase tracking-widest">SISTEMA DE ÁUDIO: {audioCtxRef.current?.state}</p>
</div>
</div> </div>
<button onClick={() => setShowPlaylist(false)} className="bg-white/5 p-2 rounded-full text-white/40 hover:text-white transition-all"><BaseIcon path={mdiClose} size={28} /></button> <button onClick={() => setShowPlaylist(false)} className="bg-white/5 p-2 rounded-full text-white/40 hover:text-white transition-all"><BaseIcon path={mdiClose} size={28} /></button>
</div> </div>
@ -704,7 +779,7 @@ const ObservationPage = () => {
<BaseIcon path={mdiCog} size={20} /> <BaseIcon path={mdiCog} size={20} />
</button> </button>
<div className={`p-3 rounded-full transition-all ${currentSong?.id === song.id ? 'bg-black text-[#E3B341]' : 'bg-white/10 text-white group-hover:bg-[#E3B341] group-hover:text-black'}`}> <div className={`p-3 rounded-full transition-all ${currentSong?.id === song.id ? 'bg-black text-[#E3B341]' : 'bg-white/10 text-white group-hover:bg-[#E3B341] group-hover:text-black'}`}>
<BaseIcon path={currentSong?.id === song.id ? mdiPause : mdiPlay} size={24} /> <BaseIcon path={currentSong?.id === song.id ? (isPaused ? mdiPlay : mdiPause) : mdiPlay} size={24} />
</div> </div>
</div> </div>
</div> </div>