iOSアプリにApple Music機能を追加（音楽の検索と再生） (MusicKit)

これまでは、楽曲を検索するにはApple Music APIを使用する必要がありました。新たに発表されたiOSのMusicKitフレームワークによって、楽曲を検索したり、アルバム内の曲をすべてリストアップしたり、楽曲を再生したりするためのリクエストをより簡単に行えるようになりました。

このフレームワークは、Androidシステムでも利用可能です。この記事では、これについては触れません。

MusicKitの新たなフレームワークには、新しくなったSwiftの並行処理も採用されています。データのリクエストに対するレスポンスを待つには、`await`コマンドを使用することができます。

この記事では、`Apple Music`のカタログを検索し、音楽を再生するアプリの構築について説明します。

## 使用事例

Appleによると、可能となる使用事例にはゲーム内のBGM、トレーニング中の音楽、ソーシャルメディアなどが含まれています（しかしながら、ご使用の際にはライセンスや著作権などに関して、常にご確認をいただくようお願いします）。

## 最初にEntitlementを追加する

最初に https://developer.apple.com/account/resources/identifiers/list に移動し、アプリケーションを開くことを選択します。クリックして`App Services`タブを開き、`MusicKit`のトグルをオンにします。

![スクリーンショット 2022-01-25 14.13.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/cdb55975-d14c-e5ac-84f6-8a25f612aa22.png)

リスト内にアプリケーションが表示されない場合は、Xcodeの`Signing & Capabilities`ビューで任意の機能を追加できます。これによりXcodeが自動的でに署名プロファイルを生成します。

トークンを生成する必要はありません。それはMusicKitによって自動的に生成されます。

## Apple Music ライブラリ へのアクセス申請

ユーザーの視聴履歴や個人ライブラリにアクセスしたい場合は、許可を申請する必要があります。

まず、`Info.plist`ファイル内に使用説明文字列`NSAppleMusicUsageDescription`を追加します。

次に、ユーザーの認可を要求します

```swift
Button("Request authorization") {
    Task {
        await MusicAuthorization.request()
    }
}
```

次のコードで、ユーザーがアクセスを許可しているか確認します。

```swift
Button("Reload authorization status") {
    self.currentAuthStatus = MusicAuthorization.currentStatus
}
```

## ユーザーのApple Musicサブスク契約状況を確認

画面が表示されたら、ユーザーのApple Musicサブスク契約状況の問い合わせができます。

まず、ユーザーのサブスク契約状況を格納する変数`musicSubscription`を作成します。表示開始時には、これは`nil`となっています。

```swift
@State private var musicSubscription: MusicSubscription?
```

ビューが表示されたら、forループで`await`を利用して読み込みを続け、ユーザーの登録ステータスにおける変化を待ちます。

```swift
.task {
    for await subscription in MusicSubscription.subscriptionUpdates {
        self.musicSubscription = subscription
    }
}
```

登録に関する更新情報があれば、ユーザーの登録ステータスを読み込むことができます。

### ユーザーがApple Musicに登録できるかどうかを確認

`musicSubscription.canBecomeSubscriber`を利用して、ユーザーがApple Musicの登録者になれるかどうかを確認します。

この値がtrueの場合、ユーザーはApple Musicにまだ登録しておらず、登録可能であることを意味します。

`MusicSubscriptionOffer`オブジェクトを利用すれば、ユーザーにApple Musicへの登録を勧めることができます。SwiftUI内では、`musicSubscriptionOffer`ビューモディファイアが使用可能です。

```swift
@State private var showAppleMusicSubscriptionOfferView: Bool = false

if musicSubscription.canBecomeSubscriber {
    Button("Promopt the user to subscribe to Apple Music") {
        self.showAppleMusicSubscriptionOfferView = true
    }
    .musicSubscriptionOffer(isPresented: $showAppleMusicSubscriptionOfferView)
}
```

変数`musicSubscription.canBecomeSubscriber`が`false`の場合は以下のいずれかが当てはまります:

1. ユーザーのデバイスが制限されているか、もしくはApple Musicにサブスクライブできません。
2. ユーザーが既にApple Musicにサブスクライブしています。どちらに当てはまるのか確認したい場合は、別の変数を使ってユーザーがApple Musicカタログにアクセスできるかチェックする必要があります。

### ユーザーがApple Musicのカタログにアクセスできるかどうかの確認

変数`musicSubscription.canPlayCatalogContent`を使用します。この変数がtrueであれば、ユーザーはApple Music内のコンテンツを再生できることを意味し、ほとんどの場合、ユーザーはApple Musicに加入していることになります。

### ユーザーのiCloudミュージックライブラリへのアクセス確認

変数`musicSubscription.hasCloudLibraryEnabled`を使って、ユーザーがクラウドミュージックライブラリを有効にしているかどうかを確認することができます。

## 音楽を検索する

Apple Musicの音楽カタログを検索するには`MusicCatalogSearchRequest`オブジェクトを使用します。

```swift
public struct MusicCatalogSearchRequest {
    public init(term: String, types: [MusicCatalogSearchable.Type])
}
```

