iOS

[iOS] Apple MusicKit 활용하기

Hatchling.dev 2024. 1. 11. 19:54

안녕하세요!!!

오늘은 AppleMusic과 연동되는 Application을 개발하면서 접하게 된 MusicKit에 대해 알아보겠습니다!!

 

먼저 AppleMusic을 활용해서 앱을 만들려고 했던 건 아니었고.. 처음엔 YouTubeMusic이나 Melon 같은 한국 사용자가 많은 음원 사이트를 활용하고 싶었으나 원하는 API를 제공하지 않아서.. AppleMusic API를 찾아보게 되었는데 다양한 기능들(음원, 플레이리스트, 재생 등)을 제공하는 것을 알게 되었습니다!

 

MusicKit을 활용하기 위해선 developer program에 가입해야 합니다ㅠㅠㅠ 이미 가입중이거나 가입해보셨던 분들은 아시겠지만 조큼.. 비쌉니다😢 99달러... developer program에 가입하게 되면 직접 앱을 마켓에 올리거나 다양한 API를 사용하실 수 있으니 관심 있으신 분들은 developer.apple.com 해당 링크를 참고하시길 바랍니다!!

 

Apple Developer

There’s never been a better time to develop for Apple platforms.

developer.apple.com


자! 이제 MusicKit을 사용해볼까요??

1. 먼저 MusicKit을 활용할 프로젝트를 생성해주세요!

2. MusicKit을 사용하기 위해 developer.apple.com에서 프로젝트 Key와 Identiffiers를 생성해주세요! (Key는 한 번만 다운로드 받을 수 있으니 잘 관리해주세요!)

3. 다운로드 받은 Key 파일을 열어 private Key 값과 developer 사이트에서 확인할 수 있는 KeyID, developer teamID를 활용해 JWT를 발급 받아야 합니다. (JWT란 웹사이트에서 인증 권한을 처리하기 위한 json형태의 token입니다.)

4. JWT를 발급 받는 방법은 다양한데 jwt.io 사이트에서 간단하게 발급받을 수 있습니다. (만약 서버와 백엔드를 운영할 계획이라면 jwt를 생성해주는 라이브러리를 활용해도 좋을 것 같네요!)

5. 그 다음에는 property list에 해당 jwt 토큰을 등록해야 합니다. 만약 프로젝트를 공개할 예정이라면 ignore 처리가 필요하겠죠?!

6. StoreKit을 활용하여 현재 프로젝트에서 AppleMusic 앱에 대한 접근 권한을 허용해줘야 합니다! (아래 코드 참고)

SKCloudServiceController.requestAuthorization { (status) in
    if status == .authorized {
        // API 실행
    }
}

7. 권한을 얻었다면 이제 JWT(developerToken) 토큰을 활용해 UserToken을 받아야 합니다. 위에서 StoreKit을 사용한 것처럼 SKCloudServiceController의 requestUserToken 메소드를 활용해 UserToken을 생성할 수 있습니다! (아래 코드 참고)

func fetchUserToken() async throws -> String {
    return try await SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken)
}

 


 

자 여기까지 진행하셨다면 MusicKit을 활용할 준비는 끝났습니다! 이제 다양한 API를 활용해보겠습니다!

 

먼저 playlist를 가져오겠습니다. (API URL은 공식문서를 참고해주세요!)

playlist를 가져오는 URL과 해당 API에 필요한 정보, developer token, user token을 URLRequest 객체에 담아 API를 요청하면 됩니다!!

반환 값을 원하는 형태로 변환하여 활용하시면 됩니다! 아래는 json 형태로 변환하여 리턴하는 코드이니 참고하셔도 좋을 것 같습니다!

func fetchPlaylists(userToken: String) async throws -> [Playlist] {
    guard let musicURL = URL(string: "https://api.music.apple.com/v1/me/library/playlists") else { throw NetworkError.invalidURL }
    var musicRequest = URLRequest(url: musicURL)
    musicRequest.httpMethod = "GET"
    musicRequest.addValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization")
    musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token")
		
    let (data, _) = try await URLSession.shared.data(for: musicRequest)
    let playlists = try JSONDecoder().decode(PlaylistDatum.self, from: data)
    return playlists.data
}
더보기

