Swift4で動画を撮影・保存する

swift4で動画を撮影し、保存する手順を説明します。

<img src="https://qiita-image-store.s3.amazonaws.com/0/237025/e75169ce-1ac3-d8bc-bb57-3a6abc6f284e.jpeg" width=30%>

## 手順
1. frameworkの追加
2. AVCaptureSession, AVCaptureDeviceの設定
3. プレビューの生成
4. 録画開始、停止ボタンを追加
5. 動画を保存

## frameworkの追加
AVFoundationとPhotosをimportします。

## AVCaptureSession, AVCaptureDeviceの設定
はじめに入力と出力を設定し、その情報をAVCaptureSessionに渡します。

```swift
let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)
let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)

do {
    if videoDevice == nil || audioDevice == nil {
        throw NSError(domain: "device error", code: -1, userInfo: nil)
    }
    let captureSession = AVCaptureSession()

    // video inputを capture sessionに追加
    let videoInput = try AVCaptureDeviceInput(device: videoDevice!)
    captureSession.addInput(videoInput)

    // audio inputを capture sessionに追加
    let audioInput = try AVCaptureDeviceInput(device: audioDevice!)
    captureSession.addInput(audioInput)
            
    // 録画時間の上限を設けたい時は maxRecordedDurationを設定する
    fileOutput.maxRecordedDuration = CMTimeMake(30, 1)

    // 出力をsessionに追加
    captureSession.addOutput(fileOutput)
            
} catch {
    // エラー処理
}
```

## プレビューの生成
AVCaptureVideoPreviewLayerを使い、画面全体にプレビューを表示します。

```swift
// プレビュー
let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoLayer.frame = self.view.bounds
videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.view.layer.addSublayer(videoLayer)

captureSession.startRunning()
```

## 録画ボタンを追加
```swift
func setUpButton() {
    recordButton = UIButton(frame: CGRect(x: 0,y: 0,width: 120,height: 50))
    recordButton.backgroundColor = UIColor.gray
    recordButton.layer.masksToBounds = true
    recordButton.setTitle("録画開始", for: UIControlState.normal)
    recordButton.layer.cornerRadius = 20.0
    recordButton.layer.position = CGPoint(x: self.view.bounds.width/2, y:self.view.bounds.height-50)
    recordButton.addTarget(self, action: #selector(RecordViewController.onClickRecordButton(sender:)), for: .touchUpInside)
       
    self.view.addSubview(recordButton)
}
    
@objc func onClickRecordButton(sender: UIButton) {
    if !isRecording {
        // 録画開始
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let documentsDirectory = paths[0] as String
        let filePath : String? = "\(documentsDirectory)/temp.mp4"
        let fileURL : NSURL = NSURL(fileURLWithPath: filePath!)
        fileOutput.startRecording(to: fileURL as URL, recordingDelegate: self)
            
        isRecording = true
        changeButtonColor(target: recordButton, color: UIColor.red)
        recordButton.setTitle("録画中", for: .normal)
    } else {
        // 録画終了
        fileOutput.stopRecording()
            
        isRecording = false
        changeButtonColor(target: recordButton, color: UIColor.gray)
        recordButton.setTitle("録画開始", for: .normal)
    }
}
    
func changeButtonColor(target: UIButton, color: UIColor) {
    target.backgroundColor = color
}

```
## 動画を保存
録画が停止されるとこのメソッドが呼び出されるので、この中で動画を保存する処理を行います。

```swift
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
    // ライブラリへ保存
    PHPhotoLibrary.shared().performChanges({
        PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL)
    }) { completed, error in
        if completed {
            print("Video is saved!")
        }
    }
}

```

## ソースコード
```swift
import UIKit
import AVFoundation
import Photos

class RecordViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
    let fileOutput = AVCaptureMovieFileOutput()
    
    var recordButton: UIButton!
    var isRecording = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setUpPreview()
    }

    func setUpPreview() {
        let videoDevice = AVCaptureDevice.default(for: AVMediaType.video)
        let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)

        do {
            if self.videoDevice == nil || self.audioDevice == nil {
                throw NSError(domain: "device error", code: -1, userInfo: nil)
            }
            let captureSession = AVCaptureSession()

            // video inputを capture sessionに追加
            let videoInput = try AVCaptureDeviceInput(device: self.videoDevice!)
            captureSession.addInput(videoInput)

            // audio inputを capture sessionに追加
            let audioInput = try AVCaptureDeviceInput(device: self.audioDevice!)
            captureSession.addInput(audioInput)
            
            // max 30sec
            self.fileOutput.maxRecordedDuration = CMTimeMake(30, 1)
            captureSession.addOutput(fileOutput)
            
            // プレビュー
            let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
            videoLayer.frame = self.view.bounds
            videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
            self.view.layer.addSublayer(videoLayer)
            
            captureSession.startRunning()
            
            setUpButton()
        } catch {
            // エラー処理
        }
    }
    
    func setUpButton() {
        recordButton = UIButton(frame: CGRect(x: 0,y: 0,width: 120,height: 50))
        recordButton.backgroundColor = UIColor.gray
        recordButton.layer.masksToBounds = true
        recordButton.setTitle("録画開始", for: UIControlState.normal)
        recordButton.layer.cornerRadius = 20.0
        recordButton.layer.position = CGPoint(x: self.view.bounds.width/2, y:self.view.bounds.height-50)
        recordButton.addTarget(self, action: #selector(RecordViewController.onClickRecordButton(sender:)), for: .touchUpInside)
        
        self.view.addSubview(recordButton)
    }
    
    @objc func onClickRecordButton(sender: UIButton) {
        if !isRecording {
            // 録画開始
            let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
            let documentsDirectory = paths[0] as String
            let filePath : String? = "\(documentsDirectory)/temp.mp4"
            let fileURL : NSURL = NSURL(fileURLWithPath: filePath!)
            fileOutput.startRecording(to: fileURL as URL, recordingDelegate: self)
            
            isRecording = true
            changeButtonColor(target: recordButton, color: UIColor.red)
            recordButton.setTitle("録画中", for: .normal)
        } else {
            // 録画終了
            fileOutput.stopRecording()
            
            isRecording = false
            changeButtonColor(target: recordButton, color: UIColor.gray)
            recordButton.setTitle("録画開始", for: .normal)
        }
    }
    
    func changeButtonColor(target: UIButton, color: UIColor) {
        target.backgroundColor = color
    }
    
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        // ライブラリへ保存
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL)
        }) { completed, error in
            if completed {
                print("Video is saved!")
            }
        }
    }
}

```