Last active
July 13, 2023 14:43
-
-
Save dneprDroid/3beda7dac207785ed176911348f68441 to your computer and use it in GitHub Desktop.
VideoPreviewView - video capture and preview (swift 3)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// swift 3 | |
import UIKit | |
import AVFoundation | |
class VideoPreviewView: UIView { | |
private var videoOutput: AVCaptureMovieFileOutput? | |
private var captureSession: AVCaptureSession? | |
private var previewLayer: AVCaptureVideoPreviewLayer? | |
var flashMode: AVCaptureTorchMode = .off { | |
willSet(mode) { | |
setupFlash(flashMode: mode) | |
} | |
} | |
weak var delegate: VideoPreviewViewDelegate? | |
convenience init(superView: UIView) { | |
self.init(frame: superView.bounds) | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
setupPreview() | |
} | |
required init(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
func setupPreview() { | |
self.clipsToBounds = true | |
self.backgroundColor = UIColor.black | |
checkVideoCameraPermission(onPermissionGranted: { | |
self.videoOutput = AVCaptureMovieFileOutput() | |
let captureSession = AVCaptureSession() | |
if let deviceInput = self.cameraDeviceInput(type: .back) { | |
captureSession.beginConfiguration() | |
captureSession.addInput(deviceInput) | |
captureSession.sessionPreset = AVCaptureSessionPresetHigh | |
captureSession.addOutput(self.videoOutput) | |
captureSession.commitConfiguration() | |
captureSession.startRunning() | |
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) | |
if let previewLayer = previewLayer { | |
previewLayer.bounds = self.bounds | |
previewLayer.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY) | |
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill | |
let cameraPreview = UIView() | |
cameraPreview.frame = self.bounds | |
cameraPreview.layer.addSublayer(previewLayer) | |
self.addSubview(cameraPreview) | |
self.captureSession = captureSession | |
self.previewLayer = previewLayer | |
} | |
} | |
}) | |
} | |
override func didMoveToWindow() { | |
super.didMoveToWindow() | |
captureSession?.startRunning() | |
} | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
if let previewLayer = previewLayer { | |
previewLayer.frame = self.bounds | |
if previewLayer.connection.isVideoOrientationSupported { | |
previewLayer.connection.videoOrientation = getAVCaptureVideoOrientation(UIDevice.current.orientation) | |
} | |
} | |
} | |
private func getAVCaptureVideoOrientation( _ orientation: UIDeviceOrientation) -> AVCaptureVideoOrientation { | |
switch orientation { | |
case .portrait: | |
return .portrait | |
case .portraitUpsideDown: | |
return .portraitUpsideDown | |
case .landscapeLeft: | |
return .landscapeRight | |
case .landscapeRight: | |
return .landscapeLeft | |
default: | |
return .portrait | |
} | |
} | |
private func setupFlash(flashMode: AVCaptureTorchMode) { | |
if let avDevice = (captureSession?.inputs.first as? AVCaptureDeviceInput)?.device { | |
if avDevice.hasTorch { | |
do { | |
try avDevice.lockForConfiguration() | |
} catch { | |
print("camera flash setup error") | |
} | |
if avDevice.torchMode != flashMode { | |
avDevice.torchMode = flashMode | |
} | |
avDevice.unlockForConfiguration() | |
} | |
} | |
} | |
func isBackCamera() -> Bool { | |
return inputCameraType() == .back | |
} | |
func isFrontCamera() -> Bool { | |
return inputCameraType() == .front | |
} | |
func toggleCameraType() { | |
guard let inputCameraType = inputCameraType() else { | |
return | |
} | |
if let currentInput = captureSession?.inputs.first as? AVCaptureInput? { | |
captureSession?.removeInput(currentInput) | |
} | |
switch inputCameraType { | |
case .back: | |
self.captureSession?.addInput(cameraDeviceInput(type: .front)) | |
break | |
case .front: | |
self.captureSession?.addInput(cameraDeviceInput(type: .back)) | |
break | |
default: | |
break | |
} | |
} | |
func releasePreview() { | |
if let videoOutput = videoOutput, videoOutput.isRecording { | |
videoOutput.stopRecording() | |
} | |
captureSession?.stopRunning() | |
captureSession?.removeOutput(videoOutput) | |
self.removeFromSuperview() | |
} | |
private func inputCameraType() -> AVCaptureDevicePosition? { | |
return (captureSession?.inputs.first as? AVCaptureDeviceInput)?.device.position | |
} | |
func checkVideoCameraPermission(onPermissionGranted callback: @escaping () -> Void) { | |
if AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) == .authorized { | |
callback() | |
} else { | |
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (granted :Bool) -> Void in | |
if granted { | |
DispatchQueue.main.async { | |
callback() | |
} | |
} else { | |
print("camera permission error...") | |
} | |
}); | |
} | |
} | |
private func cameraDeviceInput(type: AVCaptureDevicePosition) -> AVCaptureDeviceInput? { | |
let deviceTypes = [ AVCaptureDeviceType.builtInDualCamera, AVCaptureDeviceType.builtInWideAngleCamera ] | |
if let devices = AVCaptureDeviceDiscoverySession(deviceTypes: deviceTypes, mediaType: AVMediaTypeVideo, position: type).devices { | |
for device in devices where device.position == type { | |
return try? AVCaptureDeviceInput(device: device) | |
} | |
} | |
return nil | |
} | |
private func createTempFileURL() -> URL { | |
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, | |
FileManager.SearchPathDomainMask.userDomainMask, true).last | |
let pathURL = NSURL.fileURL(withPath: path!) | |
let fileURL = pathURL.appendingPathComponent("movie-\(NSDate.timeIntervalSinceReferenceDate).mov") | |
print(" video url: \(fileURL)") | |
return fileURL | |
} | |
func startCaptureVideo(){ | |
let outFileUrl = createTempFileURL() | |
videoOutput?.startRecording(toOutputFileURL: outFileUrl, recordingDelegate: self) | |
} | |
func stopCaptureVideo() { | |
videoOutput?.stopRecording() | |
} | |
deinit { | |
self.delegate = nil | |
} | |
} | |
extension VideoPreviewView: AVCaptureFileOutputRecordingDelegate { | |
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, | |
fromConnections connections: [Any]!, error: Error!) { | |
if error != nil { | |
print("\n\n video save error \(error).......") | |
} else { | |
delegate?.onVideoSaved(outputUrl: outputFileURL) | |
print("video saved, outputFileURL: \(outputFileURL)") | |
} | |
} | |
} | |
protocol VideoPreviewViewDelegate: class { | |
func onVideoSaved(outputUrl: URL) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment