알쓸코지
article thumbnail

이번 시간에는 Spotify Web API를 쉽게 사용할 수 있는 라이브러리인 `spotify-web-api-java`를 활용해서 아티스트 정보를 검색하는 API를 만들어보고자 한다.

 

https://github.com/spotify-web-api-java/spotify-web-api-java

 

GitHub - spotify-web-api-java/spotify-web-api-java: A Java wrapper for Spotify's Web API.

A Java wrapper for Spotify's Web API. Contribute to spotify-web-api-java/spotify-web-api-java development by creating an account on GitHub.

github.com

 


Spotify API 사용하기 with Spring Boot

 

build.gradle

  • spotify-web-api-java를 사용하기 위해서는 `build.gradle`에 다음의 내용을 추가해야 한다.
allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

...

dependencies {
	...
    implementation 'se.michaelthelin.spotify:spotify-web-api-java:8.4.1'
}

 

  • Spotify API 클라이언트를 설정하기 위한 구성 클래스를 정의한다.
package com.duboocho.hilarious;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import se.michaelthelin.spotify.SpotifyApi;

@Configuration
public class SpotifyConfig {

    @Value("${spotify.client-id}")
    private String clientId;

    @Value("${spotify.client-secret}")
    private String clientSecret;

    @Bean
    public SpotifyApi spotifyApi() {
        return new SpotifyApi.Builder()
                .setClientId(clientId)
                .setClientSecret(clientSecret)
                .build();
    }

}

 

application.properties

  • 지난 시간에 발급받은 `Client ID`와 `Client Secret` 값은 환경 변수로 관리한다.
spring.application.name=hilarious

spotify.client-id=${SPOTIFY_CLIENT_ID}
spotify.client-secret=${SPOTIFY_CLIENT_SECRET}

 

Artist 정보 조회 API 만들기

🔗 spotify-web-api-java - SearchArtistsExample

 

`SpotifyApi` 클래스를 활용하면 Web API의 기능들을 편리하게 사용할 수 있다.

지난 시간에 사용했던 search API를 활용하여 다음과 같은 정보들을 얻어오고자 한다.

  • 아티스트 프로필 사진
  • 아티스트 장르
  • 아티스트의 대표곡

이를 위해서는 라이브러리가 제공하는 클래스에 대해서 알아야 한다.

 

Artist 클래스

아티스트에 대한 세부 정보를 담고 있는 클래스
@JsonDeserialize(builder = Artist.Builder.class)
public class Artist extends AbstractModelObject implements IArtistTrackModelObject, ISearchModelObject {
  private final ExternalUrl externalUrls;
  private final Followers followers;
  private final String[] genres;	//아티스트가 주로 활동하는 장르 목록
  private final String href;
  private final String id;		//고유 식별자. API 호출 시 사용됨
  private final Image[] images;		//아티스트 관련 이미지 목록
  private final String name;		//아티스트 이름
  private final Integer popularity;
  private final ModelObjectType type;
  private final String uri;
  ...
 }

 

Track 클래스

곡 정보에 대한 세부 정보를 담고 있는 클래스
@JsonDeserialize(builder = Track.Builder.class)
public class Track extends AbstractModelObject implements IArtistTrackModelObject, ISearchModelObject, IPlaylistItem {
  private final AlbumSimplified album;	//트랙이 속한 앨범
  private final ArtistSimplified[] artists;
  private final CountryCode[] availableMarkets;
  private final Integer discNumber;
  private final Integer durationMs;
  private final Boolean explicit;
  private final ExternalId externalIds;
  private final ExternalUrl externalUrls;
  private final String href;
  private final String id;
  private final Boolean isPlayable;
  private final TrackLink linkedFrom;
  private final Restrictions restrictions;
  private final String name;	//트랙(곡)의 제목
  private final Integer popularity;
  private final String previewUrl;	//트랙의 미리 듣기 URL
  private final Integer trackNumber;
  private final ModelObjectType type;
  private final String uri;
  ...
}

 

TrackInfo

`Track` 클래스에서 내가 원하는 정보만 추출하기 위한 클래스
  • 곡 제목, 앨범 제목, 미리 듣기 URL
package com.duboocho.hilarious.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class TrackInfo {

    private String trackName;
    private String albumName;
    private String previewUri;

}

 

SearchResponseDto

아티스트명으로 검색했을 때 반환하고자 하는 데이터 정의
  • 아티스트 정보 - 아티스트 ID(이건 개발용), 아티스트 이름, 대표 이미지, 활동하는 장르 목록
  • 아티스트의 대표곡
package com.duboocho.hilarious.dto;

import lombok.Data;
import se.michaelthelin.spotify.model_objects.specification.Artist;

import java.util.List;

