음악과 관련된 서비스를 제공.
사용자는 앨범명, 가수명 등의 키워드로 관련된 곡을 검색할 수 있으며, 자신의 취향이 담긴 플레이리스트에 곡을 추가하고 삭제하며 재생할 수 있습니다.
또한, 커뮤니티 사이트에서 해당 링크에 대한 댓글을 남기며 소통할 수 있습니다.
플레이리스트 페이지 나머지 기능들은 거의 다 완성이 되고 플레이바를 만들어 트랙리스트에서 곡을 클릭했을 때 노래가 나오도록 설정하는 하기로 기획했다.
Songlist.tsx와 PlayBar.tsx 컴포넌트가 서로 다른 위치에 존재하기 때문에 Props로 상태를 주고 받을 수 없어 Zustand를 통해 관리하기로 결정했다.
먼저 src > utils > playlistApi.ts 에서 곡을 재생시키고 일시정지시키는 로직을 작성해주었다.
재생 시작
export const playTrack = async (trackUri: string | null, position: number) => {
const accessToken = await getPrivateAccessToken();
try {
const response = await axios.put(
`${BASEURL}/me/player/play`,
{
uris: [trackUri],
position_ms: position
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
return response.data;
} catch (error) {
console.error("Error playing track:", error);
throw error;
}
일시정지
export const pauseTrack = async () => {
const accessToken = await getPrivateAccessToken();
try {
await axios.put(
`${BASEURL}/me/player/pause`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
} catch (error) {
console.error("Error pausing track:", error);
throw error;
}
};
다음 zustand useTrackStore를 사용하여 재생되어야 할 트랙의 uri를 관리하고 노래가 진행중인지 전역으로 상태를 관리하기 위해 set을 해주었다.
src > store > useTrackStore.ts
import { create } from "zustand";
interface TrackStore {
trackUri: string | null;
setTrackUri: (uri: string) => void;
isPlaying: boolean;
setIsPlaying: (playing: boolean) => void;
}
export const useTrackStore = create<TrackStore>((set) => ({
trackUri: null,
isPlaying: false,
setTrackUri: (uri) => set({ trackUri: uri }),
setIsPlaying: (isPlaying) => set({ isPlaying }),
}));
const SongList = ({ playlistId }: SongListProps) => {
const queryClient = useQueryClient();
const [hoveredTrackId, setHoveredTrackId] = useState<string | null>(null);
const setTrackUri = useTrackStore((state) => state.setTrackUri);
// 곡 재생
const handlePlayTrack = async (trackUri: string) => {
setTrackUri(trackUri);
try {
await playTrack(trackUri);
} catch (error) {
console.error("트랙 재생 실패:", error);
}
import { useTrackStore } from "@/store/useTrackStore";
import { pauseTrack, playTrack } from "@/utils/playlistApi";
import React, { useState } from "react";
import { FaPlay, FaPause } from "react-icons/fa";
const Playbar: React.FC = () => {
const trackUri = useTrackStore((state) => state.trackUri);
const isPlaying = useTrackStore((state) => state.isPlaying);
const setIsPlaying = useTrackStore((state) => state.setIsPlaying);
// 트랙 재생 핸들러
const handlePlay = async (): Promise<void> => {
if (trackUri) {
try {
await playTrack(trackUri, currentPosition);
setIsPlaying(true);
} catch (error) {
console.error("트랙 재생 실패:", error);
}
}
};
// 트랙 일시정지 핸들러
const handlePause = async (): Promise<void> => {
try {
const position = await pauseTrack();
setIsPlaying(false);
} catch (error) {
console.error("트랙 일시정지 실패:", error);
}
};
return (
<div className="bg-pink-200 p-4 mx-auto flex gap-4 justify-center">
{isPlaying ? (
<button onClick={handlePause}>
<FaPause />
</button>
) : (
<button onClick={handlePlay}>
<FaPlay />
</button>
)}
</div>
);
};
export default Playbar;
🥵 문제점 : 이렇게 위의 기능들을 구현하고 곡을 재생하였을 때 내가 원하는 것은 곡이 일시정지 후 다시 재생버튼을 눌렀을 때 처음부터 재생되는 것이 아닌 내가 멈춘 시점부터 다시 재생되는 것이었다.
✨ 해결방법 : Spotify API를 다시 읽어보니 position을 가져오는 로직이 있어 내가 멈춘 부분부터 다시 재생할 수 있도록 곡을 일시정지할 때 위치를 가져오도록 함수를 재작성해주었다.
src > utils > playlistApi.ts
// 일시 정지 API 호출
export const pauseTrack = async () => {
const accessToken = await getPrivateAccessToken();
try {
await axios.put(
`${BASEURL}/me/player/pause`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
// 멈춘 위치를 가져오는 함수
const response = await axios.get(`${BASEURL}/me/player/currently-playing`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
if (response.data && response.data.item) {
return response.data.progress_ms;
}
return 0;
} catch (error) {
console.error("Error pausing track:", error);
throw error;
}
};
export const playTrack = async (trackUri: string | null, position: number) => {
const accessToken = await getPrivateAccessToken();
try {
const response = await axios.put(
`${BASEURL}/me/player/play`,
{
uris: [trackUri],
position_ms: position
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
return response.data;
} catch (error) {
console.error("Error playing track:", error);
throw error;
}
};
Playbar.tsx에서 현재위치를 저장할 수 있도록 useState()를 만들어 클릭했을 떄 현재 위치가 저장될 수 있도록 로직을 작성해주었다.
이번 프로젝트를 하면서 Next.js, supabase를 이용해 개발하며 react의 훅들과 zustand, tanstackQuery를 사용하며 서버와 전역 상태관리에 대해 좀 더 이해할 수 있었다. Next의 Asset최적화 이미지를 이용하며 더 나은 성능과 속도를 위해 노력했다.
또한 throttle을 개념과 Debounce와의 차이점을 알게되었고 pagenation을 구현하며 힘들었지만 많은 것을 얻을 수 있었던 시간이었음을 느낀다.
아쉬웠던 것은 middleware를 이용하여 NextResponse를 사용해보고 싶었는데 이 부분을 구현해내지 못한 것이 아쉽다. 이 점을 꼭 보완하고 다시 시도해볼 것이다!!😂