Pokétify_완성_팀프로젝트5_음악추천페이지_3_Play바생성_곡재생,일시정지_Zustand

정소현·2024년 10월 16일
0

팀프로젝트

목록 보기
22/50
post-thumbnail

🔥 프로젝트명 : Pokétify

소개

음악과 관련된 서비스를 제공.
사용자는 앨범명, 가수명 등의 키워드로 관련된 곡을 검색할 수 있으며, 자신의 취향이 담긴 플레이리스트에 곡을 추가하고 삭제하며 재생할 수 있습니다.
또한, 커뮤니티 사이트에서 해당 링크에 대한 댓글을 남기며 소통할 수 있습니다.

랜딩페이지

메인페이지

플레이리스트 페이지

커뮤니티 페이지

검색결과 페이지

마이 페이지


🌟 Play바 만들기

플레이리스트 페이지 나머지 기능들은 거의 다 완성이 되고 플레이바를 만들어 트랙리스트에서 곡을 클릭했을 때 노래가 나오도록 설정하는 하기로 기획했다.

☘️ 진행과정

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 }),
}));
  • SongList.tsx에서 클릭되는 trackUri를 넣어주기 위해 useTrackStore()를 호출하였고
    곡을 재생하는 함수를 작성해주었다.
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);
  }
  • Playbar.tsx에서 플레이바 레이아웃을 그려준 이후 useTrackStore()를 호출해 버튼을 클릭하여 재생중이면 재생버튼과 일시정지 버튼이 바뀔 수 있도록 로직을 작성했다.
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;
  }
};
  • 그리고 시작할 때 일시정지 했던 position에서 다시 시작할 수 있도록 props로 position을 추가하고 data에 position_ms를 작성해주었다.
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를 사용해보고 싶었는데 이 부분을 구현해내지 못한 것이 아쉽다. 이 점을 꼭 보완하고 다시 시도해볼 것이다!!😂

0개의 댓글