SwiftUIを使って学習がてらタイマーアプリを作ってみよう5

#SwiftUIを使って学習がてらタイマーアプリを作ってみよう　Part5

###前置き　
前回投稿した[Part4](https://qiita.com/scream_episode/items/b899fa41728fc4eed590)の続きです。
今回のゴールとしては
端末にプリインストールされているミュージックアプリで再生できる音楽を選択して、計測終了時に流せるようになるところまでです。
次回は[アプリがバックグランドに行ってもタイマー処理が止まらない](https://qiita.com/scream_episode/items/33586875cf609a884184)ようにする所をやっています


###開発環境
Mac (OS Big Sur version:11.3.1)
Xcode (version:12.5)
Swift (version: 5.4) 
対象IOS（version:14.5以上）
## 今回の作業
### 音を再生させる処理を調べてみた。
簡単に調べてみたところ、方法としては下記の三つが見つかった。
・MPMusicPlayerController
・AVAudioPlayer
・AVPlayer
このうち
MPMusicPlayerControllerはOS 3.0の時から使える物で
他二つはOS 6.0以降に使えるようになった比較的新しいやつ。

それぞれの違いをざっくり上げてみると
MPMusicPlayerController　→

```
ミュージックアプリみたいに端末にDLしてないApple Musicで「マイミュージックに追加」したアイテムも再生できる。
ただし、ホームシェアリングのアイテムを除く。
```
AVAudioPlayer　→

```
再生速度とかループ回数等の設定を指定して再生ができる。
しかし、端末にDLしてあるアイテムしか再生ができない(オンラインで購入済みのアイテムでも端末にDLしてないと使えない)
```
AVPlayer　→

```　
音と画像が再生できる。
assetと言う単位を扱っている。動画のの一部分を複数くっつけてassetとして扱ったりできる
しかし、AVAudioPlayerと同様に端末にDLしてあるアイテムしか再生ができない(オンラインで購入済みのアイテムでも端末にDLしてないと使えない)
```
今回は別に音のみ再生できれば良く、DLされていない音を使えなくとも良いのでAVAudioPlayerを使ってみました。

### どうやってミュージックアプリみたいに選曲をさせるか
調べるとMPMediaPickerControllerを使えばできるっぽいのでこれを試してみた。
*実機じゃないとデバックできないっぽい（少なくともデフォルト状態のエミュレータとかでは出来なかった）


####Step1 権限追加
info.plistで権限要求を定義して起動後に権限を認証しないといけない
#####・手順

1. Xcodeでinfo.plistを開く
1. 項目KeyにあるInformation Property List　を選択
1. 手順２をを行うことで表示される「＋’ボタンをタップ
1. Information Property Listに項目を追加することができるようになるので下記の二つを追加する
    1. Privacy - Media Library Usage Description
    1. Privacy - Music Usage Description


今回は手順４で追加したものだけでOK

####Step2 音データを選択できるようにする
端末内の音楽データ（ミュージックアプリとかで使えるやつ）を取得するのにMPMediaPickerControllerが使えるらしいので実装
＊ここからは実機がないとテストできなくなるので注意

``` MusicPicker.swift
import SwiftUI
import MediaPlayer

struct MusicPicker: UIViewControllerRepresentable {
    @Binding var song: String
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    // View作成
    func makeUIViewController(context: UIViewControllerRepresentableContext<MusicPicker>) ->  MPMediaPickerController {
        let picker = MPMediaPickerController()
        // 複数選択可否設定 true:可(複数) false: 不可(単体)
        picker.allowsPickingMultipleItems = false
        // クラウドアイテムの表示設定
        picker.showsCloudItems = false
        
        // delegate ざっくり言うとイベントが発火された時に呼ばれる処理
        picker.delegate = context.coordinator
        picker.prompt = "test"
        return picker
    }
    
    // View更新
    func updateUIViewController(_ uiViewController: MPMediaPickerController, context: Context) {
        
    }
    
    class Coordinator: NSObject, UINavigationControllerDelegate, MPMediaPickerControllerDelegate{
        var parent: MusicPicker
        init(_ parent: MusicPicker){
            self.parent = parent
        }
        
        // MediaPicker が閉じた時に呼ばれるぽい
        func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
            let selectedSongs = mediaItemCollection.items
            // 選択された曲数チェック()
            if selectedSongs.count > 0 {
                //allowsPickingMultipleItemsで単体指定にしているから必ずIndex：0 にアイテムがある想定
                if selectedSongs[0] != nil {
                    parent.song = selectedSongs[0].title ?? "no title"
                }else{
                    parent.song = "not select Music"
                }
            }
            //　単体指定の場合選択するたびに呼ばれるのでここでpickerを閉じる 
            mediaPicker.dismiss(animated: true)
        }        
    }
    
}

```


#####・手順
1.SwiftUIでUIViewControllerRepresentableを継承した構造体を作成する
> struct MusicPicker: UIViewControllerRepresentable

その際にMediaPlayerをimport しないと　MPMediaPickerControllerとかUIViewControllerRepresentableが使えない
2.MPMediaPickerControllerを実装する
UIViewContorollerをSwiftUIで使う方法とかは[公式のチュートリアル](https://developer.apple.com/tutorials/swiftui/creating-and-combining-views#Use-UIKit-and-SwiftUI-Views-Together)を見てください。

MPMediaPickerControllerのパラメータのざっくりとした説明

|パラメータ  |説明  |
|---|---|
|allowsPickingMultipleItems  |アイテムを複数選択か単体選択か設定する  |
|showsCloudItems  |クラウドアイテムを選択肢として表示するか否か  | 

選択した曲情報は
>func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection)

内の
>　mediaItemCollection.items

に格納されている
3.SettingView.swiftからMusicPicker（）を呼び出す
MusicPickerを呼び出すときはStatusタグをつけた変数の参照渡しをしてやる必要がある。


####Step３ 取得した音情報から再生を行う
AVAudioPlayerを実装して再生を実行させる。
#####・手順

1. MusicPicker.swiftで内の曲のタイトルを入れていた変数をStringからBindingタグを付けたMPMediaItem型（nil許容）に変更する

>@Binding var song: String
↓
@Binding var songInfo: MPMediaItem?

これはTimerModelで音再生処理を行なうために音声データ情報を共有するために行う
2.TimerModelにAVAudioPlayerを実装

``` TimerModel.swift 
              略
    // Playerの作成
    func createAudioPlayer(){
        if let url: URL = songInfo?.assetURL {
            do{
                avAudioPlayer = try AVAudioPlayer(contentsOf: url)
            } catch{
                // エラーが発生してプレイヤーが生成失敗時
                avAudioPlayer = nil
            }
        } else {
            // URLが存在していない
            avAudioPlayer = nil
        }
    }
    
    
    // 音楽の再生
    func playSound(){
        if let player = avAudioPlayer {
            // 音楽再生中なら止める
            if player.isPlaying {
                player.stop()
            }
            player.play()
        }
    }
    
    // 音楽の停止
    func stopSound(){
        if let player = avAudioPlayer {
            // 音楽再生中なら止める
            if player.isPlaying {
                player.stop()
            }
        }
    }
    
```
AVAudioPlayerはURLを使ってインスタンスを生成する必要がある。
ざっくり調べた限りでは生成後にURLをセットする関数が見つかんなかったので（getterはあった）

> avAudioPlayer = try AVAudioPlayer(contentsOf: url)

[参考元](https://nackpan.net/blog/2015/09/19/ios-swift-avaudioplayer/)だとNSURLを使っているが自分の環境だとNSURLではなくてURLが必要となっていた。

3.任意の画面で初期化処理と再生停止処理の呼び出しを行う



---
###引用・参考元
・[[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(MPMusicPlayerController使用)](https://nackpan.net/blog/2015/09/14/ios-swift-musicplayercontroller/)
・[[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(AVAudioPlayer使用)](https://nackpan.net/blog/2015/09/19/ios-swift-avaudioplayer/)
・[Using MPMediaItems with AVAudioPlayer](https://stackoverflow.com/questions/5222464/using-mpmediaitems-with-avaudioplayer)
・[SwiftUIでMPMediaPickerController](https://dishware.sakura.ne.jp/swift/archives/469)

###シリーズ：「SwiftUIの学習でタイマーアプリを作ってみよう」のリンク
[part1:プロジェクトの作成とGitHubに登録](https://qiita.com/scream_episode/items/f8bfcba4c65c3b758db8)
[part2: Timerを利用したカウント処理の実装](https://qiita.com/scream_episode/items/8d42198698fcf7513b54)
[part3:計測画面でのプログレスバーの実装](https://qiita.com/scream_episode/items/b899fa41728fc4eed590)
[part4:スピナー実装](https://qiita.com/scream_episode/items/7bb249ee55140424829b)
part5:ライブラリから音楽を選択して再生　←今ここ
[part6:タイマー処理のバックグランド対応](https://qiita.com/scream_episode/items/33586875cf609a884184)
[part7:音楽のバックグランド再生対応](https://qiita.com/scream_episode/items/abb2168fe66b90836443)
[part8:PageViewを作成](https://qiita.com/scream_episode/items/fb928f114de7a857b480)
[part9:PageViewのインジゲータを作成](https://qiita.com/scream_episode/items/7950d45a17f8f53b4094)