import{Button}from'@/common/components/ui/button';import{Volume2,VolumeX,Play}from'lucide-react';import{useRef,useState,useEffect}from'react';interfaceBackgroundMusicProps{hasVideo: boolean;}exportdefaultfunctionBackgroundMusic({ hasVideo }: BackgroundMusicProps){const[isPlaying,setIsPlaying]=useState<boolean>(false);const[isMuted,setIsMuted]=useState<boolean>(false);const[hasUserInteraction,setHasUserInteraction]=useState<boolean>(false);constaudioRef=useRef<HTMLAudioElement>(null);useEffect(()=>{constsavedInteraction=localStorage.getItem('hasUserInteraction');if(savedInteraction==='true'){setHasUserInteraction(true);}},[hasUserInteraction,hasVideo]);useEffect(()=>{if(!audioRef.current)return;if(hasVideo){audioRef.current.pause();setIsPlaying(false);}else{audioRef.current.muted=true;setIsMuted(true);constplayPromise=audioRef.current.play();if(playPromise!==undefined){playPromise.then(()=>{setIsPlaying(true);console.log('음소거 상태로 자동 재생 성공');if(hasUserInteraction){audioRef.current!.muted=false;setIsMuted(false);}}).catch((error)=>{console.error('자동 재생 실패:',error);setIsPlaying(false);});}}},[hasVideo,hasUserInteraction]);consthandleInitialPlay=()=>{setHasUserInteraction(true);localStorage.setItem('hasUserInteraction','true');if(audioRef.current){audioRef.current.muted=false;setIsMuted(false);constplayPromise=audioRef.current.play();if(playPromise!==undefined){playPromise.then(()=>{setIsPlaying(true);}).catch((error)=>{console.error('재생 실패:',error);setIsPlaying(false);});}}};consttoggleAudio=()=>{if(!audioRef.current)return;if(isPlaying){audioRef.current.pause();setIsPlaying(false);}else{audioRef.current.muted=false;setIsMuted(false);constplayPromise=audioRef.current.play();if(playPromise!==undefined){playPromise.then(()=>{setIsPlaying(true);}).catch((error)=>{console.error('재생 실패:',error);setIsPlaying(false);});}}};return(<><divclassName='fixed top-0 left-0 right-0 z-40'><divclassName='max-w-screen-sm mx-auto relative'><divclassName='absolute top-4 right-4'>{!hasUserInteraction ? (<Buttonvariant='secondary'size='sm'className='rounded-full bg-white/80 hover:bg-white/90 shadow-sm animate-pulse z-50'onClick={handleInitialPlay}><PlayclassName='w-4 h-4'/></Button>) : (<Buttonvariant='secondary'size='sm'className='rounded-full bg-white/80 hover:bg-white/90 shadow-sm'onClick={toggleAudio}>{isPlaying&&!isMuted ? (<Volume2className='w-4 h-4'/>) : (<VolumeXclassName='w-4 h-4'/>)}</Button>)}</div></div></div><audioref={audioRef}looppreload='auto'src='/music/here-with-me.mp3'/></>);}
상태 관리
hasVideo => 페이지에 비디오가 존재하는지 여부를 나타내는 boolean 값입니다. 비디오가 있을 경우, 배경 음악을 자동으로 중지하는 데 사용됩니다.(useVideoCheck hook 에서 가져온 값)
isPlaying => 오디오가 현재 재생 중인지 여부를 나타냅니다.
isMuted => 오디오가 음소거 상태인지 여부를 나타냅니다.
hasUserInteraction => 사용자가 음악 재생에 상호작용했는지 여부를 추적합니다. 브라우저의 자동 재생 제한을 우회하는 데 중요합니다.
audioRef => 오디오 요소에 대한 참조를 저장하는 ref입니다.
사용자 상호작용 상태 확인하는 법
localstorage로 이전에 사용자가 음악 재생에 동의했는지 localStorage에서 확인합니다.
이전에 사용자가 음악 재생에 동의했는지 localStorage에서 확인합니다.
페이지 새로고침이나 재방문 시에도 사용자의 상태를 기억합니다.
비디오 존재 여부에 따른 오디오 처리
비디오(mp4 확장자)가 존재하면 비디오를 보여주게 되고, 비디오가 없으면 배너 사진을 보여줍니다.
handleInitialPlay (초기 재생 버튼 핸들러 함수)
처음 웹 사이트에 접속하면, localstorage에는 아무것도 없기에 재생 버튼이 있습니다.
재생 버튼을 누르면 localstorage에 hasUserInteraction 값이 true로 저장됩니다.
toggleAudio (재생/일시정지 토글 함수)
현재 재생 상태에 따라 오디오를 일시 정지하거나 재생합니다.
[useVideoCheck] 비디오 URL이 유효한지 확인하고 비디오 존재 여부를 판단하는 코드
import{useState,useEffect}from'react';exportfunctionuseVideoCheck(videoUrl: string){const[hasVideo,setHasVideo]=useState<boolean>(false);const[isChecking,setIsChecking]=useState<boolean>(true);useEffect(()=>{constcheckVideo=async()=>{try{// HEAD 요청 대신 실제 비디오 요소로 체크constvideo=document.createElement('video');video.src=videoUrl;// 비디오 로드 이벤트 처리video.onloadeddata=()=>{setHasVideo(true);setIsChecking(false);};// 에러 처리video.onerror=()=>{setHasVideo(false);setIsChecking(false);};}catch(error){console.error('Video check error:',error);setHasVideo(false);setIsChecking(false);}};checkVideo();},[videoUrl]);return{ hasVideo, isChecking };}
상태 관리
hasVideo => 비디오 존재 여부를 나타내는 boolean 값
isChecking => 확인 작업 진행 중인지 여부를 나타내는 boolean 값 (즉 로딩이랑 똑같다고 보면됩니다.)
동작
onloadeddata 이벤트 핸들러를 통해 비디오가 성공적으로 로드되었는지 확인합니다.
onerror 로 예외처리 해줍니다.
비디오가 존재하면 hasVideo는 true, isChecking 확인 작업중이면(로딩) true