`term`変数には検索語を指定してください。`types`変数には検索するデータの種類を指定します（例：曲、アルバム、プレイリストなど）。検索する種類は複数定義することができます。

たとえば、次のコードを使用すると検索語が`BGM`の曲を検索することができます。

```swift
let request = MusicCatalogSearchRequest(term: "BGM", types: [Song.self])
```

次に、`let response = try await request.response()`を利用して、検索リクエストを実行します。`await`を使用しているので、検索結果が返ってくるまでコードはこの行で停止します。

```swift
Button("Perform search") {
    Task {
        do {
            let request = MusicCatalogSearchRequest(term: searchTerm, types: [Song.self])
            self.isPerformingSearch = true
            let response = try await request.response()
            self.isPerformingSearch = false
            self.searchResultSongs = response.songs
        } catch {
            fatalError("Error")
        }
    }
}
```

この例では、応答をリクエストする直前に、`isPerformingSearch`を`true`に設定しています。応答の準備が整うと、`isPerformingSearch`は`false`に、`searchResultSongs`は検索によって取得された曲に設定されます。

## 特定のアイテムをリクエストする

アイテム（曲またはアルバム）をリクエストするためには、そのIDを使用します。

```swift
let request = MusicCatalogResourceRequest<Album>(matching: \.id, equalTo: "1594311281")
let response = try await request.response()
let items: MusicItemCollection<Album> = response.items
```

## オブジェクトのさらなる情報を読み込む

MusicKitは、すべての情報で応答するわけではありません。例えば、アルバムを検索する場合、アルバム内の曲の情報は含まれません。

MusicKitから受け取ったオブジェクトは、`with`関数を呼び出すことで更新できます。

例えば、アルバムオブジェクトがあり、曲を含むアルバムを取得したい場合、関数`album.with([.artists, .tracks, .relationAlbums])`を呼び出します。

このSwiftUIビューの例では、検索結果からアルバム変数を入力し、曲情報でオブジェクトを更新します。

```swift
struct AlbumDetailsView: View {
    
    @State private var updatedAlbumObject: Album?

    var album: Album

    var body: some View {
        
        AnyView()
            .task {
                self.updatedAlbumObject = try? await album.with([.tracks])
            }
        
    }

}
```

`self.updatedAlbumObject.tracks`変数を使用して、このアルバム内のすべての曲を取得します。

```swift
if let tracks = self.updatedAlbumObject?.tracks {
    ForEach(tracks) { track in
        switch track {
        case .song(let songItem):
            SongInfoView(songItem: songItem)
        case .musicVideo(_):
            EmptyView()
        @unknown default:
            EmptyView()
        }
    }
} else {
    ProgressView("Loading tracks...")
}
```

## 音楽アイテムのアートワークを入手

`.artwork`プロパティを呼び出すと、音楽アイテム（曲やアルバムなど）のアートワークを取得できます。

```swift
if let artwork = songItem.artwork {
    ArtworkImage(artwork, height: 100)
}
```

SwiftUI内では、`ArtworkImage`変数を直接使用して、アートワーク画像を表示できます。

## 音楽を再生

システムプレーヤーまたはアプリプレーヤーを使用して音楽を再生できます。1曲または1枚のアルバムを再生できます。

システムプレーヤーを使用すると、アプリを閉じても音楽が再生され続けます。

### システムプレーヤーの使用

```swift
Button("Play using iOS system player") {
    Task {
        SystemMusicPlayer.shared.queue = .init(for: [album])
        do {
            try await SystemMusicPlayer.shared.play()
        } catch {
            print(error.localizedDescription)
        }
    }
}
```

`album`変数は、`MusicItem`プロトコルに準拠した他のオブジェクトで変えることができます。

### アプリ内プレーヤーの使用

アプリ内プレーヤーを使えば、より多くのカスタマイズを行うことができます。

```swift
Button("Play using in-app player") {
    Task {
        ApplicationMusicPlayer.shared.queue = .init(for: [album])
        do {
            try await ApplicationMusicPlayer.shared.play()
        } catch {
            print(error.localizedDescription)
        }
    }
}
```

## 他の種類のAPIリクエストの実行

その他のApple Music用APIの中には、MusicKit内では利用できないものがある場合があります。そのような場合は、`MusicDataRequest`を使ってリクエストを行ってください。レスポンスは手動で解析する必要があります。

```swift
let url = URL(string: "https://api.music.apple.com/v1/me/recent/played")!
let request = MusicDataRequest(urlRequest: URLRequest(url: url))
let response = try await request.response()
```

例えば、最近再生した音楽を取得するには、このウェブページを参照してください： [MusicKit - Access user’s recently … | Apple Developer Forums](https://developer.apple.com/forums/thread/696453)

---

https://github.com/mszmagic/MusicKit-Demo


---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: カテゴリー別に整理された、自分の公開記事とニュースレターを表示します。iOSデバイスのカメラアプリを使用して、このコードをスキャンするだけです。
<img width="190" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">

[または、Webで表示](http://mszpro.com/ioskiji/)