Tech Blog of Pinomaker
article thumbnail

 

Spotify는 전세계에서 가장 많이 사용하는 음악 스트리밍 서비스다. 이번에 React Native로 Spotify 노래 재생을 구현하게 되였기에 해당 과정을 기록한다.

 

0. 들어가기 전

이번에는 React Native Cli를 기반으로한 React Native 프로젝트에서 해당 기능을 구현했으며, 사용한 버전은 아래와 같다.

"react-native": "0.70.1"

 

1. 구현 방법 고려하기

 

Spotify에서는 개발을 위한 여러 개의 API와 SDK를 제공한다. 따라서 내 목표인 Spotify의 음악 스트리밍을 구현할 수 있는 방법이 여러개가 존재한다.

 

아래는 Spotify의 개발자 문서가 적힌 사이트인데, 들어가보면 Web API, SDK 등 여러가지를 지원하는 것을 확인할 수 있다.

 

 

Home | Spotify for Developers

Build with Spotify’s 100 million songs, 5 million podcasts and much more See it in action

developer.spotify.com

 

내가 고려한 방법들은 아래와 같다.

  1. Webview를 기반으로 Web API와 Web Playback SDK를 사용한 구현
  2. RN을 위한 여러 Custom SDK를 이용한 구현
  3. Native SDK를 이용한 구현
  4. React Native Sound의 Sound 객체를 이용한 구현

위의 있는 방법들을 차례로 해본 결과 내가 구현에 성공을 한 방법은 결국은 5번이다. 

 

(1) Webview를 기반으로 Web API를 사용한 구현

Spotify에서는 웹 기반의 API와 웹에서 사용 가능한 SDK를 제공하여 쉽게 음악 스트리밍을 웹에서 구현할 수 있었다. 나는 아래의 블로그를 참고하여, HTML + JS + SDK로 웹에서 구동되는 플레이어는 구현을 아래와 같이 했다.

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Spotify Web Playback SDK Quick Start</h1>
    <button id="togglePlay">Toggle Play</button>
    <script src="https://sdk.scdn.co/spotify-player.js"></script>
    <script>
      window.onSpotifyWebPlaybackSDKReady = () => {
        const token = [access Token]
        const player = new Spotify.Player({
          name: "Web Playback SDK Quick Start Player",
          getOAuthToken: (cb) => {
            cb(token);
          },
          volume: 0.5,
        });

        alert(`token : ${token}`);

        player.addListener("ready", ({ device_id }) => {
          console.log("Ready with Device ID", device_id);
          deviceId = device_id;
        });

        player.addListener("not_ready", ({ device_id }) => {
          alert(`Not Ready : ${device_id}`);
          console.log("Device ID has gone offline", device_id);
        });

        player.addListener("initialization_error", ({ message }) => {
          alert(message);
          console.log(message);
        });

        player.addListener("authentication_error", ({ message }) => {
          console.log(message);
        });

        player.addListener("account_error", ({ message }) => {
          console.log(message);
        });

        const play = ({
          spotify_uri,
          playerInstance: {
            _options: { getOAuthToken },
          },
        }) => {
          getOAuthToken((access_token) => {
            fetch(
              `https://api.spotify.com/v1/me/player/play?device_id=${deviceId}`,
              {
                method: "PUT",
                body: JSON.stringify({ uris: [spotify_uri] }),
                headers: {
                  "Content-Type": "application/json",
                  Authorization: `Bearer ${access_token}`,
                },
              }
            );
          });
        };

        document.getElementById("togglePlay").onclick = function () {
          alert("HELLO");
          play({
            playerInstance: player,
            spotify_uri: "spotify:track:7xGfFoTpQ2E7fRF5lN10tr",
          });
        };

        player.connect();
      };
    </script>
  </body>
</html>

하지만  문제가 존재했는 데, 웹뷰로 띄었을 떄 이상하게도 SDK를 사용할 수 없어 자꾸 initialization Error가 발생하였다. 이에 대해 여러 고생을 하면서 해결을 해볼려고 했지만 결국 해결을 못 하여 해당 방법은 포기하였다.

 

 

(2) RN을 위한 여러 Custom SDK를 이용한 구현

 

1번 방법이 실패하자, Web SDK를 RN에서 사용할 수 있게 만들어둔 모듈을 탐색하니 역시 여러개가 나왔다.

 

 

