fix: 음악자동재생 + 버그수정
parent
2988a59749
commit
0e5a63a28b
|
|
@ -53,7 +53,7 @@ function IconMusic() {
|
|||
</svg>
|
||||
);
|
||||
}
|
||||
function IconVolume({ level }: { level: number; muted: boolean }) {
|
||||
function IconVolume({ level }: { level: number }) {
|
||||
if (level === 0) {
|
||||
return (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
|
@ -145,10 +145,10 @@ export default function MusicBar() {
|
|||
loadPlaylist,
|
||||
} = usePlayer();
|
||||
|
||||
// 경로가 바뀌면 해당 경로의 플레이리스트 로드
|
||||
// 경로가 바뀌면 해당 경로의 플레이리스트 로드, 없으면 초기화
|
||||
useEffect(() => {
|
||||
const tracks = PATH_PLAYLISTS[pathname];
|
||||
if (tracks) loadPlaylist(tracks);
|
||||
loadPlaylist(tracks ?? []);
|
||||
}, [pathname, loadPlaylist]);
|
||||
|
||||
if (!hasTrack) return null;
|
||||
|
|
@ -171,9 +171,10 @@ export default function MusicBar() {
|
|||
};
|
||||
|
||||
const handleVolumeDrag = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
e.currentTarget.setPointerCapture(e.pointerId);
|
||||
const el = e.currentTarget;
|
||||
el.setPointerCapture(e.pointerId);
|
||||
const update = (ev: PointerEvent) => {
|
||||
const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
|
||||
const rect = el.getBoundingClientRect();
|
||||
setVolume(Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width)));
|
||||
};
|
||||
const stop = () => {
|
||||
|
|
@ -313,10 +314,10 @@ export default function MusicBar() {
|
|||
{/* 볼륨 — 모바일 포함 전체 표시 */}
|
||||
<button
|
||||
onClick={toggleMute}
|
||||
aria-label={muted ? t.playerPlay : t.playerPause}
|
||||
aria-label={muted ? t.playerUnmute : t.playerMute}
|
||||
className="p-1 text-white/45 hover:text-white/80 transition-colors shrink-0"
|
||||
>
|
||||
<IconVolume level={displayVolume} muted={muted} />
|
||||
<IconVolume level={displayVolume} />
|
||||
</button>
|
||||
<div
|
||||
className="w-16 md:w-20 h-[3px] bg-white/20 rounded-full cursor-pointer shrink-0 relative"
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ type Dict = {
|
|||
playerNext: string;
|
||||
playerPrev: string;
|
||||
playerClose: string;
|
||||
playerMute: string;
|
||||
playerUnmute: string;
|
||||
};
|
||||
|
||||
export const DICT: Record<Lang, Dict> = {
|
||||
|
|
@ -212,6 +214,8 @@ export const DICT: Record<Lang, Dict> = {
|
|||
playerNext: "다음 곡",
|
||||
playerPrev: "이전 곡",
|
||||
playerClose: "플레이어 닫기",
|
||||
playerMute: "음소거",
|
||||
playerUnmute: "음소거 해제",
|
||||
},
|
||||
en: {
|
||||
heroPill: "AI-powered Global Football Festival 2026 match predictions",
|
||||
|
|
@ -300,6 +304,8 @@ export const DICT: Record<Lang, Dict> = {
|
|||
playerNext: "Next track",
|
||||
playerPrev: "Previous track",
|
||||
playerClose: "Close player",
|
||||
playerMute: "Mute",
|
||||
playerUnmute: "Unmute",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -55,8 +55,12 @@ const PlayerContext = createContext<PlayerContextValue | null>(null);
|
|||
|
||||
export function PlayerProvider({ children }: { children: ReactNode }) {
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const currentSrcRef = useRef<string>("");
|
||||
const autoPlayRef = useRef(false);
|
||||
const isPlayingRef = useRef(false);
|
||||
const [playlist, setPlaylistState] = useState<Track[]>([]);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [loadKey, setLoadKey] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
|
|
@ -80,18 +84,12 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
setProgress(audio.duration ? audio.currentTime / audio.duration : 0);
|
||||
};
|
||||
const onLoaded = () => setDuration(audio.duration || 0);
|
||||
const onEnded = () =>
|
||||
setCurrentIndex((prev) =>
|
||||
// playlist.length는 클로저 문제 방지를 위해 ref 대신 함수형 업데이트 외부에서 읽음
|
||||
prev + 1 // ended 핸들러에서는 playlist ref를 통해 처리
|
||||
);
|
||||
const onPlay = () => setIsPlaying(true);
|
||||
const onPause = () => setIsPlaying(false);
|
||||
const onPlay = () => { setIsPlaying(true); isPlayingRef.current = true; };
|
||||
const onPause = () => { setIsPlaying(false); isPlayingRef.current = false; };
|
||||
|
||||
audio.addEventListener("timeupdate", onTimeUpdate);
|
||||
audio.addEventListener("loadedmetadata", onLoaded);
|
||||
audio.addEventListener("durationchange", onLoaded);
|
||||
audio.addEventListener("ended", onEnded);
|
||||
audio.addEventListener("play", onPlay);
|
||||
audio.addEventListener("pause", onPause);
|
||||
|
||||
|
|
@ -100,7 +98,6 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
audio.removeEventListener("timeupdate", onTimeUpdate);
|
||||
audio.removeEventListener("loadedmetadata", onLoaded);
|
||||
audio.removeEventListener("durationchange", onLoaded);
|
||||
audio.removeEventListener("ended", onEnded);
|
||||
audio.removeEventListener("play", onPlay);
|
||||
audio.removeEventListener("pause", onPause);
|
||||
audioRef.current = null;
|
||||
|
|
@ -115,6 +112,7 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
const onEnded = () => {
|
||||
autoPlayRef.current = true;
|
||||
setCurrentIndex((prev) =>
|
||||
playlistLenRef.current > 0 ? (prev + 1) % playlistLenRef.current : 0
|
||||
);
|
||||
|
|
@ -127,15 +125,17 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
useEffect(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio || !currentTrack) return;
|
||||
const wasPlaying = isPlaying;
|
||||
const shouldPlay = isPlayingRef.current || autoPlayRef.current;
|
||||
autoPlayRef.current = false;
|
||||
currentSrcRef.current = currentTrack.src;
|
||||
audio.src = currentTrack.src;
|
||||
audio.load();
|
||||
setProgress(0);
|
||||
setCurrentTime(0);
|
||||
setDuration(0);
|
||||
if (wasPlaying) audio.play().catch(() => {});
|
||||
if (shouldPlay) audio.play().catch(() => {});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentIndex, currentTrack?.src]);
|
||||
}, [currentIndex, currentTrack?.src, loadKey]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Actions
|
||||
|
|
@ -144,7 +144,11 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
const play = useCallback(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
if (!audio.src && currentTrack) { audio.src = currentTrack.src; audio.load(); }
|
||||
if (currentTrack && currentSrcRef.current !== currentTrack.src) {
|
||||
currentSrcRef.current = currentTrack.src;
|
||||
audio.src = currentTrack.src;
|
||||
audio.load();
|
||||
}
|
||||
audio.play().catch(() => {});
|
||||
}, [currentTrack]);
|
||||
|
||||
|
|
@ -156,7 +160,11 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
} else {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
if (!audio.src && currentTrack) { audio.src = currentTrack.src; audio.load(); }
|
||||
if (currentTrack && currentSrcRef.current !== currentTrack.src) {
|
||||
currentSrcRef.current = currentTrack.src;
|
||||
audio.src = currentTrack.src;
|
||||
audio.load();
|
||||
}
|
||||
audio.play().catch(() => {});
|
||||
}
|
||||
}, [isPlaying, currentTrack]);
|
||||
|
|
@ -205,13 +213,20 @@ export function PlayerProvider({ children }: { children: ReactNode }) {
|
|||
// 경로 변경 시 새 플레이리스트 로드
|
||||
const loadPlaylist = useCallback((tracks: Track[]) => {
|
||||
const audio = audioRef.current;
|
||||
if (audio) { audio.pause(); audio.src = ""; }
|
||||
setPlaylistState(tracks);
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
audio.removeAttribute("src");
|
||||
}
|
||||
currentSrcRef.current = "";
|
||||
autoPlayRef.current = true;
|
||||
setPlaylistState([...tracks]);
|
||||
setLoadKey((k) => k + 1);
|
||||
setCurrentIndex(0);
|
||||
setProgress(0);
|
||||
setCurrentTime(0);
|
||||
setDuration(0);
|
||||
setIsPlaying(false);
|
||||
isPlayingRef.current = false;
|
||||
}, []);
|
||||
|
||||
const value: PlayerContextValue = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue