Xcode11 での動画再生と Playback Controls について

この記事は [ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech) 8日目の記事になります。

昨日の記事は @calorie さんによる「[Firebaseを使ったTwitter認証をクライアント(Swift)からサーバ(Rails)までまるっと実装する](https://qiita.com/calorie/items/6af9c1642ff344a5cf74)」でした。Firebase を使った認証をクライアントからサーバーまで通して解説してあり、初心者でも非常に分かりやすいと思いますので、ぜひご覧ください。

#はじめに
アプリで何かの動画を再生させよう！という話になった時、こんな質問を受けます。

デザイナー：「シークバーって表示できる？」
　　　　私：「システム標準のやつならすぐできますよ」
デザイナー：「標準のやつってどんなの？」
　　　　私：「えーと・・・」

と言いながら、毎回スクリーンショットを探しても、最新のものがなかなか見つかりません。結局サンプルを作って、スクリーンショットを撮ったりしています。
そこで、この記事では、Xcode11 での動画の再生方法をまとめながら、Playback Controls のスクリーンショットを見ていきたいと思います。

確認環境は、Xcode 11.2.1 の iPhone 11 Pro 13.2.2 のシュミレーターを使います。

#動画の再生
動画再生用の UI（以降は便宜上、Playback Controls と呼びます）の話の前に、まず動画の再生方法について説明します。サードパーティのライブラリなどを使用せず実装する方法としては、次の３つがあります（2019年12月現在）。

1. AVPlayer を使う
2. AVPlayerViewController を使う
3. WKWebView で video タグを使う

Playback Controls が提供されているのは、２と３になりますが、１はもっともシンプルな動画再生や、フルカスタムで Playback Controls を自作する際の基礎となるので、ここから説明します。

# AVPlayer を使う

AVFoundation の AVPlayer を使って動画を再生します。
最小実装としては、こんな感じになると思います。

```swift
override func viewDidLoad() {
    super.viewDidLoad()

    guard let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8") else {
        return
    }

    let player = AVPlayer(url: url)

    let playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = self.view.bounds
    self.view.layer.addSublayer(playerLayer)

    player.play()
}
```

１つ１つ丁寧に説明すると、まず最初に動画の URL を作成します。

```swift
guard let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8") else {
    return
}
```
この URL は Apple の「[Creating a Basic Video Player](https://developer.apple.com/documentation/avfoundation/media_assets_playback_and_editing/creating_a_basic_video_player_ios_and_tvos)」で指定している HTTP Live Streaming (HLS) のインデックスファイルになります。これはサーバーやローカルの MP4 ファイルにしても動作します。
次にこの URL をもとに AVPlayer を作成します。

```swift
let player = AVPlayer(url: url)
```
そして、そこから実際に動画を再生させるレイヤーを作成し、サイズを調整して、View ツリーに追加します。

```swift
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer.addSublayer(playerLayer)
```
そして、最後に AVPlayer の play メソッドを呼ぶだけです。

```swift
player.play()
```
非常に簡単ですね！
実行結果のスクリーンショットがこちらになります。

|<img width="375" alt="AVPlayer sample - iPhone 11 Pro" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214356/ddc20285-5e26-e869-3f93-78cad0959481.png">|
|:-:|

サンプル動画のせいでちょっと分かりにくいかもしれませんが、Playback Controls は一切表示されておらず、動画のみのシンプルな UI になります。

# AVPlayerViewController を使う
次にいよいよ Playback Controls が表示できる AVKit の AVPlayerViewController を使います。
最小実装としては、こんな感じになると思います。

```swift
override func viewDidLoad() {
    super.viewDidLoad()

    guard let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8") else {
        return
    }

    let player = AVPlayer(url: url)

    let controller = AVPlayerViewController()
    controller.player = player

    controller.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 300)
    self.addChild(controller)
    self.view.addSubview(controller.view)

    player.play()
}
```

AVPlayer との違いは、AVPlayerViewController を作成し、作成した AVPlayer をセットするところです。

```swift
let controller = AVPlayerViewController()
controller.player = player
```

全画面表示にならないように、高さを調整し、ViewController と View ツリーに追加します。

```swift
controller.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 300)
self.addChild(controller)
self.view.addSubview(controller.view)
```
実行結果のスクリーンショットがこちらになります。
左が最初に表示されて、タップするとこのように Playback Controls が表示されます。左上にある最大化ボタンを押すと右の画面になり、Playback Controls がより大きくなり UI が変わります。

|<img width="375" alt="AVPlayerViewController - iPhone 11 Pro.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214356/1caf626c-8024-9afd-35ce-b6512f571367.png">|<img width="375" alt="AVPlayerViewController Max - iPhone 11 Pro.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214356/04534857-36d7-d54c-d353-82e8912871b1.png">|
|:-:|:-:|




# WKWebView で video タグを使う
これは、おまけになりますが、WKWebView と video タグでも動画の再生と Playback Controls の表示ができます。コードはこんな感じになります。

```swift
override func viewDidLoad() {
    super.viewDidLoad()

    guard let url = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8") else {
        return
    }

    let config = WKWebViewConfiguration()
    config.allowsInlineMediaPlayback = true

    let webView = WKWebView(frame: self.view.bounds, configuration: config)
    self.view.addSubview(webView)

    let html = String(format:"<video id='video' width='950' height='700' src='%@' controls autoplay playsinline webkit-playsinline></video>", url.absoluteString)
    webView.loadHTMLString(html, baseURL: nil)
}
```
video タグはくせがあるようですが、調べきっていないので、ここでの説明は割愛させていただきます。
実行するとこのようになります。

|<img width="375" alt="WKWebView - iPhone 11 Pro.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214356/f7fa105c-f9e9-43a1-d864-2b9fa8e16f9f.png">|<img width="375" alt="WKWebView Max - iPhone 11 Pro.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214356/2889667c-4ec3-56a5-017c-726e0528e983.png">|
|:-:|:-:|
右の最大化した時の Playback Controls は AVPlayerViewController と同じですが、左の Playback Controls は注意深く見ると少し違います。字幕の設定オプションが無く、その代わりに 15 秒の巻き戻しと早く送りがあります。

# まとめ
Xcode11 での動画の再生方法をまとめながら、Playback Controls を確認しました。動画再生の UI について検討するときにこの記事が少しでも役に立てば幸いです。
明日は、@sesta さんが公開予定です。そちらもぜひご覧下さい。