GitHub - cjam/react-native-spotify-remote: React Native wrapper around the Spotify Remote SDK

React Native wrapper around the Spotify Remote SDK - GitHub - cjam/react-native-spotify-remote: React Native wrapper around the Spotify Remote SDK

github.com

 

GitHub - lufinkey/react-native-spotify: A react native module for the Spotify SDK. [Deprecated]

A react native module for the Spotify SDK. [Deprecated] - GitHub - lufinkey/react-native-spotify: A react native module for the Spotify SDK. [Deprecated]

github.com

 

나는 둘 다 시도를 해보았으나, react-native-spotify는 빌드 과정에서 패키지를 찾을 수 없다는 지속적인 오류가 발생하여 건너갔으며, react-native-spotify-remote는 가장 대중적으로 많이 사용하는 것 같고, 여러 자료도 많아 시도를 지속적으로 했으나, https://github.com/cjam/react-native-spotify-remote/issues/166 해당 이슈로 인하여 도저히 구현이 안 되서 포기했다.

 

(3) Native SDK를 이용한 구현

Native SDK에서는 스포티파이 어플리케이션의 원격 조정 할 수 있는 기능만 제공하여 구현 불가능하다는 결론을 내렸다.

 

2. React Native Sound의 Sound 객체를 이용한 구현

먼저 react-native-sound 모듈을 설치하자

npm install react-native-sound

 

나는 Spotify Web API 중에서 음악을 가져오는 API도 같이 사용하기에, 음악 리스트 스크린에서 음악 리스트 Web API를 호출하고 그 음악을 눌렀을 때 플레이어로 이동하여 음악이 실행되는 구조였다.

 

따라서 음악 리스트를 보면 Spotify에서 음악을 재생할 수 있는 URI를 제공해준다.

 

 

Web API Reference | Spotify for Developers

The date and time the track was saved. Timestamps are returned in ISO 8601 format as Coordinated Universal Time (UTC) with a zero offset: YYYY-MM-DDTHH:MM:SSZ. If the time is imprecise (for example, the date/time of an album release), an additional field i

developer.spotify.com

 

내가 테스트용으로 사용한 API다. 해당 API를 호출할 경우에 아래와 같은 데이터를 얻게 된다.

 

{
  "href": "https://api.spotify.com/v1/me/tracks?offset=0&limit=20&locale=ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
  "items": [
    {
      "added_at": "2023-10-14T16:59:18Z",
      "track": {
        "album": {
          "album_type": "album",
          "artists": [
            {
              "external_urls": {
                "spotify": "https://open.spotify.com/artist/6OwKE9Ez6ALxpTaKcT5ayv"
              },
              "href": "https://api.spotify.com/v1/artists/6OwKE9Ez6ALxpTaKcT5ayv",
              "id": "6OwKE9Ez6ALxpTaKcT5ayv",
              "name": "AKMU",
              "type": "artist",
              "uri": "spotify:artist:6OwKE9Ez6ALxpTaKcT5ayv"
            }
          ],
          "external_urls": {
            "spotify": "https://open.spotify.com/album/1eu07xRE0vQfN5et0Y3DAy"
          },
          "href": "https://api.spotify.com/v1/albums/1eu07xRE0vQfN5et0Y3DAy",
          "id": "1eu07xRE0vQfN5et0Y3DAy",
          "images": [
            {
              "height": 640,
              "url": "https://i.scdn.co/image/ab67616d0000b27378551e802bd7b81d7af67dfb",
              "width": 640
            },
            {
              "height": 300,
              "url": "https://i.scdn.co/image/ab67616d00001e0278551e802bd7b81d7af67dfb",
              "width": 300
            },
            {
              "height": 64,
              "url": "https://i.scdn.co/image/ab67616d0000485178551e802bd7b81d7af67dfb",
              "width": 64
            }
          ],
          "is_playable": true,
          "name": "PLAY",
          "release_date": "2014-04-07",
          "release_date_precision": "day",
          "total_tracks": 11,
          "type": "album",
          "uri": "spotify:album:1eu07xRE0vQfN5et0Y3DAy"
        },
        "artists": [
          {
            "external_urls": {
              "spotify": "https://open.spotify.com/artist/6OwKE9Ez6ALxpTaKcT5ayv"
            },
            "href": "https://api.spotify.com/v1/artists/6OwKE9Ez6ALxpTaKcT5ayv",
            "id": "6OwKE9Ez6ALxpTaKcT5ayv",
            "name": "AKMU",
            "type": "artist",
            "uri": "spotify:artist:6OwKE9Ez6ALxpTaKcT5ayv"
          }
        ],
        "disc_number": 1,
        "duration_ms": 193321,
        "explicit": false,
        "external_ids": {
          "isrc": "KRB471400414"
        },
        "external_urls": {
          "spotify": "https://open.spotify.com/track/6qkx0tenDglbF21CU4wa1k"
        },
        "href": "https://api.spotify.com/v1/tracks/6qkx0tenDglbF21CU4wa1k",
        "id": "6qkx0tenDglbF21CU4wa1k",
        "is_local": false,
        "is_playable": true,
        "name": "200%",
        "popularity": 63,
        "preview_url": "https://p.scdn.co/mp3-preview/7f3b1a226d83b86924194c89aff6d92d840bef4d?cid=d411b41e0fe24fdb93f556b260b0a927",
        "track_number": 2,
        "type": "track",
        "uri": "spotify:track:6qkx0tenDglbF21CU4wa1k"
      }
    }
  ],
  "limit": 20,
  "next": null,
  "offset": 0,
  "previous": null,
  "total": 1
}

 

