capacitor-community-multilens-camerapreview 0.0.4 → 0.0.6

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 -1005
  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 +716 -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 +300 -308
  37. package/package.json +78 -78
@@ -1,716 +1,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
- 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
+ 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
+ }