iOS14で「バックグラウンド再生」が出来ない不具合への対処


### 2020/09/23：追記
頂いたコメントを元に再調査を実施、新しい対策方法を追記しました。
@imamurhさん、ありがとうございます！

### 2020/10/01：追記
**「iOS14.2 beta2」で検証**したところ、このバージョンでは対策しなくても**BG再生が出来る**ことを確認。
この件、**iOS14側のバグ**であった可能性が高まってきました。
ただ、まだベータ版なので今後もチェックしていきたいと思います。
@a26364177jp, thanks for your comment!


## 概要

現在、リリースしているメディア管理系アプリで行った**iOS14に関する不具合調査とその対策**です。


## 経緯とバグ調査

アプリを使って頂いているユーザーさんから**「iOS14でバックグラウンド再生が出来ない」**

との問い合わせがありました。

早速、手元の実機で調査してみると

**動画ファイルを再生中 → アプリをバックグラウンドへ移行 → 再生が一時停止されてしまう**

という現象を確認出来ました。

これが、**iOS14の仕様（であればアプリ側のバグ）**なのか

**iOS14側の不具合**なのかは今のところ分かりません。

ただ、**音声ファイルの再生時にこの現象は起きない**ので
（バックグラウンドでも再生は継続される）

おそらく、iOS14の新しい仕様の可能性が高いのかなと思います。

ちなみに一時停止してしまった動画は、

コントロールセンターからプレイバックボタンを押す事で、再び再生が可能でした。

(追記：**「iOS14.2 beta2」での実機検証では、BG再生可能な事からiOS14.0のバグだった可能性あり**)


## 対策（追記しました）

バックグラウンドで動画再生させるケースはあまりない？と思われがちですが

実際、弊アプリのユーザーさんでは音楽動画などをBG再生して聴かれるケースも多いです。

症状は動画ファイル再生時のみですが、取り急ぎでも対応が必要です。

### まずAVAudioSessionで色々やってみる

[AVAudioSession](https://developer.apple.com/documentation/avfoundation/avaudiosession)

**AVFoundation Framework**の**AVAudioSession**のカテゴリーを調整してみます。

```swift
AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback, options: [])
```

ここの**mode**と**options**を調整してみます。

```swift
extension AVAudioSession.Mode {
    public static let `default`: AVAudioSession.Mode
    public static let voiceChat: AVAudioSession.Mode
    public static let gameChat: AVAudioSession.Mode
    public static let videoRecording: AVAudioSession.Mode
    public static let measurement: AVAudioSession.Mode
    public static let moviePlayback: AVAudioSession.Mode
    public static let videoChat: AVAudioSession.Mode
    public static let spokenAudio: AVAudioSession.Mode
    public static let voicePrompt: AVAudioSession.Mode
}
```

```swift
    public struct CategoryOptions : OptionSet {
        public init(rawValue: UInt)
        public static var mixWithOthers: AVAudioSession.CategoryOptions { get }
        public static var duckOthers: AVAudioSession.CategoryOptions { get }
        public static var allowBluetooth: AVAudioSession.CategoryOptions { get }
        public static var defaultToSpeaker: AVAudioSession.CategoryOptions { get }
        public static var interruptSpokenAudioAndMixWithOthers: AVAudioSession.CategoryOptions { get }
        public static var allowBluetoothA2DP: AVAudioSession.CategoryOptions { get }
        public static var allowAirPlay: AVAudioSession.CategoryOptions { get }
    }
```

色々と組み合わせてみるが、効果無し。

### 仕方がないので、少し強引な対策で対処

根本的な対策方法については、引き続き調べていくとして

とりあえずの**落としどころ**が欲しかったので、

- まず**AVPlayerItemのstatus**を監視

- システムから再生が停止されてしまった際 → **playbackLikelyToKeepUp**の変更を検知
[isPlaybackLikelyToKeepUp](https://developer.apple.com/documentation/avfoundation/avplayeritem/1390348-isplaybacklikelytokeepup)
- そのタイミングで**バックグラウンド再生中であれば、再び動画を再生**させる。

と言う方法で対処することにしました。

```swift
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?){

	if let item = object as? AVPlayerItem, let keyPath = keyPath {
        if item == self.playerItem {
            switch keyPath {
						// ...略
            case #keyPath(AVPlayerItem.playbackLikelyToKeepUp):
                debugPrint("PlaybackLikelyToKeepUp is changed")             
                // ** BG状態かつ、再生ステータスなのにAVPlayerが停止という状態をチェック
                if isInBackground, isInconsistentPlaybackState {
									play()
                  debugPrint("Background Playback Forced Resuming >> Did Execute")
                }
            default:
                break
            }
        }
	 }

}
```

### 2020/09/23追記： 再生中アイテムのビデオトラックを無効化

こちらのQAをコメントにて指摘頂き、実際に検証してみました。

[Playing media while in the background using AV Foundation on iOS](https://developer.apple.com/library/archive/qa/qa1668/_index.html)

ここで挙げられている２つのうち、AVPlayerLayerを削除する方法は既に実施していましたが

**ビデオトラックをDisableにする方法**は実施していませんでした。

早速試してみると、見事に**バックグラウンドに移行した際も再生が継続**する様になりました。

#### 現在再生しているアイテムのビデオトラックを探して無効化

```swift
func changeVideoTrackState(isEnabled: Bool){
    if let tracks = currentPlayerItem.tracks {
      for playerItemTrack in tracks {
        // Find the video tracks.
        if let assetTrack = playerItemTrack.assetTrack, assetTrack.hasMediaCharacteristic(.visual) {
          // Enable/Disable the track.
          playerItemTrack.isEnabled = isEnabled
        }
      }
  }
}
```

アプリがバックグラウンドへ移行したタイミングで呼び出して、ビデオトラックを無効化します。

```swift
@objc func applicationDidEnterBackground(_ notifiaction: Notification){
    changeVideoTrackState(isEnabled: true)
}
```

アプリに戻ってきた際に再び有効化。元に戻します。

```swift
@objc func applicationDidBecomeActive(_ notifiaction: Notification){
    changeVideoTrackState(isEnabled: true)
}
```

どちらにしても、iOS13まではBG再生出来ていたので、iOS14から何かしらの仕様が変わった可能性があります。

この対策で、再び様子を見ていきたいと思います。