지난 글에서 플레이어 생성을 완료했다.
이제 플레이어를 조작해보자! 🎧
플레이어의 play
, pause
기능을 구현한다. 구체화/세분화하면 아래와 같다.
play
/pause
중 하나로 바뀐다.play
버튼을 누르면 플레이리스트의 첫 곡이 재생된다.play
버튼을 누르면 pause했던 지점부터 노래를 재생한다.pause
버튼을 누르면 노래를 멈춘다.
제가 가장 많은 삽질을 한 곳이 여기입니다.
https://developer.spotify.com/documentation/web-playback-sdk/reference/
위 링크에 제공되어 있는 Web Player SDK API를 그 어떤 방식으로 활용해봐도 노래가 재생되지 않았다. 분명 play, pause, resume과 같이 그럴싸한 이름의 API들이 친절하게 나열되어 있었는데 🤨 문서에 나와있는 코드를 따라 쳐보고, 바꿔서 쳐봐도 내 플레이어에는 아무 일이 발생하지 않았다.
그렇게 몇 번의 삽질을 하다가 원점으로 돌아가보고자 공식문서를 처음부터 찬찬히 읽던 중, 아래와 같은 문구를 발견할 수 있었다.
이런 건 좀 더 크게 써놓았으면 좋았을 걸 ^_^
요약하자면 "Web Playback SDK는 브라우저 내 로컬 디바이스를 생성하는 역할만 하니까, 디바이스를 컨트롤하기 위해서는 Web API를 사용해라." 라는 내용이었다.
사실 Spotify 공식 문서를 처음 보면, 다 영어로 되어 있는데다 서로 비슷하게 생겨서 뭐가 뭔지 구분 조차 어렵다. 나도 Web Playback SDK랑 Web API 문서를 별 구분 없이 마구잡이로 보고 있었는데, 저 문구를 확인하고 나서야 둘의 용도가 다름을 알 수 있었다.
아무튼 우리가 원하는 기능을 구현하기 위해서는 Web API를 사용해야 한다.
Web API는 Web Player SDK에 비해 더 방대하기 때문에, 원하는 걸 찾는데 시간이 더 걸렸다.
우리가 원하는 정보는 Web API - References - Player 여기에 있다. 위 링크를 클릭하면 바로 접속할 수 있다.
우리가 생성한 플레이어에 직접 메소드를 호출하는 게 아니라, 플레이어 조작 기능별로 Web API Endpoint가 존재하고, 해당 url로 요청을 보내는 방식이다.
우선 플레이어 UI를 만들어준다.
나는 Player
라는 컴포넌트로 따로 분리해줬다.
play
이자 pause
기능을 하는 버튼 하나이렇게 세 가지 요소가 들어가게 간단히 만들어주면 된다.
그리고 버튼에 우리가 만들 메서드를 onClick으로 연결해주면 된다.
내가 원하는 노래를 재생하기 위해선 스포티파이에게 내가 원하는 노래가 무엇인지를 알려줘야 한다.
이때 uri를 사용한다. URI는 특정 리소스를 식별하는 통합 자원 식별자(Uniform Resource Identifier)를 의미한다. 스포티파이는 uri로 각 노래를 구분하기 때문에, 재생하고 싶은 노래의 uri를 알아야 한다.
uri를 얻는 방법은 쉽다. 스포티파이 웹 플레이어에 접속한 다음, 원하는 노래에서 ...
버튼을 누르고 공유하기를 살펴보면 URI 복사 버튼이 있다. 맥의 경우 control/option 키 중 하나를 눌러야 복사 버튼이 나타난다.
우리 프로젝트 같은 경우는 애초에 Spotify를 사용해 노래를 크롤링 했기 때문에 데이터셋 자체에 uri가 포함되어 있었다.
Start/Resume Playback API 링크 👉 이 링크의 API를 사용하면 된다.
Query로 device_id
를, 요청 바디에는 uris
를 넣으면 된다.
// 공식문서 예시 코드
const play = ({
spotify_uri,
playerInstance: {
_options: {
getOAuthToken
}
}
}) => {
getOAuthToken(access_token => {
fetch(`https://api.spotify.com/v1/me/player/play?device_id=${id}`, {
method: 'PUT',
body: JSON.stringify({ uris: [spotify_uri] }),
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${access_token}`
},
});
});
};
play({
playerInstance: new Spotify.Player({ name: "..." }),
spotify_uri: 'spotify:track:7xGfFoTpQ2E7fRF5lN10tr',
});
https://developer.spotify.com/documentation/web-playback-sdk/reference/
여기에 예시 코드가 있어서, 이 코드를 가져다 사용했다.
지난 시간에 생성했던 플레이어를 여기서 사용한다.
WebPlayback
이라는 컴포넌트의 useEffect 내에서 플레이어를 생성했다. 해당 코드를 다시 한 번 살펴보자.
useEffect(() => {
const script = document.createElement("script");
script.src = "https://sdk.scdn.co/spotify-player.js";
script.async = true;
document.body.appendChild(script);
window.onSpotifyWebPlaybackSDKReady = () => {
const player = new window.Spotify.Player({
name: "Web Playback SDK",
getOAuthToken: (cb: any) => {
// Run code to get a fresh access token
cb(token?.replace(/\"/g, ""));
},
volume: 0.5,
});
setPlayer(player);
player.addListener("ready", ({ device_id }) => {
setId(device_id);
setReady(true);
console.log("Ready with Device ID", device_id);
});
player.addListener("not_ready", ({ device_id }) => {
console.log("Device ID has gone offline", device_id);
});
player.addListener("player_state_changed", (state) => {
if (!state) {
return;
}
setTrack(state.track_window.current_track);
setPaused(state.paused);
setPosition(state.position);
});
player.connect();
};
}, [token]);
여기서 우리가 사용할 건 두 가지다. 👉 player, device_id
생성한 플레이어와 device_id를 WebPlayback
의 상태로 저장해뒀다.(useState)
이를 사용해 API를 호출한다.
📂core/api/spotifysdk.ts 란 파일에 API 호출 함수를 작성해놓고, WebPlayback
컴포넌트에 import 한다. player와 device_id가 WebPlayback
컴포넌트의 state로 관리되고 있기 때문이다. import한 함수에 player와 device_id를 넣어 onPlay라는 함수를 만들었다.
onPlay 함수는 인자로 재생할 노래의 uri를 받는다. uri가 undefined인 채로 호출될 경우 플레이리스트의 첫번째 노래의 uri를 넘겨준다.
근데 이게 같은 비슷한 함수를 불필요하게 두 번 작성하는 느낌이라, 리팩토링 때 아예 플레이어 관련 상태와 함수를 모두 Context API에서 관리하고 필요한 컴포넌트에서 바로 사용할 수 있게 할까 생각 중이다
// WebPlayback.tsx
import { play, pause } from "../core/api/spotifysdk";
const WebPlayback = ({ data }) => {
const [player, setPlayer] = useState(null);
const [device_id, setId] = useState("");
const onPlay = (uri: string | undefined, is_new: boolean) => {
if (uri === undefined) {
play({
spotify_uri: data[0].uri,
device_id,
position: current_position,
playerInstance: player,
});
}
play({
spotify_uri: uri,
device_id,
position: is_new ? 0 : current_position,
playerInstance: player,
});
};
...
};
export default WebPlayback;
// core/api/spotifysdk.js의 play 함수
export const play = ({
spotify_uri,
device_id,
position,
playerInstance: {
_options: { getOAuthToken },
},
}: Props) => {
getOAuthToken((access_token: string) => {
fetch(`https://api.spotify.com/v1/me/player/play?device_id=${device_id}`, {
method: "PUT",
body: JSON.stringify({ uris: [spotify_uri], position_ms: position }),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${access_token}`,
},
});
});
};
onPlay 함수를 props 사용해 Player
에 전달해주고, 버튼 클릭 시 onPlay가 실행되게 한다. Player
는 props로 current_track
을 전달 받기 때문에 onPlay의 인자로 current_track.uri
를 넘겨준다.
이건 쉽다 ㅎ_ㅎ 2번과 같은 방식으로
Pause Playback API 👉 이 API를 이용하면 된다.
또 너무 길어져서 다음 글로!!!!
리팩토링 전에 기록하려고 한건데 자꾸 배보다 배꼽이 커지는 느낌이라 괴로워😖
✅ 이번 글에서 구현 완료한 기능 ✅
[x] 노래 재생 여부에 따라 버튼이 play
/pause
중 하나로 바뀐다.
[x] 재생 중인 노래가 없는 경우, play
버튼을 누르면 플레이리스트의 첫 곡이 재생된다.
[x] pause
버튼을 누르면 노래를 멈춘다.
[ ] 재생 중인 노래가 있는 경우, play
버튼을 누르면 pause했던 지점부터 노래를 재생한다.
[ ] 플레이리스트 내 노래의 재생 버튼을 클릭하면 해당 노래가 재생된다.
혹시 무료계정으로 진행하셨나요? 무료 계정을 사용하고 있다면 Spotify Web Playback SDK를 사용하여 오디오를 재생하는 것은 지원되지 않는다는 것 같아서요ㅜㅜ