UIViewの背景に動画を流す簡単な方法

# iOSで背景に動画を流す簡単な方法

初回起動画面などで背景に動画が流れているアプリってよくありますよね。
そのようにボタンなどのUIの後ろで動画を流す簡単な方法を紹介します。

## 最小実装

```swift
import UIKit
import AVFoundation

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Bundle Resourcesからsample.mp4を読み込んで再生
        let path = Bundle.main.path(forResource: "sample", ofType: "mp4")!
        let player = AVPlayer(url: URL(fileURLWithPath: path))
        player.play()


        // AVPlayer用のLayerを生成
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = view.bounds
        playerLayer.videoGravity = .resizeAspectFill
        playerLayer.zPosition = -1 // ボタン等よりも後ろに表示
        view.layer.insertSublayer(playerLayer, at: 0) // 動画をレイヤーとして追加
    }
}
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/91631/e5848ef1-1783-c570-f94a-c5da1196b666.gif" width="300px" alt="demo">

短い行で実現できましたね。

試しにラベルとボタンを置いてみます。

```swift
let label = UILabel(frame: CGRect(x: 0, y: 100, width: 200, height: 40))
label.text = "Hello!"
label.textColor = .white
label.font = UIFont.boldSystemFont(ofSize: 50)
label.textAlignment = .center
label.center.x = view.center.x
label.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
view.addSubview(label)

let loginButton = UIButton(frame: .init(x: 30, y: view.frame.height - 150, width: view.frame.width - 60, height: 50))
loginButton.setTitle("LOG IN", for: .normal)
loginButton.setTitleColor(.white, for: .normal)
loginButton.layer.borderWidth = 1
loginButton.layer.borderColor = UIColor.white.cgColor
loginButton.layer.cornerRadius = 4
loginButton.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
view.addSubview(loginButton)

let signupButton = UIButton(frame: loginButton.frame)
signupButton.frame.origin.y = loginButton.frame.minY - 60
signupButton.setTitle("SIGN UP", for: .normal)
signupButton.setTitleColor(.white, for: .normal)
signupButton.backgroundColor = UIColor(red: 0, green: 168.0/255, blue: 107.0/255, alpha: 1)
signupButton.layer.cornerRadius = 4
signupButton.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
view.addSubview(signupButton)
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/91631/c4edf6d9-a240-e53c-058f-a8b234c9e1b1.gif" width="300px" alt="demo">

はい、よく見る画面になりましたね。


これだけでも十分ですが、さらによくある以下の実装を追加してみます。

- 動画を少し薄暗くする
- リピート再生する
- アプリをバックグラウンドにした後、フォアグラウンドにしても自動再生される (デフォルトでは停止してしまいます)
- 端末を回転してもサイズが調整される

## 動画を薄暗くする & リピート再生 & アプリ復帰後自動再生 & 端末回転対応

```swift
class ViewController: UIViewController {
    
    private var observers: (player: NSObjectProtocol, 
                            willEnterForeground: NSObjectProtocol, 
                            bounds: NSKeyValueObservation)?

    override func viewDidLoad() {
        super.viewDidLoad()

        let path = Bundle.main.path(forResource: "sample", ofType: "mp4")!
        let player = AVPlayer(url: URL(fileURLWithPath: path))
        player.actionAtItemEnd = .none
        player.play()

        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = view.bounds
        playerLayer.videoGravity = .resizeAspectFill
        playerLayer.zPosition = -2 // 次に追加するoverlayより後ろにする
        view.layer.insertSublayer(playerLayer, at: 0)

        // 動画の上に重ねる半透明の黒いレイヤー
        let dimOverlay = CALayer()
        dimOverlay.frame = view.bounds
        dimOverlay.backgroundColor = UIColor.black.cgColor
        dimOverlay.zPosition = -1
        dimOverlay.opacity = 0.4 // 不透明度
        view.layer.insertSublayer(dimOverlay, at: 0)

        // 最後まで再生したら最初から再生する
        let playerObserver = NotificationCenter.default.addObserver(
            forName: .AVPlayerItemDidPlayToEndTime,
            object: player.currentItem,
            queue: .main) { [weak playerLayer] _ in
                playerLayer?.player?.seek(to: kCMTimeZero)
                playerLayer?.player?.play()
        }
        
        // アプリがバックグラウンドから戻ってきた時に再生する
        let willEnterForegroundObserver = NotificationCenter.default.addObserver(
            forName: .UIApplicationWillEnterForeground,
            object: nil,
            queue: .main) { [weak playerLayer] _ in
                playerLayer?.player?.play()
        }
        
        // 端末が回転した時に動画レイヤーのサイズを調整する
        let boundsObserver = view.layer.observe(\.bounds) { [weak playerLayer, weak dimOverlay] view, _ in
            DispatchQueue.main.async {
                playerLayer?.frame = view.bounds
                dimOverlay?.frame = view.bounds
            }
        }

        observers = (playerObserver, willEnterForegroundObserver, boundsObserver)
    }

    deinit {
        // 画面が破棄された時に監視をやめる
        if let observers = observers {
            NotificationCenter.default.removeObserver(observers.player)
            NotificationCenter.default.removeObserver(observers.willEnterForeground)
            observers.bounds.invalidate()
        }
    }
}
```

<img src="https://qiita-image-store.s3.amazonaws.com/0/91631/52fb14b4-7a45-127e-f2d8-fa8a951d22b1.gif" width= "500px" alt="demo">

※デモ動画ではボタンとラベルのコードも追加しています。

## まとめ
意外と簡単に実現することが出来ました。
細かい制御を入れると少しコード量が増えるので、実際には`UIViewController`内に記述せず、別のクラスに実装するのが良いと思います。

サンプルコードはこちらに置いてあります。
https://github.com/tattn/BackgroundVideoSample

## 関連URL

デモに使用した動画
http://mazwai.com/#/videos/218