MPRemoteCommandCenterでオーディオファイル再生時のロック画面とコントロールセンターでの操作に対応する

## はじめに

これはiOSでサーバーからダウンロードした音声ファイルをロック画面で再生するまでの設定を行った際の備忘録です。

当初はiOS9.2のシュミレーターを利用して検証していましたが、コントロールセンターでボタンが表示されない等の現象が見られたので実機を想定しています。

## 環境

* Xcode7.2
* iOS9系 実機

## やったこと

### **Background Audio&AirPlayのチェックボックスをONに**

Targets名 => Capabilities => Background Modes => Audio&AirPlay

![](https://satoshin2071.github.io/content/images/2016/03/----------2016-03-11-23-02-48.png)

### **音声ファイルのパーミッションの設定**

今回はAVAudioPlayerを利用して楽曲再生を行いました。

ダウンロードしてストレージに保存した音声ファイルをそのままロック画面で再生しようとすると、パーミッションの関係で`AVAudioPlayer play error and the error is Code=-54` エラーが発生します。

`NSFileManager.defaultManager.setAttributes`で保存先のパーミッションを`NSFileProtectionNone`に変更し上記のエラーに対応しました。

* [NSFileManager Class Reference](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/index.html#//apple_ref/doc/constant_group/File_Protection_Values)
* [NSOSStatusErrorDomain Code=-54 for AVAudioPlayer](http://stackoverflow.com/questions/34782100/nsosstatuserrordomain-code-54-for-avaudioplayer)



```Swift
	// 保存先ディレクトリパス取得
	func exportDirectoryPath() -> String {
	    guard let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first else {
	        fatalError()
	    }
	    return path + "/" + "Clips"
	}

...省略

	// 保存先ディレクトリを作成
    let dirPath = DownloadFilePathAgent.exportDirectoryPath() + "/\(self.task.uuid)"
    let fileManager:NSFileManager = NSFileManager.defaultManager()
    do {
        if fileManager.fileExistsAtPath(dirPath) {
            try fileManager.removeItemAtPath(dirPath)
        }
        try fileManager.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil)
        // ロック時にも再生できるようにパーミッション変更
        try fileManager.setAttributes([NSFileProtectionKey: NSFileProtectionNone], ofItemAtPath: dirPath)
    }
    catch let error as NSError {
        //エラー対応
    }

...省略

    // ファイルダウンロード完了時にもパーミッション変更
    try! NSFileManager.defaultManager().setAttributes([NSFileProtectionKey: NSFileProtectionNone], ofItemAtPath: filePath)
```

### **AVAudioPlayerとAVAudioSessionの設定**

以下を再生や停止を担当するクラスに実装していきます。

```Swift
   // initあたりで
   UIApplication.sharedApplication().beginReceivingRemoteControlEvents()

   // AVAudioPlayer#play()実行前に呼び出して即座に再生開始するように
   audioPlayer?.prepareToPlay()
```

ロック画面でも再生しますよー、の設定です。

プロジェクト設定からBackground Audio&AirPlayをONにします。

Project Navigator -> Project -> target ->  Capabilities -> Background Modes 

![スクリーンショット 2016-04-22 16.08.37.png](https://qiita-image-store.s3.amazonaws.com/0/6691/7f488893-cca0-00e3-2185-cb38e9763c9b.png)


AVAudioSession の設定

```Swift
	session = AVAudioSession.sharedInstance()

	//ロック時も再生のカテゴリを指定
	do {
	    try session?.setCategory(AVAudioSessionCategoryPlayback)
	}
	catch let error as NSError {
	    print(error.description)
	}

	do {
	    //オーディオセッションを有効化
	    try session?.setActive(true)
	}
	catch let error as NSError {
	    print(error.description)
	}
```

### **再生ボタン等の表示設定**

iOS 7.1以降は`remoteControlReceivedWithEvent`をオーバライドしてロック画面のハンドリングに対応する方法でなく`MPRemoteCommandCenter`を利用するように推奨されています。

[MPRemoteCommand Class Reference](https://developer.apple.com/library/ios/documentation/MediaPlayer/Reference/MPRemoteCommand_Ref/index.html#//apple_ref/occ/cl/MPRemoteCommand)

上記のAVAudioSessionを実装したクラスに追記していきます。

表示するボタンと対応する処理の設定

```Swift
    let commandCenter = MPRemoteCommandCenter.sharedCommandCenter()
    commandCenter.playCommand.addTarget(self, action: "play")
    commandCenter.playCommand.enabled = true
    commandCenter.pauseCommand.addTarget(self, action: "pause")
    commandCenter.pauseCommand.enabled = true
    commandCenter.skipForwardCommand.addTarget(self, action: "skipForward")
    commandCenter.skipForwardCommand.enabled = true
    commandCenter.skipBackwardCommand.addTarget(self, action: "skipBackward")
    commandCenter.skipBackwardCommand.enabled = true
```

## 仕上がり

![](https://satoshin2071.github.io/content/images/2016/03/IMG_4705-1.PNG)

再生している楽曲情報の設定

```Swift
    MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = [
        MPMediaItemPropertyTitle: "タイトル",
        MPMediaItemPropertyArtist : "アーティスト名",
        MPNowPlayingInfoPropertyPlaybackRate : NSNumber(float: 1.0), //再生レート
        MPMediaItemPropertyPlaybackDuration : NSNumber(double: self.duration) //シークバー
    ]
```

### **ついでに再生中の電話割り込み/復帰対応**

必須機能かと思いますのでついでに実装しておきましょう。

AVAudioPlayerDelegateに準拠しているなら以下の２つのデリゲートメソッドで割り込みに対応できます。

```swift

     // 割り込み開始時にプレーヤーを制御するデリゲートメソッド
     func audioPlayerBeginInterruption(player: AVAudioPlayer) {
         // プレーヤー/オーディオセッションはすでに一時停止状態
     }
     
     // 割り込み終了時にプレーヤーを制御するデリゲートメソッド  
     func audioPlayerEndInterruption(player: AVAudioPlayer) {
         // 復帰処理
     }
```
上記以外に`AVAudioSessionInterruptionNotification`を利用する方法がありました。

```swift

    // 通知登録
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "sessionDidInterrupt", name: AVAudioSessionInterruptionNotification, object: nil)

    /**
     電話が鳴って割り込まれた / 電話終わった 際のハンドリング
     */
    func sessionDidInterrupt(notification:NSNotification) {
        
        guard let typeNumber = notification.userInfo?[AVAudioSessionInterruptionTypeKey],
        let type = AVAudioSessionInterruptionType(rawValue: typeNumber.unsignedLongValue)
            else {
                return
        }
        
        switch type {
        case .Began:
            dispatch_async(dispatch_get_main_queue(), { [weak self] in
                self?.pause()
            })
        case .Ended:
            dispatch_async(dispatch_get_main_queue(), { [weak self] in
                self?.play()
            })
        }
    }

```



## 備考

デフォルトの「ミュージック」のアプリはロック画面のシークバーの把手で再生をコントロールできますがリファレンス等を確認してもその機能は提供されていない模様です。 

MixCloudやSoundCloud等のアプリにもなかったので多分そうなのでしょう。

## 参考
* [iOS6以降のAVAudioSessionのdelegate設定方法](http://qiita.com/naoty_k/items/10164a4239c18830a71a)
* [かゆいところに手が届きそうなAVFoundationとMediaPlayerによる音楽再生アプリ作成メモ](http://qiita.com/yimajo/items/c30c4d5f5eab06172028)
* [SimpleLibraryMusicPlayer](https://github.com/RIwai/SimpleLibraryMusicPlayer/blob/d325e6e713113170359e31af87e37135decdc13d/SimpleLibraryMusicPlayer/LocalMusicPlayer.swift)
* [Programming-iOS-Book-Examples](https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/bk2ch14p643ducking)
* [AudioSessionProgrammingGuide](https://developer.apple.com/jp/documentation/AudioSessionProgrammingGuide.pdf)