7
This commit is contained in:
parent
0f68650e37
commit
c8e1ff52ac
@ -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" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user