Created
September 26, 2016 21:56
-
-
Save makthrow/0570cc571f1c7866e094096626c19498 to your computer and use it in GitHub Desktop.
This file contains 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
import UIKit | |
import AVFoundation | |
import AssetsLibrary | |
import Photos | |
import MobileCoreServices | |
import FirebaseAuth | |
import CoreLocation | |
import RecordButton | |
// handles introduction videos (called from HomeViewController) | |
var SessionRunningAndDeviceAuthorizedContext = "SessionRunningAndDeviceAuthorizedContext" | |
var CapturingStillImageContext = "CapturingStillImageContext" | |
var RecordingContext = "RecordingContext" | |
class CamViewController: UIViewController, AVCaptureFileOutputRecordingDelegate { | |
// MARK: property | |
var sessionQueue: DispatchQueue! | |
var session: AVCaptureSession? | |
var videoDeviceInput: AVCaptureDeviceInput? | |
var movieFileOutput: AVCaptureMovieFileOutput? | |
var stillImageOutput: AVCaptureStillImageOutput? | |
var deviceAuthorized: Bool = false | |
var backgroundRecordId: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid | |
var sessionRunningAndDeviceAuthorized: Bool { | |
get { | |
return (self.session?.isRunning != nil && self.deviceAuthorized ) | |
} | |
} | |
var runtimeErrorHandlingObserver: AnyObject? | |
var lockInterfaceRotation: Bool = false | |
var fileToUploadURL: URL! | |
var fileToUploadDATA: Data! | |
var currentMediaTypeToUpload: String? | |
@IBOutlet weak var previewView: AVCamPreviewView! | |
// TAKE PICTURE | |
@IBOutlet weak var snapButton: UIButton! | |
// RECORD VIDEO | |
@IBOutlet weak var recordButton: UIButton! | |
// REVERSE CAM | |
@IBOutlet weak var cameraButton: UIButton! | |
@IBOutlet weak var backButton: UIButton! | |
var customRecordButton : RecordButton! | |
var progressTimer : Timer! | |
var progress : CGFloat! = 0 | |
// MARK: Override methods | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// custom recordButton https://github.com/samuelbeek/recordbutton | |
let recordButtonRect = CGRect(x: 0, y: 0, width: 70, height: 70) | |
let customRecordButton = RecordButton(frame: recordButtonRect) | |
customRecordButton.center = self.view.center | |
customRecordButton.progressColor = UIColor.red | |
customRecordButton.closeWhenFinished = false | |
customRecordButton.center.x = self.view.center.x | |
customRecordButton.addTarget(self, action: #selector(self.record), for: .touchDown) | |
customRecordButton.addTarget(self, action: #selector(self.stop), for: UIControlEvents.touchUpInside) | |
self.view.addSubview(customRecordButton) | |
let session: AVCaptureSession = AVCaptureSession() | |
self.session = session | |
self.previewView.session = session | |
self.checkDeviceAuthorizationStatus() | |
let sessionQueue: DispatchQueue = DispatchQueue(label: "session queue",attributes: []) | |
self.sessionQueue = sessionQueue | |
sessionQueue.async(execute: { | |
self.backgroundRecordId = UIBackgroundTaskInvalid | |
let videoDevice: AVCaptureDevice! = CamViewController.deviceWithMediaType(AVMediaTypeVideo, preferringPosition: AVCaptureDevicePosition.front) // start in reverse cam | |
var error: NSError? = nil | |
var videoDeviceInput: AVCaptureDeviceInput? | |
do { | |
videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) | |
} catch let error1 as NSError { | |
error = error1 | |
videoDeviceInput = nil | |
} catch { | |
fatalError() | |
} | |
if (error != nil) { | |
print(error) | |
let alert = UIAlertController(title: "Error", message: error!.localizedDescription | |
, preferredStyle: .alert) | |
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) | |
self.present(alert, animated: true, completion: nil) | |
} | |
if session.canAddInput(videoDeviceInput){ | |
session.addInput(videoDeviceInput) | |
self.videoDeviceInput = videoDeviceInput | |
DispatchQueue.main.async(execute: { | |
// Why are we dispatching this to the main queue? | |
// Because AVCaptureVideoPreviewLayer is the backing layer for AVCamPreviewView and UIView can only be manipulated on main thread. | |
// Note: As an exception to the above rule, it is not necessary to serialize video orientation changes on the AVCaptureVideoPreviewLayer’s connection with other session manipulation. | |
let orientation: AVCaptureVideoOrientation = AVCaptureVideoOrientation(rawValue: UIDevice.current.orientation.rawValue)! | |
(self.previewView.layer as! AVCaptureVideoPreviewLayer).connection.videoOrientation = orientation | |
}) | |
} | |
let audioDevice: AVCaptureDevice = AVCaptureDevice.devices(withMediaType: AVMediaTypeAudio).first as! AVCaptureDevice | |
var audioDeviceInput: AVCaptureDeviceInput? | |
do { | |
audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice) | |
} catch let error2 as NSError { | |
error = error2 | |
audioDeviceInput = nil | |
} catch { | |
fatalError() | |
} | |
if error != nil{ | |
print(error) | |
let alert = UIAlertController(title: "Error", message: error!.localizedDescription | |
, preferredStyle: .alert) | |
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) | |
self.present(alert, animated: true, completion: nil) | |
} | |
if session.canAddInput(audioDeviceInput){ | |
session.addInput(audioDeviceInput) | |
} | |
let movieFileOutput: AVCaptureMovieFileOutput = AVCaptureMovieFileOutput() | |
if session.canAddOutput(movieFileOutput){ | |
session.addOutput(movieFileOutput) | |
let connection: AVCaptureConnection? = movieFileOutput.connection(withMediaType: AVMediaTypeVideo) | |
let stab = connection?.isVideoStabilizationSupported | |
if (stab != nil) { | |
//deprecated in iOS 8.0 | |
//connection!.enablesVideoStabilizationWhenAvailable = true | |
connection!.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto } | |
self.movieFileOutput = movieFileOutput | |
} | |
let stillImageOutput: AVCaptureStillImageOutput = AVCaptureStillImageOutput() | |
if session.canAddOutput(stillImageOutput){ | |
stillImageOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] | |
session.addOutput(stillImageOutput) | |
self.stillImageOutput = stillImageOutput | |
} | |
}) | |
} | |
override func viewWillAppear(_ animated: Bool) { | |
getUserLocation() | |
let recordButtonRect = CGRect(x: 0, y: 0, width: 70, height: 70) | |
let customRecordButton = RecordButton(frame: recordButtonRect) | |
customRecordButton.center = self.view.center | |
customRecordButton.progressColor = UIColor.red | |
customRecordButton.closeWhenFinished = false | |
customRecordButton.center.x = self.view.center.x | |
customRecordButton.addTarget(self, action: #selector(self.record), for: .touchDown) | |
customRecordButton.addTarget(self, action: #selector(self.stop), for: UIControlEvents.touchUpInside) | |
self.view.addSubview(customRecordButton) | |
if (customRecordButton != nil) { | |
print ("customRecordButton is not nil : viewWillAppear begin") | |
} | |
else { | |
print ("customRecordButton is nil : viewWillAppear begin") | |
} | |
self.sessionQueue.async(execute: { | |
self.addObserver(self, forKeyPath: "sessionRunningAndDeviceAuthorized", options: [.old , .new] , context: &SessionRunningAndDeviceAuthorizedContext) | |
self.addObserver(self, forKeyPath: "stillImageOutput.capturingStillImage", options:[.old , .new], context: &CapturingStillImageContext) | |
self.addObserver(self, forKeyPath: "movieFileOutput.recording", options: [.old , .new], context: &RecordingContext) | |
NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange(_:)), name: NSNotification.Name.AVCaptureDeviceSubjectAreaDidChange, object: self.videoDeviceInput?.device) | |
weak var weakSelf = self | |
self.runtimeErrorHandlingObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVCaptureSessionRuntimeError, object: self.session, queue: nil, using: { | |
(note: Notification?) in | |
let strongSelf: CamViewController = weakSelf! | |
strongSelf.sessionQueue.async(execute: { | |
if let sess = strongSelf.session{ | |
sess.startRunning() | |
} | |
}) | |
}) | |
self.session?.startRunning() | |
}) | |
if (customRecordButton != nil) { | |
print ("customRecordButton is not nil : viewWillAppear end") | |
} | |
else { | |
print ("customRecordButton is nil : viewWillAppear end") | |
} | |
} | |
override func viewWillDisappear(_ animated: Bool) { | |
self.sessionQueue.async(execute: { | |
if let sess = self.session{ | |
sess.stopRunning() | |
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVCaptureDeviceSubjectAreaDidChange, object: self.videoDeviceInput?.device) | |
NotificationCenter.default.removeObserver(self.runtimeErrorHandlingObserver!) | |
self.removeObserver(self, forKeyPath: "sessionRunningAndDeviceAuthorized", context: &SessionRunningAndDeviceAuthorizedContext) | |
self.removeObserver(self, forKeyPath: "stillImageOutput.capturingStillImage", context: &CapturingStillImageContext) | |
self.removeObserver(self, forKeyPath: "movieFileOutput.recording", context: &RecordingContext) | |
} | |
}) | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
// Dispose of any resources that can be recreated. | |
} | |
override var prefersStatusBarHidden : Bool { | |
return true | |
} | |
override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) { | |
(self.previewView.layer as! AVCaptureVideoPreviewLayer).connection.videoOrientation = AVCaptureVideoOrientation(rawValue: toInterfaceOrientation.rawValue)! | |
// if let layer = self.previewView.layer as? AVCaptureVideoPreviewLayer{ | |
// layer.connection.videoOrientation = self.convertOrientation(toInterfaceOrientation) | |
// } | |
} | |
override var shouldAutorotate : Bool { | |
return !self.lockInterfaceRotation | |
} | |
// observeValueForKeyPath:ofObject:change:context: | |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | |
if context == &CapturingStillImageContext{ | |
let isCapturingStillImage: Bool = (change![NSKeyValueChangeKey.newKey]! as AnyObject).boolValue | |
if isCapturingStillImage { | |
self.runStillImageCaptureAnimation() | |
} | |
}else if context == &RecordingContext{ | |
let isRecording: Bool = (change![NSKeyValueChangeKey.newKey]! as AnyObject).boolValue | |
DispatchQueue.main.async(execute: { | |
if isRecording { | |
self.recordButton.titleLabel!.text = "Stop" | |
self.recordButton.isEnabled = true | |
// self.snapButton.enabled = false | |
self.cameraButton.isEnabled = false | |
self.backButton.isEnabled = false | |
}else{ | |
// self.snapButton.enabled = true | |
self.recordButton.titleLabel!.text = "Record" | |
self.recordButton.isEnabled = true | |
self.cameraButton.isEnabled = true | |
self.backButton.isEnabled = true | |
} | |
}) | |
} | |
else{ | |
return super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) | |
} | |
} | |
// MARK: Selector | |
func subjectAreaDidChange(_ notification: Notification){ | |
let devicePoint: CGPoint = CGPoint(x: 0.5, y: 0.5) | |
self.focusWithMode(AVCaptureFocusMode.continuousAutoFocus, exposureMode: AVCaptureExposureMode.continuousAutoExposure, point: devicePoint, monitorSubjectAreaChange: false) | |
} | |
// MARK: Custom Function | |
func focusWithMode(_ focusMode:AVCaptureFocusMode, exposureMode:AVCaptureExposureMode, point:CGPoint, monitorSubjectAreaChange:Bool){ | |
self.sessionQueue.async(execute: { | |
let device: AVCaptureDevice! = self.videoDeviceInput!.device | |
do { | |
try device.lockForConfiguration() | |
if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode){ | |
device.focusMode = focusMode | |
device.focusPointOfInterest = point | |
} | |
if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode){ | |
device.exposurePointOfInterest = point | |
device.exposureMode = exposureMode | |
} | |
device.isSubjectAreaChangeMonitoringEnabled = monitorSubjectAreaChange | |
device.unlockForConfiguration() | |
}catch{ | |
print(error) | |
} | |
}) | |
} | |
class func setFlashMode(_ flashMode: AVCaptureFlashMode, device: AVCaptureDevice){ | |
if device.hasFlash && device.isFlashModeSupported(flashMode) { | |
var error: NSError? = nil | |
do { | |
try device.lockForConfiguration() | |
device.flashMode = flashMode | |
device.unlockForConfiguration() | |
} catch let error1 as NSError { | |
error = error1 | |
print(error) | |
} | |
} | |
} | |
func runStillImageCaptureAnimation(){ | |
DispatchQueue.main.async(execute: { | |
self.previewView.layer.opacity = 0.0 | |
print("opacity 0") | |
UIView.animate(withDuration: 0.25, animations: { | |
self.previewView.layer.opacity = 1.0 | |
print("opacity 1") | |
}) | |
}) | |
} | |
class func deviceWithMediaType(_ mediaType: String, preferringPosition:AVCaptureDevicePosition)->AVCaptureDevice{ | |
var devices = AVCaptureDevice.devices(withMediaType: mediaType); | |
var captureDevice: AVCaptureDevice = devices![0] as! AVCaptureDevice; | |
for device in devices!{ | |
if (device as AnyObject).position == preferringPosition{ | |
captureDevice = device as! AVCaptureDevice | |
break | |
} | |
} | |
return captureDevice | |
} | |
func checkDeviceAuthorizationStatus(){ | |
let mediaType:String = AVMediaTypeVideo; | |
AVCaptureDevice.requestAccess(forMediaType: mediaType, completionHandler: { (granted: Bool) in | |
if granted{ | |
self.deviceAuthorized = true; | |
}else{ | |
DispatchQueue.main.async(execute: { | |
let alert: UIAlertController = UIAlertController( | |
title: "AVCam", | |
message: "AVCam does not have permission to access camera", | |
preferredStyle: UIAlertControllerStyle.alert); | |
let action: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: { | |
(action2: UIAlertAction) in | |
exit(0); | |
} ); | |
alert.addAction(action); | |
self.present(alert, animated: true, completion: nil); | |
}) | |
self.deviceAuthorized = false; | |
} | |
}) | |
} | |
// MARK: File Output Delegate | |
func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) { | |
if(error != nil){ | |
print(error) | |
} | |
self.lockInterfaceRotation = false | |
// Note the backgroundRecordingID for use in the ALAssetsLibrary completion handler to end the background task associated with this recording. This allows a new recording to be started, associated with a new UIBackgroundTaskIdentifier, once the movie file output's -isRecording is back to NO — which happens sometime after this method returns. | |
let backgroundRecordId: UIBackgroundTaskIdentifier = self.backgroundRecordId | |
self.backgroundRecordId = UIBackgroundTaskInvalid | |
// TODO: iOS9.0 - rewrite using Photos framework | |
PHPhotoLibrary.shared().performChanges({ | |
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputFileURL) | |
}, completionHandler: { (success, error) in | |
self.fileToUploadURL = outputFileURL | |
// self.presentAddCommentAlert() | |
uploadVideo(self.fileToUploadURL, title: "\(Constants.userID)") | |
if !success { NSLog("error creating asset: \(error)") } | |
}) | |
} | |
// MARK: Actions | |
@IBAction func toggleMovieRecord(_ sender: AnyObject) { | |
self.recordButton.isEnabled = false | |
self.sessionQueue.async(execute: { | |
if !self.movieFileOutput!.isRecording{ | |
self.lockInterfaceRotation = true | |
if UIDevice.current.isMultitaskingSupported { | |
self.backgroundRecordId = UIApplication.shared.beginBackgroundTask(expirationHandler: {}) | |
} | |
self.movieFileOutput!.connection(withMediaType: AVMediaTypeVideo).videoOrientation = | |
AVCaptureVideoOrientation(rawValue: (self.previewView.layer as! AVCaptureVideoPreviewLayer).connection.videoOrientation.rawValue )! | |
// Turning OFF flash for video recording | |
CamViewController.setFlashMode(AVCaptureFlashMode.off, device: self.videoDeviceInput!.device) | |
let outputFilePath = | |
URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("movie.mov") | |
//NSTemporaryDirectory().stringByAppendingPathComponent( "movie".stringByAppendingPathExtension("mov")!) | |
self.movieFileOutput!.startRecording( toOutputFileURL: outputFilePath, recordingDelegate: self) | |
}else{ | |
self.movieFileOutput!.stopRecording() | |
} | |
}) | |
} | |
@IBAction func snapStillImage(_ sender: AnyObject) { | |
print("snapStillImage") | |
self.sessionQueue.async(execute: { | |
// Update the orientation on the still image output video connection before capturing. | |
let videoOrientation = (self.previewView.layer as! AVCaptureVideoPreviewLayer).connection.videoOrientation | |
self.stillImageOutput!.connection(withMediaType: AVMediaTypeVideo).videoOrientation = videoOrientation | |
// Flash set to Auto for Still Capture | |
CamViewController.setFlashMode(AVCaptureFlashMode.auto, device: self.videoDeviceInput!.device) | |
self.stillImageOutput!.captureStillImageAsynchronously(from: self.stillImageOutput!.connection(withMediaType: AVMediaTypeVideo), completionHandler: { | |
(imageDataSampleBuffer: CMSampleBuffer?, error: Error?) in | |
if error == nil { | |
let data:Data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) | |
let image:UIImage = UIImage( data: data)! | |
let library:ALAssetsLibrary = ALAssetsLibrary() | |
let orientation: ALAssetOrientation = ALAssetOrientation(rawValue: image.imageOrientation.rawValue)! | |
library.writeImage(toSavedPhotosAlbum: image.cgImage, orientation: orientation, completionBlock: nil) | |
// TODO: rewrite using library2 . new Photos framework | |
print("save to album") | |
self.fileToUploadDATA = data | |
// upload image? | |
}else{ | |
// print("Did not capture still image") | |
print(error) | |
} | |
}) | |
}) | |
} | |
@IBAction func changeCamera(_ sender: AnyObject) { | |
print("change camera") | |
self.cameraButton.isEnabled = false | |
self.recordButton.isEnabled = false | |
self.snapButton.isEnabled = false | |
self.sessionQueue.async(execute: { | |
let currentVideoDevice:AVCaptureDevice = self.videoDeviceInput!.device | |
let currentPosition: AVCaptureDevicePosition = currentVideoDevice.position | |
var preferredPosition: AVCaptureDevicePosition = AVCaptureDevicePosition.unspecified | |
switch currentPosition{ | |
case AVCaptureDevicePosition.front: | |
preferredPosition = AVCaptureDevicePosition.back | |
case AVCaptureDevicePosition.back: | |
preferredPosition = AVCaptureDevicePosition.front | |
case AVCaptureDevicePosition.unspecified: | |
preferredPosition = AVCaptureDevicePosition.back | |
} | |
let device:AVCaptureDevice = CamViewController.deviceWithMediaType(AVMediaTypeVideo, preferringPosition: preferredPosition) | |
var videoDeviceInput: AVCaptureDeviceInput? | |
do { | |
videoDeviceInput = try AVCaptureDeviceInput(device: device) | |
} catch _ as NSError { | |
videoDeviceInput = nil | |
} catch { | |
fatalError() | |
} | |
self.session!.beginConfiguration() | |
self.session!.removeInput(self.videoDeviceInput) | |
if self.session!.canAddInput(videoDeviceInput){ | |
NotificationCenter.default.removeObserver(self, name:NSNotification.Name.AVCaptureDeviceSubjectAreaDidChange, object:currentVideoDevice) | |
CamViewController.setFlashMode(AVCaptureFlashMode.auto, device: device) | |
NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaDidChange(_:)), name: NSNotification.Name.AVCaptureDeviceSubjectAreaDidChange, object: device) | |
self.session!.addInput(videoDeviceInput) | |
self.videoDeviceInput = videoDeviceInput | |
}else{ | |
self.session!.addInput(self.videoDeviceInput) | |
} | |
self.session!.commitConfiguration() | |
DispatchQueue.main.async(execute: { | |
self.recordButton.isEnabled = true | |
self.snapButton.isEnabled = true | |
self.cameraButton.isEnabled = true | |
}) | |
}) | |
} | |
@IBAction func focusAndExposeTap(_ gestureRecognizer: UIGestureRecognizer) { | |
print("focusAndExposeTap") | |
let devicePoint: CGPoint = (self.previewView.layer as! AVCaptureVideoPreviewLayer).captureDevicePointOfInterest(for: gestureRecognizer.location(in: gestureRecognizer.view)) | |
print(devicePoint) | |
self.focusWithMode(AVCaptureFocusMode.autoFocus, exposureMode: AVCaptureExposureMode.autoExpose, point: devicePoint, monitorSubjectAreaChange: true) | |
} | |
@IBAction func backButton(_ sender: UIButton) { | |
dismiss(animated: true, completion: nil) | |
} | |
// RECORD BUTTON | |
func record() { | |
self.progressTimer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(updateProgress), userInfo: nil, repeats: true) | |
} | |
func updateProgress() { | |
let maxDuration = CGFloat(5) // max duration of the recordButton | |
progress = progress + (CGFloat(0.05) / maxDuration) | |
customRecordButton.setProgress(progress) // (customRecordButton is always nil here) | |
if progress >= 1 { | |
progressTimer.invalidate() | |
} | |
} | |
func stop() { | |
self.progressTimer.invalidate() | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment