capacitor-community-multilens-camerapreview 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CapacitorCommunityMultilensCamerapreview.podspec +17 -17
  2. package/README.md +16 -16
  3. package/android/build.gradle +55 -55
  4. package/android/src/main/AndroidManifest.xml +4 -4
  5. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +1008 -1008
  6. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +544 -544
  7. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +23 -23
  8. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +29 -29
  9. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +386 -386
  10. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +24 -24
  11. package/android/src/main/res/layout/bridge_layout_main.xml +15 -15
  12. package/android/src/main/res/layout/camera_activity.xml +68 -68
  13. package/android/src/main/res/values/camera_ids.xml +4 -4
  14. package/android/src/main/res/values/camera_theme.xml +9 -9
  15. package/android/src/main/res/values/colors.xml +3 -3
  16. package/android/src/main/res/values/strings.xml +3 -3
  17. package/android/src/main/res/values/styles.xml +3 -3
  18. package/dist/docs.json +1 -1
  19. package/dist/esm/definitions.d.ts +80 -80
  20. package/dist/esm/definitions.js +1 -1
  21. package/dist/esm/definitions.js.map +1 -1
  22. package/dist/esm/index.d.ts +4 -4
  23. package/dist/esm/index.js +6 -6
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/web.d.ts +28 -28
  26. package/dist/esm/web.js +155 -155
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +149 -149
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +149 -149
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Plugin/CameraController.swift +720 -716
  33. package/ios/Plugin/Info.plist +24 -24
  34. package/ios/Plugin/Plugin.h +10 -10
  35. package/ios/Plugin/Plugin.m +18 -18
  36. package/ios/Plugin/Plugin.swift +308 -300
  37. package/package.json +78 -78