@Data
public class SearchResponseDto {

    private String artistId;
    private String artistName;
    private String imageUrl;
    private String[] genres;

    private List<TrackInfo> tracks;

    public SearchResponseDto(Artist artist, List<TrackInfo> tracks) {
        this.artistId = artist.getId();
        this.artistName = artist.getName();
        this.imageUrl = artist.getImages()[0].getUrl();
        this.genres = artist.getGenres();
        this.tracks = tracks;
    }

}

 

SpotifyService

아티스트명으로 검색했을 때, 해당 아티스트 관련 정보와 대표곡 3곡을 반환하도록 구현했다.
package com.duboocho.hilarious;

import com.duboocho.hilarious.dto.SearchResponseDto;
import com.duboocho.hilarious.dto.TrackInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.core5.http.ParseException;
import org.springframework.stereotype.Service;
import se.michaelthelin.spotify.SpotifyApi;
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException;
import se.michaelthelin.spotify.model_objects.credentials.ClientCredentials;
import se.michaelthelin.spotify.model_objects.specification.Artist;
import se.michaelthelin.spotify.model_objects.specification.Paging;
import se.michaelthelin.spotify.model_objects.specification.Track;
import se.michaelthelin.spotify.requests.authorization.client_credentials.ClientCredentialsRequest;
import se.michaelthelin.spotify.requests.data.artists.GetArtistsTopTracksRequest;
import se.michaelthelin.spotify.requests.data.search.simplified.SearchArtistsRequest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.neovisionaries.i18n.CountryCode.KR;

@Slf4j
@Service
@RequiredArgsConstructor
public class SpotifyService {

    private static final int SEARCH_LIMIT = 1;
    private static final int TOP_TRACK_LIMIT = 3;

    private static final String ERROR_FETCHING_ACCESS_TOKEN = "Error fetching access token";
    private static final String ERROR_SEARCHING_ARTIST = "Error searching artist";

    private final SpotifyApi spotifyApi;
    private String accessToken;

    public List<SearchResponseDto> search(String artistName) {
        try {
            if (accessToken == null || accessToken.isEmpty()) {
                accessToken = fetchAccessToken();
            }

            Artist[] artists = searchArtist(artistName);
            List<SearchResponseDto> results = new ArrayList<>();

            for (Artist artist : artists) {
                List<TrackInfo> tracks = getTopTracks(artist.getId());
                results.add(new SearchResponseDto(artist, tracks));
            }
            return results;

        } catch (IOException | SpotifyWebApiException | ParseException e) {
            log.error(ERROR_SEARCHING_ARTIST + e.getMessage());
            return new ArrayList<>();
        }
    }

    private Artist[] searchArtist(String artistName) throws IOException, ParseException, SpotifyWebApiException {
        SearchArtistsRequest request = spotifyApi.searchArtists(artistName)
                .limit(SEARCH_LIMIT)
                .build();

        Paging<Artist> searchResult = request.execute();
        return Optional.ofNullable(searchResult.getItems()).orElse(new Artist[0]);
    }

    private List<TrackInfo> getTopTracks(String artistId) throws IOException, ParseException, SpotifyWebApiException {
        GetArtistsTopTracksRequest request = spotifyApi.getArtistsTopTracks(artistId, KR)
                .build();

        Track[] tracks = request.execute();
        List<TrackInfo> trackInfos = new ArrayList<>();

        for (int i = 0; i < Math.min(TOP_TRACK_LIMIT, tracks.length); i++) {
            Track track = tracks[i];
            trackInfos.add(new TrackInfo(track.getName(), track.getAlbum().getName(), track.getPreviewUrl()));
        }
        return trackInfos;
    }

    private String fetchAccessToken() {
        ClientCredentialsRequest clientCredentialsRequest = spotifyApi.clientCredentials().build();
        try {
            final ClientCredentials clientCredentials = clientCredentialsRequest.execute();
            spotifyApi.setAccessToken(clientCredentials.getAccessToken());
            return spotifyApi.getAccessToken();
        } catch (IOException | SpotifyWebApiException | ParseException e) {
            log.error(ERROR_FETCHING_ACCESS_TOKEN + e.getMessage());
            return "error";
        }
    }

}

 

SpotifyController

package com.duboocho.hilarious;

import com.duboocho.hilarious.dto.SearchResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class SpotifyController {

    private final SpotifyService spotifyService;

    @GetMapping("/search")
    public ResponseEntity<List<SearchResponseDto>> search(@RequestParam("artistName") String artistName) {
        return ResponseEntity.ok(spotifyService.search(artistName));
    }

}

 

실행 결과

`localhost:8080/search?artistName=day6`로 검색했을 때 결과

profile

알쓸코지

@chocoji

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