This commit is contained in:
Flatlogic Bot 2026-02-26 20:21:02 +00:00
parent 0f68650e37
commit c8e1ff52ac

View File

@ -25,6 +25,8 @@ import {
mdiDancePole,
mdiEye,
mdiEyeOff,
mdiUpload,
mdiCheckCircle,
} from '@mdi/js';
import BaseIcon from '../components/BaseIcon';
import { useAppDispatch } from '../stores/hooks';
@ -114,6 +116,7 @@ const ObservationPage = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const crowdAudioRef = useRef<HTMLAudioElement | null>(null);
const karaokeAudioRef = useRef<HTMLAudioElement | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const audioCtxRef = useRef<AudioContext | null>(null);
const audioDestRef = useRef<MediaStreamAudioDestinationNode | null>(null);
@ -148,6 +151,9 @@ const ObservationPage = () => {
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);
const [videoUrl, setVideoUrl] = useState<string | null>(null);
const [customAudioMap, setCustomAudioMap] = useState<Record<string, string>>({});
const [songToUpload, setSongToUpload] = useState<any>(null);
const imageCache = useRef<Map<string, HTMLImageElement>>(new Map());
// Persistent karaoke audio setup
@ -296,7 +302,8 @@ const ObservationPage = () => {
setShowPlaylist(false);
audio.pause();
audio.src = song.url;
// Use custom audio if available, otherwise use default URL
audio.src = customAudioMap[song.id] || song.url;
audio.load();
try {
@ -320,6 +327,38 @@ const ObservationPage = () => {
}
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file && songToUpload) {
const url = URL.createObjectURL(file);
setCustomAudioMap(prev => {
// Revoke previous custom audio for this song if it exists
if (prev[songToUpload.id]) {
URL.revokeObjectURL(prev[songToUpload.id]);
}
return { ...prev, [songToUpload.id]: url };
});
setSongToUpload(null);
// If we're currently playing this song, update the source immediately
if (currentSong?.id === songToUpload.id && karaokeAudioRef.current) {
const wasPaused = karaokeAudioRef.current.paused;
karaokeAudioRef.current.src = url;
karaokeAudioRef.current.load();
if (!wasPaused) {
karaokeAudioRef.current.play().catch(err => console.error("Playback update error:", err));
}
}
}
// Reset file input value to allow selecting the same file again if needed
if (fileInputRef.current) fileInputRef.current.value = "";
};
const triggerUpload = (e: React.MouseEvent, song: any) => {
e.stopPropagation();
setSongToUpload(song);
fileInputRef.current?.click();
};
useEffect(() => {
let animationFrame: number;
const canvas = canvasRef.current;
@ -417,6 +456,9 @@ const ObservationPage = () => {
<div className="relative h-screen w-full bg-[#1a1a1a] overflow-hidden flex flex-col font-sans">
<Head><title>KARAOKE GLOBAL 10K+ | AO VIVO</title></Head>
{/* Hidden File Input for Custom Audio */}
<input type="file" ref={fileInputRef} onChange={handleFileChange} accept="audio/*" className="hidden" />
<div className="absolute inset-0 z-0 bg-black overflow-hidden">
{!isCameraActive && (
<div className="flex flex-col items-center justify-center h-full space-y-8 z-10 relative">
@ -466,8 +508,9 @@ const ObservationPage = () => {
<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="flex items-center justify-center space-x-3 mb-6">
<BaseIcon path={mdiMusicNote} size={24} className="text-[#E3B341]" />
<BaseIcon path={customAudioMap[currentSong.id] ? mdiCheckCircle : mdiMusicNote} size={24} className={customAudioMap[currentSong.id] ? "text-green-500" : "text-[#E3B341]"} />
<span className="text-[#E3B341] text-lg font-black uppercase tracking-widest">{currentSong.title} - {currentSong.artist}</span>
{customAudioMap[currentSong.id] && <span className="text-[10px] bg-green-500 text-black px-2 py-0.5 rounded font-black">LOCAL</span>}
</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">
{currentLyrics || "VAI COMEÇAR..."}
@ -551,7 +594,7 @@ const ObservationPage = () => {
<div className="flex items-center justify-between mb-6">
<div className="flex flex-col">
<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">Seu palco, sua música, seu mundo</p>
<p className="text-[10px] text-white/40 font-bold uppercase tracking-widest">Configure seus playbacks e brilhe</p>
</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>
</div>
@ -574,19 +617,27 @@ const ObservationPage = () => {
<div className="flex-grow overflow-y-auto pr-2 space-y-3 custom-scrollbar">
{filteredSongs.length > 0 ? filteredSongs.map(song => (
<button key={song.id} onClick={() => selectSong(song)} className={`w-full flex items-center justify-between p-4 rounded-2xl border-2 transition-all group ${currentSong?.id === song.id ? 'bg-[#E3B341] border-[#E3B341] text-black' : 'bg-white/5 border-white/10 text-white hover:border-[#E3B341] hover:bg-white/10'}`}>
<div className="flex flex-col items-start overflow-hidden">
<span className="font-black text-lg truncate w-full italic leading-tight">{song.title}</span>
<div key={song.id} className={`w-full flex items-center justify-between p-4 rounded-2xl border-2 transition-all group cursor-pointer ${currentSong?.id === song.id ? 'bg-[#E3B341] border-[#E3B341] text-black' : 'bg-white/5 border-white/10 text-white hover:border-[#E3B341] hover:bg-white/10'}`} onClick={() => selectSong(song)}>
<div className="flex flex-col items-start overflow-hidden flex-grow">
<span className="font-black text-lg truncate w-full italic leading-tight flex items-center space-x-2">
{customAudioMap[song.id] && <BaseIcon path={mdiCheckCircle} size={16} className="text-green-500" />}
<span>{song.title}</span>
</span>
<div className="flex items-center space-x-2 mt-1">
<span className="text-[10px] font-black uppercase opacity-60 tracking-wider">{song.artist}</span>
<span className="w-1 h-1 bg-white/30 rounded-full"></span>
<span className="text-[9px] font-black uppercase text-[#E3B341] group-hover:text-inherit">{song.genre}</span>
</div>
</div>
<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} />
<div className="flex items-center space-x-2">
<button onClick={(e) => triggerUpload(e, song)} title="Trocar áudio por arquivo local" className={`p-2 rounded-full transition-all ${currentSong?.id === song.id ? 'bg-black/20 text-black hover:bg-black/40' : 'bg-white/5 text-[#E3B341] hover:bg-white/20'}`}>
<BaseIcon path={mdiUpload} size={20} />
</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'}`}>
<BaseIcon path={currentSong?.id === song.id ? mdiPause : mdiPlay} size={24} />
</div>
</div>
</button>
</div>
)) : (
<div className="flex flex-col items-center justify-center py-20 text-white/20">
<BaseIcon path={mdiFilterVariant} size={64} className="mb-4" />
@ -595,17 +646,20 @@ const ObservationPage = () => {
</div>
)}
<div className="p-8 border-2 border-dashed border-white/10 rounded-[2rem] text-center opacity-40 hover:opacity-100 transition-all cursor-pointer group">
<div className="p-8 border-2 border-dashed border-white/10 rounded-[2rem] text-center opacity-40 hover:opacity-100 transition-all cursor-pointer group" onClick={() => {
setSongToUpload({ id: 'new-custom', title: 'Upload Personalizado', artist: 'Arquivo Local', genre: 'Personalizado' });
fileInputRef.current?.click();
}}>
<BaseIcon path={mdiCreation} size={48} className="text-[#E3B341] mx-auto mb-4 group-hover:scale-125 transition-all" />
<h4 className="text-white font-black uppercase tracking-tighter">Explorar Nuvem de 10.000 Hits</h4>
<p className="text-[10px] text-white/60 font-bold mt-1 uppercase">Novos playbacks adicionados diariamente</p>
<h4 className="text-white font-black uppercase tracking-tighter">Carregar Áudio Personalizado</h4>
<p className="text-[10px] text-white/60 font-bold mt-1 uppercase">Substitua playbacks por seus próprios arquivos MP3/WAV</p>
</div>
</div>
<div className="mt-6 pt-6 border-t border-white/10 flex items-center justify-between">
<div className="flex flex-col">
<span className="text-[9px] font-black text-white/30 uppercase tracking-widest">Qualidade de Áudio</span>
<span className="text-[#E3B341] text-xs font-black">ULTRA-HD 320KBPS</span>
<span className="text-[9px] font-black text-white/30 uppercase tracking-widest">Configuração de Playback</span>
<span className="text-[#E3B341] text-xs font-black">LOCAL & CLOUD SYNC ATIVO</span>
</div>
<div className="flex items-center space-x-4">
<BaseIcon path={mdiVolumeHigh} size={20} className="text-white/40" />