[iOS 11] Apple Music APIを使用してアルバム検索アプリを作る
はじめに
こんにちは。モバイルアプリサービス部の平屋です。
本記事では、Apple Music APIを使用してアルバム情報を取得し、収録曲を再生する実装を紹介します。
Apple Music APIはWWDC17で紹介されたMusicKitに含まれるWeb APIです。MusicKitの概要は以下の記事にまとめています。
サンプルアプリについて
本記事を書くにあたって、以下の機能をもったサンプルアプリを作成しました。
- トップ画面
- キーワードでアルバムを検索する
- アルバムを選択すると、詳細画面を表示
- 詳細画面
- 収録曲情報などを表示する
- 曲をタップすると再生される
本記事では、上記の機能を実現するのに必要な作業のうちの一部を抜粋して紹介します。
記事内で扱っていない部分については、以下のリポジトリで公開しているコードを参照してください!
検証環境
- macOS Sierra 10.12.6
- Xcode 9
- 端末
- iPhone 7, iOS 11
- Apple Music 購読中の Apple IDでログイン済み
目次
- Developer Tokenを作成する
- Apple Music APIへのリクエストのフォーマット
- Search APIを使用してキーワードに合致するアルバムを取得する
- Albums APIを使用してアルバムの詳細を取得する
- アルバムを再生する
- 動作確認
Developer Tokenを作成する
Developer Tokenを作成するには、以下の記事の「Music IDを作成する」から「Developer Tokenを作成する」までの作業を行います。
Apple Music APIへのリクエストのフォーマット
Apple Music APIのURLの形式は以下の通りです。
- Apple Music Catalogに関するAPI
https://api.music.apple.com/{version}/catalog/{storefront}/{api}?[params]
- ユーザー固有のデータに関するAPI
https://api.music.apple.com/{version}/me/{api}
- パスパラメータ
- version
- APIのバージョンを指定
- 現在はv1のみ指定可
- APIのバージョンを指定
- storefront
- 国ごとに割り振られたコードを指定
- 日本の場合はjpを指定
- 国ごとに割り振られたコードを指定
- api
- 各API用の文字列を指定
- Search APIの場合はsearchを指定
- 各API用の文字列を指定
- version
本記事では「Apple Music Catalogに関するAPI」のうち、Search / Albums APIを扱います。
Search APIを使用してキーワードに合致するアルバムを取得する
リクエスト
キーワードに合致するリソースを取得するにはSearch APIを使用します。
パスは/catalog/{storefront}/search
で、検索ワードやリソースのタイプはクエリパラメータで指定します。
func search(term: String, completion: @escaping (SearchResult?) -> Void) { // ... // jp: 日本のストアを指定 var components = URLComponents(string: "https://api.music.apple.com/v1/catalog/jp/search")! let expectedTerms = term.replacingOccurrences(of: " ", with: "+") let urlParameters = ["term": expectedTerms, // 検索ワード "limit": "10", // 件数 "types": "albums"] // リソースのタイプとしてアルバムを指定 var queryItems = [URLQueryItem]() for (key, value) in urlParameters { queryItems.append(URLQueryItem(name: key, value: value)) } components.queryItems = queryItems var request = URLRequest(url: components.url!) request.httpMethod = "GET" request.addValue("Bearer \(developerToken)", // Developer Tokenをヘッダに入れる forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: request) { data, response, error -> Void in // ... } task.resume() }
レスポンスの形式
以下のJSONは、GET https://api.music.apple.com/v1/catalog/jp/search?term=swift&types=albums
のレスポンスです。
results > albums > dataの中にアルバムの配列が入ってきます。
{ "results": { "albums": { "href": "/v1/catalog/jp/search?term=swift&types=albums", "next": "/v1/catalog/jp/search?offset=5&term=swift&types=albums", "data": [ // アルバムの配列 { "id": "907312293", "type": "albums", "href": "/v1/catalog/jp/albums/907312293", "attributes": { "artwork": { ...}, "artistName": "テイラー・スウィフト", "isSingle": false, // ... } }, // ... ] } } }
今回のサンプルアプリでは、Codableプロトコルに適合したstructを用意しました。
このプロトコルに関するドキュメントはこちらにあります。
レスポンスJSON内のアルバムに対応するstructはResourceです。
struct SearchResult: Codable { let albums: [Resource]? // ... } struct Resource: Codable { let id: String? let type: String? let attributes: Attributes? let href: String? let next: String? let relationships: Relationships? } struct Attributes: Codable { let artwork: Artwork? let artistName: String? //... } struct Relationships: Codable { let tracks: [Resource]? // ... }
レスポンスのパース
SearchResultのinit(from: Decoder)
内にResourceの配列を取得する処理を実装します。
struct SearchResult: Codable { // ... enum RootKeys: String, CodingKey { case results } enum ResultsKeys: String, CodingKey { case albums } enum AlbumsKeys: String, CodingKey { case data } init(albums: [Resource]?) { self.albums = albums } init(from decoder: Decoder) throws { // [Resource]を取得する let values = try decoder.container(keyedBy: RootKeys.self) let results = try values.nestedContainer(keyedBy: ResultsKeys.self, forKey: .results) let albums = try? results.nestedContainer(keyedBy: AlbumsKeys.self, forKey: .albums) let data = try albums?.decode([Resource].self, forKey: .data) self.init(albums: data) } }
レスポンス取得時にJSONDecoderを使用してSearchResult
オブジェクトを作成します。
func search(term: String, completion: @escaping (SearchResult?) -> Void) { // ... let task = URLSession.shared.dataTask(with: request) { data, response, error -> Void in if let error = error { // ... } else { // JSONDecoderを使用してデコードを行う guard let searchResult = try? JSONDecoder().decode(SearchResult.self, from: data!) else { print("JSON Decode Failed"); completion(nil) return } completion(searchResult) } } // ... }
Albums APIを使用してアルバムの詳細を取得する
リクエスト
アルバムの詳細情報を取得するにはAlbums APIを使用します。
パスは/catalog/{storefront}/albums/{id}
で、{id}
にアルバムのIDを指定します。
func album(id: String, completion: @escaping (Resource?) -> Swift.Void) { // ... // jp: 日本のストアを指定 // id: アルバムのリソースID let url = URL(string: "https://api.music.apple.com/v1/catalog/jp/albums/\(id)")! var request = URLRequest(url: url) request.httpMethod = "GET" request.addValue("Bearer \(developerToken)", // Developer Tokenをヘッダに入れる forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: request) { data, response, error -> Void in // ... } task.resume() }
レスポンスの形式
以下のJSONは、GET https://api.music.apple.com/v1/catalog/jp/albums/907312293
のレスポンスです。
ルートのdataの値はアルバムの配列で、アルバム収録曲のオブジェクトはrelationships > tracks > dataの中に入ってきます。
{ "data": [ // アルバムの配列 { "id": "907312293", "type": "albums", "href": "/v1/catalog/jp/albums/907312293", "attributes": { "artwork": { ... }, "artistName": "テイラー・スウィフト", "isSingle": false, // ... }, "relationships": { "artists": { ... }, "tracks": { "data": [ // アルバム収録曲の配列 { "id": "907312371", "type": "songs", "href": "/v1/catalog/jp/songs/907312371", "attributes": { "name": "Welcome To New York", "trackNumber": 1, // ... } }, { "id": "907312376", // ... }, // ... ], "href": "/v1/catalog/jp/albums/907312293/tracks" } } } ] }
レスポンスのパース
Relationshipsのinit(from: Decoder)
内にResourceの配列を取得する処理を実装します。
struct Relationships: Codable { // ... enum RelationshipsKeys: String, CodingKey { case tracks } enum TracksKeys: String, CodingKey { case data } init(tracks: [Resource]?) { self.tracks = tracks } init(from decoder: Decoder) throws { // [Resource]を取得する let values = try decoder.container(keyedBy: RelationshipsKeys.self) let tracks = try values.nestedContainer(keyedBy: TracksKeys.self, forKey: .tracks) let data = try tracks.decode([Resource].self, forKey: .data) self.init(tracks: data) } }
レスポンス取得時にJSONDecoderを使用してResource
オブジェクトを作成します。
func album(id: String, completion: @escaping (Resource?) -> Swift.Void) { // ... let task = URLSession.shared.dataTask(with: request) { data, response, error -> Void in if let error = error { // ... } else { guard // アルバムを取り出す let jsonData = try? JSONSerialization.jsonObject(with: data!), let dictionary = jsonData as? [String: Any], let dataArray = dictionary["data"] as? [[String: Any]], let albumDictionary = dataArray.first, let albumData = try? JSONSerialization.data(withJSONObject: albumDictionary), // JSONDecoderを使用してデコードを行う let album = try? JSONDecoder().decode(Resource.self, from: albumData) else { print("JSON Decode Failed"); completion(nil) return } completion(album) } } task.resume() }
アルバムを再生する
再生可能かどうかを判別する
Apple Musicで提供されている曲を再生するには以下を満たす必要があります。
- Apple Music購読中のApple IDで端末にログインしていること
StoreKitのSKCloudServiceControllerのrequestCapabilities(completionHandler:)
を使用すれば、再生可能かどうかを判別できます。
self.cloudServiceController.requestCapabilities { capabilities, error in guard capabilities.contains(.musicCatalogPlayback) else { return } // 再生可能! }
再生する
曲を再生するにはMediaPlayerのMPMusicPlayerControllerを使用します。
setQueue(with:)
を呼ぶと再生キューに曲を追加できます。play()
を呼ぶと、再生キューに入っている曲が再生されます。
以下の実装は指定曲からアルバムを再生する例です。
class AlbumViewController: UITableViewController { // ... let musicPlayer = MPMusicPlayerController.systemMusicPlayer var album: Resource? // ... } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // ... // Descriptorを作成 let tracks = album!.relationships!.tracks! let trackIDs = tracks.map { $0.id! } let startTrackID = tracks[indexPath.row].id! let descriptor = MPMusicPlayerStoreQueueDescriptor(storeIDs: trackIDs) descriptor.startItemID = startTrackID // 再生キューに曲を追加 musicPlayer.setQueue(with: descriptor) // 再生 musicPlayer.play() }
動作確認
最後に、サンプルアプリの動作例を紹介します。
サンプルアプリを起動します。ナビゲーションバー下部にサーチバーが表示されます。
キーワードを入力すると、Search APIから取得したアルバムの一覧が表示されます。
アルバムを選択すると、詳細画面が表示されます。この画面にはAlbums APIから取得したアルバム詳細情報が表示されます。
任意の曲をタップすると、その曲からアルバムが再生されます。
アプリがバックグラウンドになっても再生が継続されます。
さいごに
本記事では、Apple Music APIを使用してアルバム情報を取得し、収録曲を再生する実装を紹介しました。
今回作成したサンプルアプリのソースコードは以下のリポジトリで公開してますので参考にしてみてください。