자 이 데이터를 자세히 보면 preview_url이나 href에서 해당 음원의 주소를 cdn으로 되어있는 것을 전달해주는 것을 알 수 있다. 나는 이 데이터를 가지고 Sound 객체를 생성하여 구현했다.

 

 

 

// ** src/screen/PlayerScreen/index.js

// ** React Imports
import { useEffect, useState } from 'react';

// ** Component Imports
import PlayerScreenView from './player-screen';

// ** Utils Imports
import Sound from 'react-native-sound';

const PlayerScreen = ({
  audioUrl = 'https://p.scdn.co/mp3-preview/7f3b1a226d83b86924194c89aff6d92d840bef4d?cid=d411b41e0fe24fdb93f556b260b0a927',
}) => {
  const [sound, setSound] = useState(null);
  const [isPlay, setIsPlay] = useState(false);

  useEffect(() => {
    const sound = new Sound(audioUrl, null);

    setSound(sound);
  }, []);

  const handlePlay = () => {
    if (sound) {
      sound.play();
      setIsPlay(true);
    }
  };

  const handleStop = () => {
    if (sound && isPlay) {
      sound.pause();
      setIsPlay(false);
    }
  };

  return <PlayerScreenView handlePlay={handlePlay} handleStop={handleStop} />;
};

export default PlayerScreen;
// ** src/screen/PlayerScreen/player-screen.js

// ** RN Imports
import { Button, View } from 'react-native';

const PlayerScreenView = ({ handlePlay, handleStop }) => {
  return (
    <View style={{ flex: 1 }}>
      <Button title="Play" onPress={handlePlay} />
      <Button title="Stop" onPress={handleStop} />
    </View>
  );
};

export default PlayerScreenView;

 

리스트로 부터 넘겨받을 Audio Uri를 선언한다. 나는 리스트와의 연동이 구현이 미흡해서 default value를 테스트용으로 삽입해서 사용헀다. 넘어오는 audioUrl를 기반으로 Sound 객체를 생성하고, Button이 클릭 될 때 Sound의 play(), pause() 메서드를 이용하여 노래를 출력 하고 멈추는 코드를 구현했다.

 

3. 마무리하며..

금방할 거 같았던 작업을 생각보다 여러 이슈를 겪으며 오래 걸렸는 데, 그래도 나름 잘 구현이 된 거 같아서 나쁘진 않다. 다만 Spotify 쪽에서 React Native나 Flutter 쪽 SDK를 공식 지원해준다면 좀 더 편해지지 않을까 싶긴헀으며, 음악 재생을 지원해주는 게 신기했는 데, Spotify에 내 계정이 아니라 각 사용자들이 계정과 프리미엄 라이센스를 구독해야한다는 점이 있기에 실 Product에서는 사용하기엔 애매한 거 같다.

 

 

참고 자료

 

'F.E > React Native' 카테고리의 다른 글

[ERROR] expo start 에러  (0) 2022.05.02
[React Native] expo를 활용하여 프로젝트 생성  (0) 2022.05.02
profile

Tech Blog of Pinomaker

@pinomaker

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!