513 lines
19 KiB
Swift
513 lines
19 KiB
Swift
//
|
|
// CameraManager.swift
|
|
// camera
|
|
//
|
|
// Created by Natalia Terlecka on 10/10/14.
|
|
// Copyright (c) 2014 imaginaryCloud. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import AVFoundation
|
|
import AssetsLibrary
|
|
|
|
private let _singletonSharedInstance = CameraManager()
|
|
|
|
enum CameraDevice {
|
|
case Front, Back
|
|
}
|
|
|
|
enum CameraFlashMode: Int {
|
|
case Off, On, Auto
|
|
}
|
|
|
|
enum CameraOutputMode {
|
|
case StillImage, VideoWithMic, VideoOnly
|
|
}
|
|
|
|
enum CameraOutputQuality {
|
|
case Low, Medium, High
|
|
}
|
|
|
|
/// Class for handling iDevices custom camera usage
|
|
class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
|
|
|
|
/// The Bool property to determin if current device has front camera.
|
|
var hasFrontCamera: Bool = {
|
|
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
|
|
for device in devices {
|
|
let captureDevice = device as AVCaptureDevice
|
|
if (captureDevice.position == AVCaptureDevicePosition.Front) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}()
|
|
|
|
/// A block creating UI to present error message to the user.
|
|
var showErrorBlock:(erTitle: String, erMessage: String) -> Void = { (erTitle: String, erMessage: String) -> Void in
|
|
UIAlertView(title: erTitle, message: erMessage, delegate: nil, cancelButtonTitle: "OK").show()
|
|
}
|
|
|
|
/// Property to change camera device between front and back.
|
|
var cameraDevice: CameraDevice {
|
|
get {
|
|
return self.currentCameraDevice
|
|
}
|
|
set(newCameraDevice) {
|
|
if newCameraDevice != self.currentCameraDevice {
|
|
self.captureSession?.beginConfiguration()
|
|
|
|
switch newCameraDevice {
|
|
case .Front:
|
|
if self.hasFrontCamera {
|
|
if let validBackDevice = self.rearCamera? {
|
|
self.captureSession?.removeInput(validBackDevice)
|
|
}
|
|
if let validFrontDevice = self.frontCamera? {
|
|
self.captureSession?.addInput(validFrontDevice)
|
|
}
|
|
}
|
|
case .Back:
|
|
if let validFrontDevice = self.frontCamera? {
|
|
self.captureSession?.removeInput(validFrontDevice)
|
|
}
|
|
if let validBackDevice = self.rearCamera? {
|
|
self.captureSession?.addInput(validBackDevice)
|
|
}
|
|
}
|
|
self.captureSession?.commitConfiguration()
|
|
|
|
self.currentCameraDevice = newCameraDevice
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Property to change camera flash mode.
|
|
var flashMode: CameraFlashMode {
|
|
get {
|
|
return self.currentFlashMode
|
|
}
|
|
set(newflashMode) {
|
|
if newflashMode != self.currentFlashMode {
|
|
self.captureSession?.beginConfiguration()
|
|
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
|
|
for device in devices {
|
|
let captureDevice = device as AVCaptureDevice
|
|
if (captureDevice.position == AVCaptureDevicePosition.Back) {
|
|
let avFlashMode = AVCaptureFlashMode.fromRaw(newflashMode.toRaw())
|
|
if (captureDevice.isFlashModeSupported(avFlashMode!)) {
|
|
captureDevice.lockForConfiguration(nil)
|
|
captureDevice.flashMode = avFlashMode!
|
|
captureDevice.unlockForConfiguration()
|
|
}
|
|
}
|
|
}
|
|
self.captureSession?.commitConfiguration()
|
|
|
|
self.currentFlashMode = newflashMode
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Property to change camera output quality.
|
|
var cameraOutputQuality: CameraOutputQuality {
|
|
get {
|
|
return self.currentCameraOutputQuality
|
|
}
|
|
set(newCameraOutputQuality) {
|
|
if newCameraOutputQuality != self.currentCameraOutputQuality {
|
|
self.captureSession?.beginConfiguration()
|
|
switch (newCameraOutputQuality) {
|
|
case CameraOutputQuality.Low:
|
|
self.captureSession?.sessionPreset = AVCaptureSessionPresetLow
|
|
case CameraOutputQuality.Medium:
|
|
self.captureSession?.sessionPreset = AVCaptureSessionPresetMedium
|
|
case CameraOutputQuality.High:
|
|
self.captureSession?.sessionPreset = AVCaptureSessionPresetHigh
|
|
}
|
|
self.captureSession?.commitConfiguration()
|
|
|
|
self.currentCameraOutputQuality = newCameraOutputQuality
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Property to change camera output.
|
|
var cameraOutputMode: CameraOutputMode {
|
|
get {
|
|
return self.currentCameraOutputMode
|
|
}
|
|
set(newCameraOutputMode) {
|
|
if newCameraOutputMode != self.currentCameraOutputMode {
|
|
self.captureSession?.beginConfiguration()
|
|
|
|
// remove current setting
|
|
switch self.currentCameraOutputMode {
|
|
case .StillImage:
|
|
if let validStillImageOutput = self.stillImageOutput? {
|
|
self.captureSession?.removeOutput(validStillImageOutput)
|
|
}
|
|
case .VideoOnly, .VideoWithMic:
|
|
if let validMovieOutput = self.movieOutput? {
|
|
self.captureSession?.removeOutput(validMovieOutput)
|
|
}
|
|
if self.currentCameraOutputMode == .VideoWithMic {
|
|
if let validMic = self.mic? {
|
|
self.captureSession?.removeInput(validMic)
|
|
}
|
|
}
|
|
}
|
|
// configure new devices
|
|
switch newCameraOutputMode {
|
|
case .StillImage:
|
|
if (self.stillImageOutput == nil) {
|
|
self._setupStillImageOutput()
|
|
}
|
|
if let validStillImageOutput = self.stillImageOutput? {
|
|
self.captureSession?.addOutput(validStillImageOutput)
|
|
}
|
|
self.captureSession?.sessionPreset = AVCaptureSessionPresetPhoto
|
|
|
|
case .VideoOnly, .VideoWithMic:
|
|
if (self.movieOutput == nil) {
|
|
self._setupMovieOutput()
|
|
}
|
|
if let validMovieOutput = self.movieOutput? {
|
|
self.captureSession?.addOutput(validMovieOutput)
|
|
}
|
|
if self.currentCameraOutputMode == .VideoWithMic {
|
|
if (self.mic == nil) {
|
|
self._setupMic()
|
|
}
|
|
if let validMic = self.mic? {
|
|
self.captureSession?.addInput(validMic)
|
|
}
|
|
}
|
|
self.captureSession?.sessionPreset = AVCaptureSessionPresetMedium
|
|
|
|
}
|
|
self.captureSession?.commitConfiguration()
|
|
|
|
self.currentCameraOutputMode = newCameraOutputMode
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Capture sessioc to customize camera settings.
|
|
var captureSession: AVCaptureSession?
|
|
|
|
private weak var embedingView: UIView?
|
|
private var videoCompletition: ((videoURL: NSURL) -> Void)?
|
|
|
|
private var sessionQueue: dispatch_queue_t = dispatch_queue_create("CameraSessionQueue", DISPATCH_QUEUE_SERIAL)
|
|
|
|
private var frontCamera: AVCaptureInput?
|
|
private var rearCamera: AVCaptureInput?
|
|
private var mic: AVCaptureDeviceInput?
|
|
private var stillImageOutput: AVCaptureStillImageOutput?
|
|
private var movieOutput: AVCaptureMovieFileOutput?
|
|
private var previewLayer: AVCaptureVideoPreviewLayer?
|
|
|
|
private var cameraIsSetup = false
|
|
|
|
private var currentCameraDevice = CameraDevice.Back
|
|
private var currentFlashMode = CameraFlashMode.Off
|
|
private var currentCameraOutputMode = CameraOutputMode.StillImage
|
|
private var currentCameraOutputQuality = CameraOutputQuality.High
|
|
|
|
private var tempFilePath: NSURL = {
|
|
let tempPath = NSTemporaryDirectory().stringByAppendingPathComponent("tempMovie").stringByAppendingPathExtension("mp4")
|
|
if NSFileManager.defaultManager().fileExistsAtPath(tempPath!) {
|
|
NSFileManager.defaultManager().removeItemAtPath(tempPath!, error: nil)
|
|
}
|
|
return NSURL(fileURLWithPath: tempPath!)
|
|
}()
|
|
|
|
/// CameraManager singleton instance to use the camera.
|
|
class var sharedInstance: CameraManager {
|
|
return _singletonSharedInstance
|
|
}
|
|
|
|
deinit {
|
|
self.stopAndRemoveCaptureSession()
|
|
self._stopFollowingDeviceOrientation()
|
|
}
|
|
|
|
/**
|
|
Inits a capture session and adds a preview layer to the given view. Preview layer bounds will automaticaly be set to match given view.
|
|
|
|
:param: view The view you want to add the preview layer to
|
|
:param: cameraOutputMode The mode you want capturesession to run image / video / video and microphone
|
|
*/
|
|
func addPreeviewLayerToView(view: UIView, cameraOutputMode: CameraOutputMode)
|
|
{
|
|
if let validEmbedingView = self.embedingView? {
|
|
if let validPreviewLayer = self.previewLayer? {
|
|
validPreviewLayer.removeFromSuperlayer()
|
|
}
|
|
}
|
|
if self.cameraIsSetup {
|
|
self._addPreeviewLayerToView(view)
|
|
self.cameraOutputMode = cameraOutputMode
|
|
} else {
|
|
self._setupCamera({ Void -> Void in
|
|
self._addPreeviewLayerToView(view)
|
|
self.cameraOutputMode = cameraOutputMode
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
Stops running capture session but all setup devices, inputs and outputs stay for further reuse.
|
|
*/
|
|
func stopCaptureSession()
|
|
{
|
|
self.captureSession?.stopRunning()
|
|
}
|
|
|
|
/**
|
|
Stops running capture session and removes all setup devices, inputs and outputs.
|
|
*/
|
|
func stopAndRemoveCaptureSession()
|
|
{
|
|
self.stopCaptureSession()
|
|
self.cameraDevice = .Back
|
|
self.cameraIsSetup = false
|
|
self.previewLayer = nil
|
|
self.captureSession = nil
|
|
self.frontCamera = nil
|
|
self.rearCamera = nil
|
|
self.mic = nil
|
|
self.stillImageOutput = nil
|
|
self.movieOutput = nil
|
|
}
|
|
|
|
/**
|
|
Captures still image from currently running capture session.
|
|
|
|
:param: imageCompletition Completition block containing the captured UIImage
|
|
*/
|
|
func capturePictureWithCompletition(imageCompletition: UIImage -> Void)
|
|
{
|
|
if self.cameraIsSetup {
|
|
if self.cameraOutputMode == .StillImage {
|
|
dispatch_async(self.sessionQueue, {
|
|
if let validStillImageOutput = self.stillImageOutput? {
|
|
validStillImageOutput.captureStillImageAsynchronouslyFromConnection(validStillImageOutput.connectionWithMediaType(AVMediaTypeVideo), completionHandler: { [weak self] (sample: CMSampleBuffer!, error: NSError!) -> Void in
|
|
if (error? != nil) {
|
|
dispatch_async(dispatch_get_main_queue(), {
|
|
if let weakSelf = self {
|
|
weakSelf._show("error", message: error.localizedDescription)
|
|
}
|
|
})
|
|
} else {
|
|
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sample)
|
|
imageCompletition(UIImage(data: imageData))
|
|
}
|
|
})
|
|
}
|
|
})
|
|
} else {
|
|
self._show("Capture session output mode video", message: "I can't take any picture")
|
|
}
|
|
} else {
|
|
self._show("No capture session setup", message: "I can't take any picture")
|
|
}
|
|
}
|
|
|
|
/**
|
|
Starts recording a video with or without voice as in the session preset.
|
|
*/
|
|
func startRecordingVideo()
|
|
{
|
|
if self.cameraOutputMode != .StillImage {
|
|
self.movieOutput?.startRecordingToOutputFileURL(self.tempFilePath, recordingDelegate: self)
|
|
} else {
|
|
self._show("Capture session output still image", message: "I can only take pictures")
|
|
}
|
|
}
|
|
|
|
/**
|
|
Stop recording a video.
|
|
*/
|
|
func stopRecordingVideo(completition:(videoURL: NSURL) -> Void)
|
|
{
|
|
if let runningMovieOutput = self.movieOutput {
|
|
if runningMovieOutput.recording {
|
|
self.videoCompletition = completition
|
|
runningMovieOutput.stopRecording()
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// PRAGMA MARK - AVCaptureFileOutputRecordingDelegate
|
|
|
|
func captureOutput(captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAtURL fileURL: NSURL!, fromConnections connections: [AnyObject]!)
|
|
{
|
|
|
|
}
|
|
|
|
func captureOutput(captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAtURL outputFileURL: NSURL!, fromConnections connections: [AnyObject]!, error: NSError!)
|
|
{
|
|
if (error != nil) {
|
|
self._show("Unable to save video to the iPhone", message: error.localizedDescription)
|
|
} else {
|
|
let library = ALAssetsLibrary()
|
|
library.writeVideoAtPathToSavedPhotosAlbum(outputFileURL, completionBlock: { (assetURL: NSURL?, error: NSError?) -> Void in
|
|
if (error != nil) {
|
|
self._show("Unable to save video to the iPhone.", message: error!.localizedDescription)
|
|
} else {
|
|
if let validCompletition = self.videoCompletition {
|
|
if let validAssetURL = assetURL {
|
|
validCompletition(videoURL: validAssetURL)
|
|
self.videoCompletition = nil
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// PRAGMA MARK - CameraManager()
|
|
|
|
@objc private func _orientationChanged()
|
|
{
|
|
if let validPreviewLayer = self.previewLayer {
|
|
switch UIDevice.currentDevice().orientation {
|
|
case .LandscapeLeft:
|
|
validPreviewLayer.connection.videoOrientation = .LandscapeRight
|
|
case .LandscapeRight:
|
|
validPreviewLayer.connection.videoOrientation = .LandscapeLeft
|
|
default:
|
|
validPreviewLayer.connection.videoOrientation = .Portrait
|
|
}
|
|
dispatch_async(dispatch_get_main_queue(), { () -> Void in
|
|
if let validEmbedingView = self.embedingView? {
|
|
validPreviewLayer.frame = validEmbedingView.bounds
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private func _setupCamera(completition: Void -> Void)
|
|
{
|
|
if self._checkIfCameraIsAvailable() {
|
|
self.captureSession = AVCaptureSession()
|
|
self.captureSession?.sessionPreset = AVCaptureSessionPresetPhoto
|
|
|
|
dispatch_async(sessionQueue, {
|
|
if let validCaptureSession = self.captureSession? {
|
|
validCaptureSession.beginConfiguration()
|
|
self._addVideoInput()
|
|
self._setupStillImageOutput()
|
|
if let validStillImageOutput = self.stillImageOutput? {
|
|
self.captureSession?.addOutput(self.stillImageOutput)
|
|
}
|
|
self._setupPreviewLayer()
|
|
validCaptureSession.commitConfiguration()
|
|
validCaptureSession.startRunning()
|
|
self._startFollowingDeviceOrientation()
|
|
completition()
|
|
self.cameraIsSetup = true
|
|
}
|
|
})
|
|
} else {
|
|
self._show("Camera unavailable", message: "The device does not have a camera")
|
|
}
|
|
}
|
|
|
|
private func _startFollowingDeviceOrientation()
|
|
{
|
|
NSNotificationCenter.defaultCenter().addObserver(self, selector: "_orientationChanged", name: UIDeviceOrientationDidChangeNotification, object: nil)
|
|
}
|
|
|
|
private func _stopFollowingDeviceOrientation()
|
|
{
|
|
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIDeviceOrientationDidChangeNotification, object: nil)
|
|
}
|
|
|
|
private func _addPreeviewLayerToView(view: UIView)
|
|
{
|
|
self.embedingView = view
|
|
dispatch_async(dispatch_get_main_queue(), { () -> Void in
|
|
self.previewLayer?.frame = view.layer.bounds
|
|
view.clipsToBounds = true
|
|
view.layer.addSublayer(self.previewLayer)
|
|
})
|
|
}
|
|
|
|
private func _checkIfCameraIsAvailable() -> Bool
|
|
{
|
|
let deviceHasCamera = UIImagePickerController.isCameraDeviceAvailable(UIImagePickerControllerCameraDevice.Rear) || UIImagePickerController.isCameraDeviceAvailable(UIImagePickerControllerCameraDevice.Front)
|
|
return deviceHasCamera
|
|
}
|
|
|
|
private func _addVideoInput()
|
|
{
|
|
var error: NSError?
|
|
|
|
var videoFrontDevice: AVCaptureDevice?
|
|
var videoBackDevice: AVCaptureDevice?
|
|
for device: AnyObject in AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) {
|
|
if device.position == AVCaptureDevicePosition.Back {
|
|
videoBackDevice = device as? AVCaptureDevice
|
|
} else if device.position == AVCaptureDevicePosition.Front {
|
|
videoFrontDevice = device as? AVCaptureDevice
|
|
}
|
|
}
|
|
if let validVideoFrontDevice = videoFrontDevice? {
|
|
self.frontCamera = AVCaptureDeviceInput.deviceInputWithDevice(validVideoFrontDevice, error: &error) as AVCaptureDeviceInput
|
|
self.hasFrontCamera = true
|
|
}
|
|
if let validVideoBackDevice = videoBackDevice? {
|
|
self.rearCamera = AVCaptureDeviceInput.deviceInputWithDevice(validVideoBackDevice, error: &error) as AVCaptureDeviceInput
|
|
if !(error != nil) {
|
|
if let validBackDevice = self.rearCamera? {
|
|
self.captureSession?.addInput(validBackDevice)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _setupMic()
|
|
{
|
|
if (self.mic == nil) {
|
|
var error: NSError?
|
|
let micDevice:AVCaptureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio);
|
|
self.mic = AVCaptureDeviceInput.deviceInputWithDevice(micDevice, error: &error) as? AVCaptureDeviceInput;
|
|
if let errorHappened = error {
|
|
self.mic = nil
|
|
self._show("Mic error", message: errorHappened.description)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _setupStillImageOutput()
|
|
{
|
|
if (self.stillImageOutput == nil) {
|
|
self.stillImageOutput = AVCaptureStillImageOutput()
|
|
}
|
|
}
|
|
|
|
private func _setupMovieOutput()
|
|
{
|
|
if (self.movieOutput == nil) {
|
|
self.movieOutput = AVCaptureMovieFileOutput()
|
|
}
|
|
}
|
|
|
|
private func _setupPreviewLayer()
|
|
{
|
|
self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
|
|
self.previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
|
|
}
|
|
|
|
private func _show (title: String, message: String)
|
|
{
|
|
dispatch_async(dispatch_get_main_queue(), { () -> Void in
|
|
self.showErrorBlock(erTitle: title, erMessage: message)
|
|
})
|
|
}
|
|
}
|