0
votes

I'm trying to record and save video using AVFoundation Framework with both front and rear camera. I'm able to start session but unable to save video recording in document directory. I check movieOutput.isRecording it gives false every time. Hence the delegate output method is also not called due to this. Even Start delegate is not called on start recording.

import UIKit
import Foundation
import AVKit
import AVFoundation

class AppVideoRecorder: NSObject {
    
    private var session = AVCaptureSession()
    private var movieOutput = AVCaptureMovieFileOutput()
    private var camera: AVCaptureDevice?
    private var activeInput: AVCaptureDeviceInput?
    private var previewLayer = AVCaptureVideoPreviewLayer()
    
    private var renderView: UIView!
    var isFrontCamera: Bool = false
    
    init(for view: UIView) {
        self.renderView = view
    }
    
    deinit {
        print("Called")
    }
    
    func setupSession() {
        
        self.session.sessionPreset = .high
       
        // Setup Camera
        self.camera = AVCaptureDevice.default(
            .builtInWideAngleCamera,
            for: .video,
            position: self.isFrontCamera ? .front : .back
        )
                
        if let camera = self.camera {
            do {
                let input = try AVCaptureDeviceInput(device: camera)
                if self.session.canAddInput(input) {
                    self.session.addInput(input)
                    self.activeInput = input
                }
            } catch {
                print(error)
            }
        }
        
        // Setup Microphone
        if let microphone = AVCaptureDevice.default(for: .audio) {
            do {
                let micInput = try AVCaptureDeviceInput(device: microphone)
                if self.session.canAddInput(micInput) {
                    self.session.addInput(micInput)
                }
            } catch {
                print(error)
            }
        }
        
        // Movie output
        if self.session.canAddOutput(self.movieOutput) {
            self.session.addOutput(self.movieOutput)
        }
    }
    
    func setupPreview() {
        // Configure previewLayer
        self.previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
        self.previewLayer.frame = self.renderView.bounds
        self.previewLayer.videoGravity = .resizeAspectFill
        self.renderView.layer.addSublayer(self.previewLayer)
    }
    
    func startSession() {
        if self.session.isRunning { return }
        DispatchQueue.main.async {
            self.session.startRunning()
        }
    }
    
    func stopSession() {
        if self.session.isRunning {
            DispatchQueue.main.async {
                self.session.stopRunning()
            }
        }
    }
    
    func removeInput() {
        guard let input = self.activeInput else { return }
        self.session.removeInput(input)
    }
 
    func isCameraOn(completion: @escaping (Bool) -> Void) {
        if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
            completion(true)
        } else {
            AVCaptureDevice.requestAccess(for: .video,
                                          completionHandler: { (granted) in
                completion(granted)
            })
        }
    }
    
    func toggleCamera() {
        
        self.session.beginConfiguration()
                
        for input in self.session.inputs {
            if let inputObj = input as? AVCaptureDeviceInput {
                self.session.removeInput(inputObj)
            }
        }
        
        self.camera = AVCaptureDevice.default(
            .builtInWideAngleCamera,
            for: .video,
            position: self.isFrontCamera ? .front : .back
        )

        if let camera = self.camera {
            do {
                let input = try AVCaptureDeviceInput(device: camera)
                if self.session.canAddInput(input) {
                    self.session.addInput(input)
                    self.activeInput = input
                }
            } catch {
                print(error)
            }
        }

        self.session.commitConfiguration()
    }
    
}

extension AppVideoRecorder: AVCaptureFileOutputRecordingDelegate {
    
    private var currentVideoOrientation: AVCaptureVideoOrientation {
         var orientation: AVCaptureVideoOrientation

         switch UIDevice.current.orientation {
         case .portrait:
             orientation = AVCaptureVideoOrientation.portrait
         case .landscapeRight:
             orientation = AVCaptureVideoOrientation.landscapeLeft
         case .portraitUpsideDown:
             orientation = AVCaptureVideoOrientation.portraitUpsideDown
         default:
             orientation = AVCaptureVideoOrientation.landscapeRight
         }

         return orientation
     }
    
    func recordVideo() {
        if self.movieOutput.isRecording { // FALSE EVERY TIME
           self.stopRecording()
        } else {
            self.startRecording()
        }
    }
    
    private func startRecording() {
        
        guard let connection = self.movieOutput.connection(with: .video),
            let device = self.activeInput?.device else { return }
        // handle return error

        if connection.isVideoOrientationSupported {
            connection.videoOrientation = self.currentVideoOrientation
        }
        
        if connection.isVideoStabilizationSupported {
            connection.preferredVideoStabilizationMode = .auto
        }

        if device.isSmoothAutoFocusSupported {
            do {
                try device.lockForConfiguration()
                device.isSmoothAutoFocusEnabled = false
                device.unlockForConfiguration()
            } catch {
                print("Error setting configuration: \(error)")
            }
            
        }
        
        let paths = FileManager.default.urls(for: .documentDirectory,
                                             in: .userDomainMask)
        guard let path = paths.first else { return }
        let fileUrl = path.appendingPathComponent("celeb_video.mp4")
        try? FileManager.default.removeItem(at: fileUrl)
        
        self.movieOutput.startRecording(to: fileUrl, recordingDelegate: self)
    }
    
    private func stopRecording() {
        self.movieOutput.stopRecording()
    }
    
    func fileOutput(_ output: AVCaptureFileOutput,
                    didFinishRecordingTo outputFileURL: URL,
                    from connections: [AVCaptureConnection],
                    error: Error?) {
        print("DELEGATE CALL BACK")
        if let error = error {
            //do something
            print(error)
        } else {
            //do something
            print(outputFileURL.path)
            //            UISaveVideoAtPathToSavedPhotosAlbum(outputFileURL.path, nil, nil, nil)
        }
    }

    func fileOutput(_ output: AVCaptureFileOutput,
                didStartRecordingTo fileURL: URL,
                from connections: [AVCaptureConnection]) {
    print("didStartRecordingTo CALL BACK:", fileURL.path)
}

}

Here is my calling code in view controller. recordingView is UIView

private lazy var recorder: AppVideoRecorder = {
    return AppVideoRecorder(for: self.recordingView)
}()

@IBAction func recordingAction(_ sender: UIButton) {
    sender.isSelected.toggle()
    if sender.isSelected {
        self.recorder.setupSession()
        self.recorder.setupPreview()
        self.recorder.startSession()
        self.recorder.recordVideo()
    } else {
        self.recorder.recordVideo()
        self.recorder.removeInput()
        self.recorder.stopSession()
    }
}

@IBAction func swapCameraAction(_ sender: UIButton) {
    sender.isSelected.toggle()
    self.recorder.isFrontCamera = sender.isSelected
    self.recorder.toggleCamera()
}

Please let me know what I missed.

1
One thing that seems to be missing is setting self as the delegate of the movie output.vzsg
@vzsg: I have already added in startRecording function, recording delegate. Let me know any thing else delegate i need to add.Abhishek Thapliyal
Oh, that's right. Sorry about the noise then. I presume you've already tried stepping through the code to see that none of the guards trip?vzsg
Yes i checked via break point and logs as well, guard let conditions are working fineAbhishek Thapliyal
Have you added the entries on the plist for accessing the camera and the photo library?MLBDG

1 Answers

0
votes

As from the link Starting video recording immediately with AVCaptureMovieFileOutput

I have added notifications, now it is working as it takes time to start.

private func setupNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(sessionDidStartRunning(_:)),
                                           name: .AVCaptureSessionDidStartRunning,
                                           object: nil)
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(sessionDidStopRunning(_:)),
                                           name: .AVCaptureSessionDidStopRunning,
                                           object: nil)
}

@objc
private func sessionDidStartRunning(_ notification: NSNotification) {
    self.startRecording()
}

@objc
private func sessionDidStopRunning(_ notification: NSNotification) {
    
}