@@ -1,716 +1,720 @@
1
- //
2
- // CameraController.swift
3
- // Plugin
4
- //
5
- // Created by Ariel Hernandez Musa on 7/14/19.
6
- // Copyright © 2019 Max Lynch. All rights reserved.
7
- //
8
-
9
- import AVFoundation
10
- import UIKit
11
-
12
- class CameraController: NSObject {
13
- var captureSession: AVCaptureSession?
14
-
15
- var currentCameraPosition: CameraPosition?
16
-
17
- var frontCamera: AVCaptureDevice?
18
- var frontCameraInput: AVCaptureDeviceInput?
19
-
20
- var dataOutput: AVCaptureVideoDataOutput?
21
- var photoOutput: AVCapturePhotoOutput?
22
-
23
- var rearCamera: AVCaptureDevice?
24
- var rearCameraInput: AVCaptureDeviceInput?
25
-
26
- var previewLayer: AVCaptureVideoPreviewLayer?
27
-
28
- var flashMode = AVCaptureDevice.FlashMode.off
29
- var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
30
-
31
- var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
32
-
33
- var highResolutionOutput: Bool = false
34
-
35
- var audioDevice: AVCaptureDevice?
36
- var audioInput: AVCaptureDeviceInput?
37
-
38
- var zoomFactor: CGFloat = 1.0
39
- }
40
- extension CameraController {
41
- func prepare(cameraPosition: String, zoomFactor: String, disableAudio: Bool, completionHandler: @escaping (Error?) -> Void) {
42
-
43
- func createCaptureSession() {
44
- self.captureSession = AVCaptureSession()
45
- }
46
-
47
- func configureCaptureDevices() throws {
48
- var session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
49
- if(zoomFactor == "ultra"){
50
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: AVMediaType.video, position: .unspecified)
51
- } else if(zoomFactor == "wide"){
52
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
53
- } else if(zoomFactor == "tele"){
54
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
55
- }
56
- let cameras = session.devices.compactMap { $0 }
57
- guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
58
-
59
- for camera in cameras {
60
- if camera.position == .front {
61
- self.frontCamera = camera
62
- }
63
-
64
- if camera.position == .back {
65
- self.rearCamera = camera
66
-
67
- try camera.lockForConfiguration()
68
- camera.focusMode = .continuousAutoFocus
69
- camera.unlockForConfiguration()
70
- }
71
- }
72
- if disableAudio == false {
73
- self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
74
- }
75
- }
76
-
77
- func configureDeviceInputs() throws {
78
- guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
79
-
80
- if cameraPosition == "rear" {
81
- if let rearCamera = self.rearCamera {
82
- self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
83
-
84
- if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) }
85
-
86
- self.currentCameraPosition = .rear
87
- }
88
- } else if cameraPosition == "front" {
89
- if let frontCamera = self.frontCamera {
90
- self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
91
-
92
- if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) } else { throw CameraControllerError.inputsAreInvalid }
93
-
94
- self.currentCameraPosition = .front
95
- }
96
- } else { throw CameraControllerError.noCamerasAvailable }
97
-
98
- // Add audio input
99
- if disableAudio == false {
100
- if let audioDevice = self.audioDevice {
101
- self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
102
- if captureSession.canAddInput(self.audioInput!) {
103
- captureSession.addInput(self.audioInput!)
104
- } else {
105
- throw CameraControllerError.inputsAreInvalid
106
- }
107
- }
108
- }
109
- }
110
-
111
- func configurePhotoOutput() throws {
112
- guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
113
-
114
- self.photoOutput = AVCapturePhotoOutput()
115
- self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
116
- self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
117
- if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
118
- captureSession.startRunning()
119
- }
120
-
121
- func configureDataOutput() throws {
122
- guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
123
-
124
- self.dataOutput = AVCaptureVideoDataOutput()
125
- self.dataOutput?.videoSettings = [
126
- (kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
127
- ]
128
- self.dataOutput?.alwaysDiscardsLateVideoFrames = true
129
- if captureSession.canAddOutput(self.dataOutput!) {
130
- captureSession.addOutput(self.dataOutput!)
131
- }
132
-
133
- captureSession.commitConfiguration()
134
-
135
- let queue = DispatchQueue(label: "DataOutput", attributes: [])
136
- self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
137
- }
138
-
139
- DispatchQueue(label: "prepare").async {
140
- do {
141
- createCaptureSession()
142
- try configureCaptureDevices()
143
- try configureDeviceInputs()
144
- try configurePhotoOutput()
145
- try configureDataOutput()
146
- // try configureVideoOutput()
147
- } catch {
148
- DispatchQueue.main.async {
149
- completionHandler(error)
150
- }
151
-
152
- return
153
- }
154
-
155
- DispatchQueue.main.async {
156
- completionHandler(nil)
157
- }
158
- }
159
- }
160
-
161
- func displayPreview(on view: UIView) throws {
162
- guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
163
-
164
- self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
165
- self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
166
-
167
- view.layer.insertSublayer(self.previewLayer!, at: 0)
168
- self.previewLayer?.frame = view.frame
169
-
170
- updateVideoOrientation()
171
- }
172
-
173
- func setupGestures(target: UIView, enableZoom: Bool) {
174
- setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
175
- if enableZoom {
176
- setupPinchGesture(target: target, selector: #selector(handlePinch(_:)), delegate: self)
177
- }
178
- }
179
-
180
- func setupTapGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
181
- let tapGesture = UITapGestureRecognizer(target: self, action: selector)
182
- tapGesture.delegate = delegate
183
- target.addGestureRecognizer(tapGesture)
184
- }
185
-
186
- func setupPinchGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
187
- let pinchGesture = UIPinchGestureRecognizer(target: self, action: selector)
188
- pinchGesture.delegate = delegate
189
- target.addGestureRecognizer(pinchGesture)
190
- }
191
-
192
- func updateVideoOrientation() {
193
- assert(Thread.isMainThread) // UIApplication.statusBarOrientation requires the main thread.
194
-
195
- let videoOrientation: AVCaptureVideoOrientation
196
- switch UIApplication.shared.statusBarOrientation {
197
- case .portrait:
198
- videoOrientation = .portrait
199
- case .landscapeLeft:
200
- videoOrientation = .landscapeLeft
201
- case .landscapeRight:
202
- videoOrientation = .landscapeRight
203
- case .portraitUpsideDown:
204
- videoOrientation = .portraitUpsideDown
205
- case .unknown:
206
- fallthrough
207
- @unknown default:
208
- videoOrientation = .portrait
209
- }
210
-
211
- previewLayer?.connection?.videoOrientation = videoOrientation
212
- dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
213
- photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
214
- }
215
-
216
-
217
- func switchCameras() throws {
218
- guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
219
-
220
- captureSession.beginConfiguration()
221
-
222
- func switchToFrontCamera() throws {
223
-
224
- guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
225
- let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
226
-
227
- self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
228
-
229
- captureSession.removeInput(rearCameraInput)
230
-
231
- if captureSession.canAddInput(self.frontCameraInput!) {
232
- captureSession.addInput(self.frontCameraInput!)
233
-
234
- self.currentCameraPosition = .front
235
- } else {
236
- throw CameraControllerError.invalidOperation
237
- }
238
- }
239
-
240
- func switchToRearCamera() throws {
241
-
242
- guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
243
- let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
244
-
245
- self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
246
-
247
- captureSession.removeInput(frontCameraInput)
248
-
249
- if captureSession.canAddInput(self.rearCameraInput!) {
250
- captureSession.addInput(self.rearCameraInput!)
251
-
252
- self.currentCameraPosition = .rear
253
- } else { throw CameraControllerError.invalidOperation }
254
- }
255
-
256
- switch currentCameraPosition {
257
- case .front:
258
- try switchToRearCamera()
259
-
260
- case .rear:
261
- try switchToFrontCamera()
262
- }
263
-
264
- captureSession.commitConfiguration()
265
- }
266
- func setZoom(lens: String) throws {
267
- print(lens)
268
- guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
269
-
270
- captureSession.beginConfiguration()
271
-
272
- guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput) else { throw CameraControllerError.invalidOperation }
273
-
274
- var session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
275
- if(lens == "ultra"){
276
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: AVMediaType.video, position: .unspecified)
277
- } else if(lens == "wide"){
278
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
279
- } else if(lens == "tele"){
280
- session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
281
- }
282
- let cameras = session.devices.compactMap { $0 }
283
- guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
284
-
285
- for camera in cameras {
286
- if camera.position == .front {
287
- self.frontCamera = camera
288
- }
289
-
290
- if camera.position == .back {
291
- self.rearCamera = camera
292
-
293
- try camera.lockForConfiguration()
294
- camera.focusMode = .continuousAutoFocus
295
- camera.unlockForConfiguration()
296
- }
297
- }
298
-
299
- if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
300
- for input in inputs {
301
- captureSession.removeInput(input)
302
- }
303
- }
304
- captureSession.commitConfiguration()
305
-
306
- self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera!)
307
-
308
- //captureSession.removeInput(frontCameraInput)
309
-
310
- if captureSession.canAddInput(self.rearCameraInput!) {
311
- captureSession.addInput(self.rearCameraInput!)
312
- self.currentCameraPosition = .rear
313
- }
314
- }
315
-
316
- func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
317
- guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
318
- let settings = AVCapturePhotoSettings()
319
-
320
- settings.flashMode = self.flashMode
321
- settings.isHighResolutionPhotoEnabled = self.highResolutionOutput
322
-
323
- self.photoOutput?.capturePhoto(with: settings, delegate: self)
324
- self.photoCaptureCompletionBlock = completion
325
- }
326
-
327
- func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
328
- guard let captureSession = captureSession,
329
- captureSession.isRunning else {
330
- completion(nil, CameraControllerError.captureSessionIsMissing)
331
- return
332
- }
333
-
334
- self.sampleBufferCaptureCompletionBlock = completion
335
- }
336
-
337
- func getSupportedFlashModes() throws -> [String] {
338
- var currentCamera: AVCaptureDevice?
339
- switch currentCameraPosition {
340
- case .front:
341
- currentCamera = self.frontCamera!
342
- case .rear:
343
- currentCamera = self.rearCamera!
344
- default: break
345
- }
346
-
347
- guard
348
- let device = currentCamera
349
- else {
350
- throw CameraControllerError.noCamerasAvailable
351
- }
352
-
353
- var supportedFlashModesAsStrings: [String] = []
354
- if device.hasFlash {
355
- guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
356
- throw CameraControllerError.noCamerasAvailable
357
- }
358
-
359
- for flashMode in supportedFlashModes {
360
- var flashModeValue: String?
361
- switch flashMode {
362
- case AVCaptureDevice.FlashMode.off:
363
- flashModeValue = "off"
364
- case AVCaptureDevice.FlashMode.on:
365
- flashModeValue = "on"
366
- case AVCaptureDevice.FlashMode.auto:
367
- flashModeValue = "auto"
368
- default: break
369
- }
370
- if flashModeValue != nil {
371
- supportedFlashModesAsStrings.append(flashModeValue!)
372
- }
373
- }
374
- }
375
- if device.hasTorch {
376
- supportedFlashModesAsStrings.append("torch")
377
- }
378
- return supportedFlashModesAsStrings
379
-
380
- }
381
- func getSupportedCameras() throws -> [String] {
382
- var supportedCameras: [String] = [];
383
- if let device = AVCaptureDevice.default(.builtInUltraWideCamera, for: AVMediaType.video, position: .back) {
384
- supportedCameras.append("ultra")
385
- }
386
- if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) {
387
- supportedCameras.append("wide")
388
- }
389
- if let device = AVCaptureDevice.default(.builtInTelephotoCamera, for: AVMediaType.video, position: .back) {
390
- supportedCameras.append("tele")
391
- }
392
- return supportedCameras;
393
- }
394
-
395
- func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
396
- var currentCamera: AVCaptureDevice?
397
- switch currentCameraPosition {
398
- case .front:
399
- currentCamera = self.frontCamera!
400
- case .rear:
401
- currentCamera = self.rearCamera!
402
- default: break
403
- }
404
-
405
- guard let device = currentCamera else {
406
- throw CameraControllerError.noCamerasAvailable
407
- }
408
-
409
- guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
410
- throw CameraControllerError.invalidOperation
411
- }
412
- if supportedFlashModes.contains(flashMode) {
413
- do {
414
- try device.lockForConfiguration()
415
-
416
- if device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on {
417
- device.torchMode = AVCaptureDevice.TorchMode.off
418
- }
419
- self.flashMode = flashMode
420
- let photoSettings = AVCapturePhotoSettings()
421
- photoSettings.flashMode = flashMode
422
- self.photoOutput?.photoSettingsForSceneMonitoring = photoSettings
423
-
424
- device.unlockForConfiguration()
425
- } catch {
426
- throw CameraControllerError.invalidOperation
427
- }
428
- } else {
429
- throw CameraControllerError.invalidOperation
430
- }
431
- }
432
-
433
- func setTorchMode() throws {
434
- var currentCamera: AVCaptureDevice?
435
- switch currentCameraPosition {
436
- case .front:
437
- currentCamera = self.frontCamera!
438
- case .rear:
439
- currentCamera = self.rearCamera!
440
- default: break
441
- }
442
-
443
- guard
444
- let device = currentCamera,
445
- device.hasTorch,
446
- device.isTorchAvailable
447
- else {
448
- throw CameraControllerError.invalidOperation
449
- }
450
-
451
- do {
452
- try device.lockForConfiguration()
453
- if device.isTorchModeSupported(AVCaptureDevice.TorchMode.on) {
454
- device.torchMode = AVCaptureDevice.TorchMode.on
455
- } else if device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto) {
456
- device.torchMode = AVCaptureDevice.TorchMode.auto
457
- } else {
458
- device.torchMode = AVCaptureDevice.TorchMode.off
459
- }
460
- device.unlockForConfiguration()
461
- } catch {
462
- throw CameraControllerError.invalidOperation
463
- }
464
-
465
- }
466
-
467
- func captureVideo(completion: @escaping (URL?, Error?) -> Void) {
468
- guard let captureSession = self.captureSession, captureSession.isRunning else {
469
- completion(nil, CameraControllerError.captureSessionIsMissing)
470
- return
471
- }
472
- let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
473
- let identifier = UUID()
474
- let randomIdentifier = identifier.uuidString.replacingOccurrences(of: "-", with: "")
475
- let finalIdentifier = String(randomIdentifier.prefix(8))
476
- let fileName="cpcp_video_"+finalIdentifier+".mp4"
477
-
478
- let fileUrl = path.appendingPathComponent(fileName)
479
- try? FileManager.default.removeItem(at: fileUrl)
480
- /*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
481
- self.videoRecordCompletionBlock = completion*/
482
- }
483
-
484
- func stopRecording(completion: @escaping (Error?) -> Void) {
485
- guard let captureSession = self.captureSession, captureSession.isRunning else {
486
- completion(CameraControllerError.captureSessionIsMissing)
487
- return
488
- }
489
- // self.videoOutput?.stopRecording()
490
- }
491
- }
492
-
493
- extension CameraController: UIGestureRecognizerDelegate {
494
- func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
495
- return true
496
- }
497
-
498
- @objc
499
- func handleTap(_ tap: UITapGestureRecognizer) {
500
- guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
501
-
502
- let point = tap.location(in: tap.view)
503
- let devicePoint = self.previewLayer?.captureDevicePointConverted(fromLayerPoint: point)
504
-
505
- do {
506
- try device.lockForConfiguration()
507
- defer { device.unlockForConfiguration() }
508
-
509
- let focusMode = AVCaptureDevice.FocusMode.autoFocus
510
- if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
511
- device.focusPointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
512
- device.focusMode = focusMode
513
- }
514
-
515
- let exposureMode = AVCaptureDevice.ExposureMode.autoExpose
516
- if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
517
- device.exposurePointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
518
- device.exposureMode = exposureMode
519
- }
520
- } catch {
521
- debugPrint(error)
522
- }
523
- }
524
-
525
- @objc
526
- private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
527
- guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
528
-
529
- func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(1.0, min(factor, device.activeFormat.videoMaxZoomFactor)) }
530
-
531
- func update(scale factor: CGFloat) {
532
- do {
533
- try device.lockForConfiguration()
534
- defer { device.unlockForConfiguration() }
535
-
536
- device.videoZoomFactor = factor
537
- } catch {
538
- debugPrint(error)
539
- }
540
- }
541
-
542
- switch pinch.state {
543
- case .began: fallthrough
544
- case .changed:
545
- let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
546
- update(scale: newScaleFactor)
547
- case .ended:
548
- zoomFactor = device.videoZoomFactor
549
- default: break
550
- }
551
- }
552
- }
553
-
554
- extension CameraController: AVCapturePhotoCaptureDelegate {
555
- public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
556
- resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
557
- if let error = error { self.photoCaptureCompletionBlock?(nil, error) } else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
558
- let image = UIImage(data: data) {
559
- self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
560
- } else {
561
- self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
562
- }
563
- }
564
- }
565
-
566
- extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
567
- func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
568
- guard let completion = sampleBufferCaptureCompletionBlock else { return }
569
-
570
- guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
571
- completion(nil, CameraControllerError.unknown)
572
- return
573
- }
574
-
575
- CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
576
- defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
577
-
578
- let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
579
- let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
580
- let width = CVPixelBufferGetWidth(imageBuffer)
581
- let height = CVPixelBufferGetHeight(imageBuffer)
582
- let colorSpace = CGColorSpaceCreateDeviceRGB()
583
- let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
584
- CGImageAlphaInfo.premultipliedFirst.rawValue
585
-
586
- let context = CGContext(
587
- data: baseAddress,
588
- width: width,
589
- height: height,
590
- bitsPerComponent: 8,
591
- bytesPerRow: bytesPerRow,
592
- space: colorSpace,
593
- bitmapInfo: bitmapInfo
594
- )
595
-
596
- guard let cgImage = context?.makeImage() else {
597
- completion(nil, CameraControllerError.unknown)
598
- return
599
- }
600
-
601
- let image = UIImage(cgImage: cgImage)
602
- completion(image.fixedOrientation(), nil)
603
-
604
- sampleBufferCaptureCompletionBlock = nil
605
- }
606
- }
607
-
608
- enum CameraControllerError: Swift.Error {
609
- case captureSessionAlreadyRunning
610
- case captureSessionIsMissing
611
- case inputsAreInvalid
612
- case invalidOperation
613
- case noCamerasAvailable
614
- case unknown
615
- }
616
-
617
- public enum CameraPosition {
618
- case front
619
- case rear
620
- }
621
-
622
- extension CameraControllerError: LocalizedError {
623
- public var errorDescription: String? {
624
- switch self {
625
- case .captureSessionAlreadyRunning:
626
- return NSLocalizedString("Capture Session is Already Running", comment: "Capture Session Already Running")
627
- case .captureSessionIsMissing:
628
- return NSLocalizedString("Capture Session is Missing", comment: "Capture Session Missing")
629
- case .inputsAreInvalid:
630
- return NSLocalizedString("Inputs Are Invalid", comment: "Inputs Are Invalid")
631
- case .invalidOperation:
632
- return NSLocalizedString("Invalid Operation", comment: "invalid Operation")
633
- case .noCamerasAvailable:
634
- return NSLocalizedString("Failed to access device camera(s)", comment: "No Cameras Available")
635
- case .unknown:
636
- return NSLocalizedString("Unknown", comment: "Unknown")
637
-
638
- }
639
- }
640
- }
641
-
642
- extension UIImage {
643
-
644
- func fixedOrientation() -> UIImage? {
645
-
646
- guard imageOrientation != UIImage.Orientation.up else {
647
- // This is default orientation, don't need to do anything
648
- return self.copy() as? UIImage
649
- }
650
-
651
- guard let cgImage = self.cgImage else {
652
- // CGImage is not available
653
- return nil
654
- }
655
-
656
- guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
657
- return nil // Not able to create CGContext
658
- }
659
-
660
- var transform: CGAffineTransform = CGAffineTransform.identity
661
- switch imageOrientation {
662
- case .down, .downMirrored:
663
- transform = transform.translatedBy(x: size.width, y: size.height)
664
- transform = transform.rotated(by: CGFloat.pi)
665
- print("down")
666
- break
667
- case .left, .leftMirrored:
668
- transform = transform.translatedBy(x: size.width, y: 0)
669
- transform = transform.rotated(by: CGFloat.pi / 2.0)
670
- print("left")
671
- break
672
- case .right, .rightMirrored:
673
- transform = transform.translatedBy(x: 0, y: size.height)
674
- transform = transform.rotated(by: CGFloat.pi / -2.0)
675
- print("right")
676
- break
677
- case .up, .upMirrored:
678
- break
679
- }
680
-
681
- // Flip image one more time if needed to, this is to prevent flipped image
682
- switch imageOrientation {
683
- case .upMirrored, .downMirrored:
684
- transform.translatedBy(x: size.width, y: 0)
685
- transform.scaledBy(x: -1, y: 1)
686
- break
687
- case .leftMirrored, .rightMirrored:
688
- transform.translatedBy(x: size.height, y: 0)
689
- transform.scaledBy(x: -1, y: 1)
690
- case .up, .down, .left, .right:
691
- break
692
- }
693
-
694
- ctx.concatenate(transform)
695
-
696
- switch imageOrientation {
697
- case .left, .leftMirrored, .right, .rightMirrored:
698
- ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
699
- default:
700
- ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
701
- break
702
- }
703
- guard let newCGImage = ctx.makeImage() else { return nil }
704
- return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
705
- }
706
- }
707
-
708
- extension CameraController: AVCaptureFileOutputRecordingDelegate {
709
- func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
710
- /*if error == nil {
711
- self.videoRecordCompletionBlock?(outputFileURL, nil)
712
- } else {
713
- self.videoRecordCompletionBlock?(nil, error)
714
- }*/
715
- }
716
- }
1
+ //
2
+ // CameraController.swift
3
+ // Plugin
4
+ //
5
+ // Created by Ariel Hernandez Musa on 7/14/19.
6
+ // Copyright © 2019 Max Lynch. All rights reserved.
7
+ //
8
+
9
+ import AVFoundation
10
+ import UIKit
11
+
12
+ class CameraController: NSObject {
13
+ var captureSession: AVCaptureSession?
14
+
15
+ var currentCameraPosition: CameraPosition?
16
+
17
+ var frontCamera: AVCaptureDevice?
18
+ var frontCameraInput: AVCaptureDeviceInput?
19
+
20
+ var dataOutput: AVCaptureVideoDataOutput?
21
+ var photoOutput: AVCapturePhotoOutput?
22
+
23
+ var rearCamera: AVCaptureDevice?
24
+ var rearCameraInput: AVCaptureDeviceInput?
25
+
26
+ var previewLayer: AVCaptureVideoPreviewLayer?
27
+
28
+ var flashMode = AVCaptureDevice.FlashMode.off
29
+ var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
30
+
31
+ var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
32
+
33
+ var highResolutionOutput: Bool = false
34
+
35
+ var audioDevice: AVCaptureDevice?
36
+ var audioInput: AVCaptureDeviceInput?
37
+
38
+ var zoomFactor: CGFloat = 1.0
39
+ }
40
+ extension CameraController {
41
+ func prepare(cameraPosition: String, zoomFactor: String, disableAudio: Bool, completionHandler: @escaping (Error?) -> Void) {
42
+
43
+ func createCaptureSession() {
44
+ self.captureSession = AVCaptureSession()
45
+ }
46
+
47
+ func configureCaptureDevices() throws {
48
+ var session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
49
+ if(zoomFactor == "ultra"){
50
+ session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: AVMediaType.video, position: .unspecified)
51
+ } else if(zoomFactor == "wide"){
52
+ session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
53
+ } else if(zoomFactor == "tele"){
54
+ session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
55
+ }
56
+ let cameras = session.devices.compactMap { $0 }
57
+ guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
58
+
59
+ for camera in cameras {
60
+ if camera.position == .front {
61
+ self.frontCamera = camera
62
+ }
63
+
64
+ if camera.position == .back {
65
+ self.rearCamera = camera
66
+
67
+ try camera.lockForConfiguration()
68
+ if camera.isFocusModeSupported(.continuousAutoFocus){
69
+ camera.focusMode = .continuousAutoFocus
70
+ }
71
+ camera.unlockForConfiguration()
72
+ }
73
+ }
74
+ if disableAudio == false {
75
+ self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
76
+ }
77
+ }
78
+
79
+ func configureDeviceInputs() throws {
80
+ guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
81
+
82
+ if cameraPosition == "rear" {
83
+ if let rearCamera = self.rearCamera {
84
+ self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
85
+
86
+ if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) }
87
+
88
+ self.currentCameraPosition = .rear
89
+ }
90
+ } else if cameraPosition == "front" {
91
+ if let frontCamera = self.frontCamera {
92
+ self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
93
+
94
+ if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) } else { throw CameraControllerError.inputsAreInvalid }
95
+
96
+ self.currentCameraPosition = .front
97
+ }
98
+ } else { throw CameraControllerError.noCamerasAvailable }
99
+
100
+ // Add audio input
101
+ if disableAudio == false {
102
+ if let audioDevice = self.audioDevice {
103
+ self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
104
+ if captureSession.canAddInput(self.audioInput!) {
105
+ captureSession.addInput(self.audioInput!)
106
+ } else {
107
+ throw CameraControllerError.inputsAreInvalid
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ func configurePhotoOutput() throws {
114
+ guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
115
+
116
+ self.photoOutput = AVCapturePhotoOutput()
117
+ self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
118
+ self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
119
+ if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
120
+ captureSession.startRunning()
121
+ }
122
+
123
+ func configureDataOutput() throws {
124
+ guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
125
+
126
+ self.dataOutput = AVCaptureVideoDataOutput()
127
+ self.dataOutput?.videoSettings = [
128
+ (kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
129
+ ]
130
+ self.dataOutput?.alwaysDiscardsLateVideoFrames = true
131
+ if captureSession.canAddOutput(self.dataOutput!) {
132
+ captureSession.addOutput(self.dataOutput!)
133
+ }
134
+
135
+ captureSession.commitConfiguration()
136
+
137
+ let queue = DispatchQueue(label: "DataOutput", attributes: [])
138
+ self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
139
+ }
140
+
141
+ DispatchQueue(label: "prepare").async {
142
+ do {
143
+ createCaptureSession()
144
+ try configureCaptureDevices()
145
+ try configureDeviceInputs()
146
+ try configurePhotoOutput()
147
+ try configureDataOutput()
148
+ // try configureVideoOutput()
149
+ } catch {
150
+ DispatchQueue.main.async {
151
+ completionHandler(error)
152
+ }
153
+
154
+ return
155
+ }
156
+
157
+ DispatchQueue.main.async {
158
+ completionHandler(nil)
159
+ }
160
+ }
161
+ }
162
+
163
+ func displayPreview(on view: UIView) throws {
164
+ guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
165
+
166
+ self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
167
+ self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
168
+
169
+ view.layer.insertSublayer(self.previewLayer!, at: 0)
170
+ self.previewLayer?.frame = view.frame
171
+
172
+ updateVideoOrientation()
173
+ }
174
+
175
+ func setupGestures(target: UIView, enableZoom: Bool) {
176
+ setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
177
+ if enableZoom {
178
+ setupPinchGesture(target: target, selector: #selector(handlePinch(_:)), delegate: self)
179
+ }
180
+ }
181
+
182
+ func setupTapGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
183
+ let tapGesture = UITapGestureRecognizer(target: self, action: selector)
184
+ tapGesture.delegate = delegate
185
+ target.addGestureRecognizer(tapGesture)
186
+ }
187
+
188
+ func setupPinchGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
189
+ let pinchGesture = UIPinchGestureRecognizer(target: self, action: selector)
190
+ pinchGesture.delegate = delegate
191
+ target.addGestureRecognizer(pinchGesture)
192
+ }
193
+
194
+ func updateVideoOrientation() {
195
+ assert(Thread.isMainThread) // UIApplication.statusBarOrientation requires the main thread.
196
+
197
+ let videoOrientation: AVCaptureVideoOrientation
198
+ switch UIApplication.shared.statusBarOrientation {
199
+ case .portrait:
200
+ videoOrientation = .portrait
201
+ case .landscapeLeft:
202
+ videoOrientation = .landscapeLeft
203
+ case .landscapeRight:
204
+ videoOrientation = .landscapeRight
205
+ case .portraitUpsideDown:
206
+ videoOrientation = .portraitUpsideDown
207
+ case .unknown:
208
+ fallthrough
209
+ @unknown default:
210
+ videoOrientation = .portrait
211
+ }
212
+
213
+ previewLayer?.connection?.videoOrientation = videoOrientation
214
+ dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
215
+ photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
216
+ }
217
+
218
+
219
+ func switchCameras() throws {
220
+ guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
221
+
222
+ captureSession.beginConfiguration()
223
+
224
+ func switchToFrontCamera() throws {
225
+
226
+ guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput),
227
+ let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
228
+
229
+ self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
230
+
231
+ captureSession.removeInput(rearCameraInput)
232
+
233
+ if captureSession.canAddInput(self.frontCameraInput!) {
234
+ captureSession.addInput(self.frontCameraInput!)
235
+
236
+ self.currentCameraPosition = .front
237
+ } else {
238
+ throw CameraControllerError.invalidOperation
239
+ }
240
+ }
241
+
242
+ func switchToRearCamera() throws {
243
+
244
+ guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput),
245
+ let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
246
+
247
+ self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
248
+
249
+ captureSession.removeInput(frontCameraInput)
250
+
251
+ if captureSession.canAddInput(self.rearCameraInput!) {
252
+ captureSession.addInput(self.rearCameraInput!)
253
+
254
+ self.currentCameraPosition = .rear
255
+ } else { throw CameraControllerError.invalidOperation }
256
+ }
257
+
258
+ switch currentCameraPosition {
259
+ case .front:
260
+ try switchToRearCamera()
261
+
262
+ case .rear:
263
+ try switchToFrontCamera()
264
+ }
265
+
266
+ captureSession.commitConfiguration()
267
+ }
268
+ func setZoom(lens: String) throws {
269
+ print(lens)
270
+ guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
271
+
272
+ captureSession.beginConfiguration()
273
+
274
+ guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput) else { throw CameraControllerError.invalidOperation }
275
+
276
+ var session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
277
+ if(lens == "ultra"){
278
+ session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: AVMediaType.video, position: .unspecified)
279
+ } else if(lens == "wide"){
280
+ session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
281
+ } else if(lens == "tele"){
282
+ session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInTelephotoCamera], mediaType: AVMediaType.video, position: .unspecified)
283
+ }
284
+ let cameras = session.devices.compactMap { $0 }
285
+ guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
286
+
287
+ for camera in cameras {
288
+ if camera.position == .front {
289
+ self.frontCamera = camera
290
+ }
291
+
292
+ if camera.position == .back {
293
+ self.rearCamera = camera
294
+
295
+ try camera.lockForConfiguration()
296
+ if camera.isFocusModeSupported(.continuousAutoFocus){
297
+ camera.focusMode = .continuousAutoFocus
298
+ }
299
+ camera.unlockForConfiguration()
300
+ }
301
+ }
302
+
303
+ if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] {
304
+ for input in inputs {
305
+ captureSession.removeInput(input)
306
+ }
307
+ }
308
+ captureSession.commitConfiguration()
309
+
310
+ self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera!)
311
+
312
+ //captureSession.removeInput(frontCameraInput)
313
+
314
+ if captureSession.canAddInput(self.rearCameraInput!) {
315
+ captureSession.addInput(self.rearCameraInput!)
316
+ self.currentCameraPosition = .rear
317
+ }
318
+ }
319
+
320
+ func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
321
+ guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
322
+ let settings = AVCapturePhotoSettings()
323
+
324
+ settings.flashMode = self.flashMode
325
+ settings.isHighResolutionPhotoEnabled = self.highResolutionOutput
326
+
327
+ self.photoOutput?.capturePhoto(with: settings, delegate: self)
328
+ self.photoCaptureCompletionBlock = completion
329
+ }
330
+
331
+ func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
332
+ guard let captureSession = captureSession,
333
+ captureSession.isRunning else {
334
+ completion(nil, CameraControllerError.captureSessionIsMissing)
335
+ return
336
+ }
337
+
338
+ self.sampleBufferCaptureCompletionBlock = completion
339
+ }
340
+
341
+ func getSupportedFlashModes() throws -> [String] {
342
+ var currentCamera: AVCaptureDevice?
343
+ switch currentCameraPosition {
344
+ case .front:
345
+ currentCamera = self.frontCamera!
346
+ case .rear:
347
+ currentCamera = self.rearCamera!
348
+ default: break
349
+ }
350
+
351
+ guard
352
+ let device = currentCamera
353
+ else {
354
+ throw CameraControllerError.noCamerasAvailable
355
+ }
356
+
357
+ var supportedFlashModesAsStrings: [String] = []
358
+ if device.hasFlash {
359
+ guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
360
+ throw CameraControllerError.noCamerasAvailable
361
+ }
362
+
363
+ for flashMode in supportedFlashModes {
364
+ var flashModeValue: String?
365
+ switch flashMode {
366
+ case AVCaptureDevice.FlashMode.off:
367
+ flashModeValue = "off"
368
+ case AVCaptureDevice.FlashMode.on:
369
+ flashModeValue = "on"
370
+ case AVCaptureDevice.FlashMode.auto:
371
+ flashModeValue = "auto"
372
+ default: break
373
+ }
374
+ if flashModeValue != nil {
375
+ supportedFlashModesAsStrings.append(flashModeValue!)
376
+ }
377
+ }
378
+ }
379
+ if device.hasTorch {
380
+ supportedFlashModesAsStrings.append("torch")
381
+ }
382
+ return supportedFlashModesAsStrings
383
+
384
+ }
385
+ func getSupportedCameras() throws -> [String] {
386
+ var supportedCameras: [String] = [];
387
+ if let device = AVCaptureDevice.default(.builtInUltraWideCamera, for: AVMediaType.video, position: .back) {
388
+ supportedCameras.append("ultra")
389
+ }
390
+ if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) {
391
+ supportedCameras.append("wide")
392
+ }
393
+ if let device = AVCaptureDevice.default(.builtInTelephotoCamera, for: AVMediaType.video, position: .back) {
394
+ supportedCameras.append("tele")
395
+ }
396
+ return supportedCameras;
397
+ }
398
+
399
+ func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
400
+ var currentCamera: AVCaptureDevice?
401
+ switch currentCameraPosition {
402
+ case .front:
403
+ currentCamera = self.frontCamera!
404
+ case .rear:
405
+ currentCamera = self.rearCamera!
406
+ default: break
407
+ }
408
+
409
+ guard let device = currentCamera else {
410
+ throw CameraControllerError.noCamerasAvailable
411
+ }
412
+
413
+ guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
414
+ throw CameraControllerError.invalidOperation
415
+ }
416
+ if supportedFlashModes.contains(flashMode) {
417
+ do {
418
+ try device.lockForConfiguration()
419
+
420
+ if device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on {
421
+ device.torchMode = AVCaptureDevice.TorchMode.off
422
+ }
423
+ self.flashMode = flashMode
424
+ let photoSettings = AVCapturePhotoSettings()
425
+ photoSettings.flashMode = flashMode
426
+ self.photoOutput?.photoSettingsForSceneMonitoring = photoSettings
427
+
428
+ device.unlockForConfiguration()
429
+ } catch {
430
+ throw CameraControllerError.invalidOperation
431
+ }
432
+ } else {
433
+ throw CameraControllerError.invalidOperation
434
+ }
435
+ }
436
+
437
+ func setTorchMode() throws {
438
+ var currentCamera: AVCaptureDevice?
439
+ switch currentCameraPosition {
440
+ case .front:
441
+ currentCamera = self.frontCamera!
442
+ case .rear:
443
+ currentCamera = self.rearCamera!
444
+ default: break
445
+ }
446
+
447
+ guard
448
+ let device = currentCamera,
449
+ device.hasTorch,
450
+ device.isTorchAvailable
451
+ else {
452
+ throw CameraControllerError.invalidOperation
453
+ }
454
+
455
+ do {
456
+ try device.lockForConfiguration()
457
+ if device.isTorchModeSupported(AVCaptureDevice.TorchMode.on) {
458
+ device.torchMode = AVCaptureDevice.TorchMode.on
459
+ } else if device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto) {
460
+ device.torchMode = AVCaptureDevice.TorchMode.auto
461
+ } else {
462
+ device.torchMode = AVCaptureDevice.TorchMode.off
463
+ }
464
+ device.unlockForConfiguration()
465
+ } catch {
466
+ throw CameraControllerError.invalidOperation
467
+ }
468
+
469
+ }
470
+
471
+ func captureVideo(completion: @escaping (URL?, Error?) -> Void) {
472
+ guard let captureSession = self.captureSession, captureSession.isRunning else {
473
+ completion(nil, CameraControllerError.captureSessionIsMissing)
474
+ return
475
+ }
476
+ let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
477
+ let identifier = UUID()
478
+ let randomIdentifier = identifier.uuidString.replacingOccurrences(of: "-", with: "")
479
+ let finalIdentifier = String(randomIdentifier.prefix(8))
480
+ let fileName="cpcp_video_"+finalIdentifier+".mp4"
481
+
482
+ let fileUrl = path.appendingPathComponent(fileName)
483
+ try? FileManager.default.removeItem(at: fileUrl)
484
+ /*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
485
+ self.videoRecordCompletionBlock = completion*/
486
+ }
487
+
488
+ func stopRecording(completion: @escaping (Error?) -> Void) {
489
+ guard let captureSession = self.captureSession, captureSession.isRunning else {
490
+ completion(CameraControllerError.captureSessionIsMissing)
491
+ return
492
+ }
493
+ // self.videoOutput?.stopRecording()
494
+ }
495
+ }
496
+
497
+ extension CameraController: UIGestureRecognizerDelegate {
498
+ func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
499
+ return true
500
+ }
501
+
502
+ @objc
503
+ func handleTap(_ tap: UITapGestureRecognizer) {
504
+ guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
505
+
506
+ let point = tap.location(in: tap.view)
507
+ let devicePoint = self.previewLayer?.captureDevicePointConverted(fromLayerPoint: point)
508
+
509
+ do {
510
+ try device.lockForConfiguration()
511
+ defer { device.unlockForConfiguration() }
512
+
513
+ let focusMode = AVCaptureDevice.FocusMode.autoFocus
514
+ if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
515
+ device.focusPointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
516
+ device.focusMode = focusMode
517
+ }
518
+
519
+ let exposureMode = AVCaptureDevice.ExposureMode.autoExpose
520
+ if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
521
+ device.exposurePointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
522
+ device.exposureMode = exposureMode
523
+ }
524
+ } catch {
525
+ debugPrint(error)
526
+ }
527
+ }
528
+
529
+ @objc
530
+ private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
531
+ guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
532
+
533
+ func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(1.0, min(factor, device.activeFormat.videoMaxZoomFactor)) }
534
+
535
+ func update(scale factor: CGFloat) {
536
+ do {
537
+ try device.lockForConfiguration()
538
+ defer { device.unlockForConfiguration() }
539
+
540
+ device.videoZoomFactor = factor
541
+ } catch {
542
+ debugPrint(error)
543
+ }
544
+ }
545
+
546
+ switch pinch.state {
547
+ case .began: fallthrough
548
+ case .changed:
549
+ let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
550
+ update(scale: newScaleFactor)
551
+ case .ended:
552
+ zoomFactor = device.videoZoomFactor
553
+ default: break
554
+ }
555
+ }
556
+ }
557
+
558
+ extension CameraController: AVCapturePhotoCaptureDelegate {
559
+ public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
560
+ resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
561
+ if let error = error { self.photoCaptureCompletionBlock?(nil, error) } else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
562
+ let image = UIImage(data: data) {
563
+ self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
564
+ } else {
565
+ self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
566
+ }
567
+ }
568
+ }
569
+
570
+ extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
571
+ func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
572
+ guard let completion = sampleBufferCaptureCompletionBlock else { return }
573
+
574
+ guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
575
+ completion(nil, CameraControllerError.unknown)
576
+ return
577
+ }
578
+
579
+ CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
580
+ defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
581
+
582
+ let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
583
+ let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
584
+ let width = CVPixelBufferGetWidth(imageBuffer)
585
+ let height = CVPixelBufferGetHeight(imageBuffer)
586
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
587
+ let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
588
+ CGImageAlphaInfo.premultipliedFirst.rawValue
589
+
590
+ let context = CGContext(
591
+ data: baseAddress,
592
+ width: width,
593
+ height: height,
594
+ bitsPerComponent: 8,
595
+ bytesPerRow: bytesPerRow,
596
+ space: colorSpace,
597
+ bitmapInfo: bitmapInfo
598
+ )
599
+
600
+ guard let cgImage = context?.makeImage() else {
601
+ completion(nil, CameraControllerError.unknown)
602
+ return
603
+ }
604
+
605
+ let image = UIImage(cgImage: cgImage)
606
+ completion(image.fixedOrientation(), nil)
607
+
608
+ sampleBufferCaptureCompletionBlock = nil
609
+ }
610
+ }
611
+
612
+ enum CameraControllerError: Swift.Error {
613
+ case captureSessionAlreadyRunning
614
+ case captureSessionIsMissing
615
+ case inputsAreInvalid
616
+ case invalidOperation
617
+ case noCamerasAvailable
618
+ case unknown
619
+ }
620
+
621
+ public enum CameraPosition {
622
+ case front
623
+ case rear
624
+ }
625
+
626
+ extension CameraControllerError: LocalizedError {
627
+ public var errorDescription: String? {
628
+ switch self {
629
+ case .captureSessionAlreadyRunning:
630
+ return NSLocalizedString("Capture Session is Already Running", comment: "Capture Session Already Running")
631
+ case .captureSessionIsMissing:
632
+ return NSLocalizedString("Capture Session is Missing", comment: "Capture Session Missing")
633
+ case .inputsAreInvalid:
634
+ return NSLocalizedString("Inputs Are Invalid", comment: "Inputs Are Invalid")
635
+ case .invalidOperation:
636
+ return NSLocalizedString("Invalid Operation", comment: "invalid Operation")
637
+ case .noCamerasAvailable:
638
+ return NSLocalizedString("Failed to access device camera(s)", comment: "No Cameras Available")
639
+ case .unknown:
640
+ return NSLocalizedString("Unknown", comment: "Unknown")
641
+
642
+ }
643
+ }
644
+ }
645
+
646
+ extension UIImage {
647
+
648
+ func fixedOrientation() -> UIImage? {
649
+
650
+ guard imageOrientation != UIImage.Orientation.up else {
651
+ // This is default orientation, don't need to do anything
652
+ return self.copy() as? UIImage
653
+ }
654
+
655
+ guard let cgImage = self.cgImage else {
656
+ // CGImage is not available
657
+ return nil
658
+ }
659
+
660
+ guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
661
+ return nil // Not able to create CGContext
662
+ }
663
+
664
+ var transform: CGAffineTransform = CGAffineTransform.identity
665
+ switch imageOrientation {
666
+ case .down, .downMirrored:
667
+ transform = transform.translatedBy(x: size.width, y: size.height)
668
+ transform = transform.rotated(by: CGFloat.pi)
669
+ print("down")
670
+ break
671
+ case .left, .leftMirrored:
672
+ transform = transform.translatedBy(x: size.width, y: 0)
673
+ transform = transform.rotated(by: CGFloat.pi / 2.0)
674
+ print("left")
675
+ break
676
+ case .right, .rightMirrored:
677
+ transform = transform.translatedBy(x: 0, y: size.height)
678
+ transform = transform.rotated(by: CGFloat.pi / -2.0)
679
+ print("right")
680
+ break
681
+ case .up, .upMirrored:
682
+ break
683
+ }
684
+
685
+ // Flip image one more time if needed to, this is to prevent flipped image
686
+ switch imageOrientation {
687
+ case .upMirrored, .downMirrored:
688
+ transform.translatedBy(x: size.width, y: 0)
689
+ transform.scaledBy(x: -1, y: 1)
690
+ break
691
+ case .leftMirrored, .rightMirrored:
692
+ transform.translatedBy(x: size.height, y: 0)
693
+ transform.scaledBy(x: -1, y: 1)
694
+ case .up, .down, .left, .right:
695
+ break
696
+ }
697
+
698
+ ctx.concatenate(transform)
699
+
700
+ switch imageOrientation {
701
+ case .left, .leftMirrored, .right, .rightMirrored:
702
+ ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
703
+ default:
704
+ ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
705
+ break
706
+ }
707
+ guard let newCGImage = ctx.makeImage() else { return nil }
708
+ return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
709
+ }
710
+ }
711
+
712
+ extension CameraController: AVCaptureFileOutputRecordingDelegate {
713
+ func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
714
+ /*if error == nil {
715
+ self.videoRecordCompletionBlock?(outputFileURL, nil)
716
+ } else {
717
+ self.videoRecordCompletionBlock?(nil, error)
718
+ }*/
719
+ }
720
+ }