playlist 데이터 구조

struct PlaylistDatum: Codable {
    let data: [Playlist]
    let meta: Meta?
}

struct Playlist: Codable {
    let id, type, href: String
    let attributes: Attributes
}

struct Attributes: Codable {
    let playParams: PlayParams
		let artwork: PlaylistArtwork?
		let description: PlaylistDescription?
    let hasCatalog, canEdit, isPublic: Bool
    let name, dateAdded: String
}

struct PlaylistArtwork: Codable {
    let width, height: JSONNull?
    let url: String
}

struct PlaylistDescription: Codable {
    let standard: String
}

struct PlayParams: Codable {
    let id, kind: String
    let isLibrary: Bool
}

struct Meta: Codable {
    let total: Int
}

플레이리스트를 가져왔다면 이번에는 해당 플레이리스트 안에 있는 노래들을 가져오는 방법을 알아보도록 하겠습니다!

API를 요청하는 방법은 플레이리스트를 요청했을 때와 url만 제외하면 동일합니다.

func fetchSongs(userToken: String, id: String) async throws -> [Song] {
    guard let musicURL = URL(string: "https://api.music.apple.com/v1/me/library/playlists/\(id)/tracks") else { throw NetworkError.invalidURL }
    var musicRequest = URLRequest(url: musicURL)
    musicRequest.httpMethod = "GET"
    musicRequest.addValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization")
    musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token")
		
    let (data, _) = try await URLSession.shared.data(for: musicRequest)
    let songs = try JSONDecoder().decode(SongDatum.self, from: data)
    return songs.data
}
더보기

song 데이터 구조

struct SongDatum: Codable {
    let data: [Song]
    let meta: SongMeta
}

struct Song: Codable {
    let id: String
    let type: TypeEnum
    let attributes: SongAttributes
    let href: String
}

struct SongAttributes: Codable {
    let name, albumName: String
    let genreNames: [String]
    let artwork: Artwork
    let hasLyrics: Bool
    let artistName: String
    let durationInMillis, discNumber: Int
    let playParams: SongPlayParams
    let trackNumber: Int
    let releaseDate: String
    let contentRating: String?
}

struct Artwork: Codable {
    let url: String
    let height, width: Int
}

struct SongPlayParams: Codable {
    let reportingID: String?
    let catalogID, id: String
    let kind: Kind
    let reporting, isLibrary: Bool

    enum CodingKeys: String, CodingKey {
        case reportingID = "reportingId"
        case catalogID = "catalogId"
        case id, kind, reporting, isLibrary
    }
}

enum Kind: String, Codable {
    case song = "song"
}

enum TypeEnum: String, Codable {
    case librarySongs = "library-songs"
}

struct SongMeta: Codable {
    let total: Int
}

 

데이터 구조를 잘 보시면 옵셔널 데이터들이 간혹 있는 경우가 있는데 해당 데이터가 없는 경우 API에서 nil이 아닌 해당 프로퍼티가 아예 제외된 상태로 값이 반환되기 때문에 애를 먹었던 기억이 있네요ㅠㅠㅠ 직접 해당 프로퍼티에 옵셔널을 씌워 해결할 수 있었습니다!

혹시나 다른 API를 사용하면서 값이 제대로 반환되지 않는다면 해당 부분을 생각해보는 것도 도움이 되실 것 같습니다!!


 

MusicKit을 처음 사용하면서 느낀 점은 생각보다 관련 자료가 많지 않다는 것이었습니다... 생각보다 AppleMusic API를 활용하는 경우가 많지는 않은가 봅니다🫨

MusicKit 뿐만 아니라 JWT, UIKit + Combine 등 처음 사용해본 기술들이 많아서 어려움도 많았지만 그 만큼 새로운 것들을 알아가며 더 재미있었던 프로젝트였습니다😄

(UIKit + Combine에 대한 내용은 다음에 따로 포스팅 할 예정입니다!!)

 

참고 사이트

https://developer.apple.com/documentation/applemusicapi/