capacitor-community-multilens-camerapreview 0.0.1

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 -0
  2. package/README.md +210 -0
  3. package/android/build.gradle +55 -0
  4. package/android/src/main/AndroidManifest.xml +5 -0
  5. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +1005 -0
  6. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +547 -0
  7. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +23 -0
  8. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +29 -0
  9. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +386 -0
  10. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +24 -0
  11. package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
  12. package/android/src/main/res/layout/camera_activity.xml +68 -0
  13. package/android/src/main/res/values/camera_ids.xml +4 -0
  14. package/android/src/main/res/values/camera_theme.xml +9 -0
  15. package/android/src/main/res/values/colors.xml +3 -0
  16. package/android/src/main/res/values/strings.xml +3 -0
  17. package/android/src/main/res/values/styles.xml +3 -0
  18. package/dist/docs.json +390 -0
  19. package/dist/esm/definitions.d.ts +79 -0
  20. package/dist/esm/definitions.js +2 -0
  21. package/dist/esm/definitions.js.map +1 -0
  22. package/dist/esm/index.d.ts +4 -0
  23. package/dist/esm/index.js +7 -0
  24. package/dist/esm/index.js.map +1 -0
  25. package/dist/esm/web.d.ts +27 -0
  26. package/dist/esm/web.js +153 -0
  27. package/dist/esm/web.js.map +1 -0
  28. package/dist/plugin.cjs.js +164 -0
  29. package/dist/plugin.cjs.js.map +1 -0
  30. package/dist/plugin.js +167 -0
  31. package/dist/plugin.js.map +1 -0
  32. package/ios/Plugin/CameraController.swift +667 -0
  33. package/ios/Plugin/Info.plist +24 -0
  34. package/ios/Plugin/Plugin.h +10 -0
  35. package/ios/Plugin/Plugin.m +17 -0
  36. package/ios/Plugin/Plugin.swift +300 -0
  37. package/package.json +78 -0
@@ -0,0 +1,667 @@
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
+
267
+ func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
268
+ guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
269
+ let settings = AVCapturePhotoSettings()
270
+
271
+ settings.flashMode = self.flashMode
272
+ settings.isHighResolutionPhotoEnabled = self.highResolutionOutput
273
+
274
+ self.photoOutput?.capturePhoto(with: settings, delegate: self)
275
+ self.photoCaptureCompletionBlock = completion
276
+ }
277
+
278
+ func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
279
+ guard let captureSession = captureSession,
280
+ captureSession.isRunning else {
281
+ completion(nil, CameraControllerError.captureSessionIsMissing)
282
+ return
283
+ }
284
+
285
+ self.sampleBufferCaptureCompletionBlock = completion
286
+ }
287
+
288
+ func getSupportedFlashModes() throws -> [String] {
289
+ var currentCamera: AVCaptureDevice?
290
+ switch currentCameraPosition {
291
+ case .front:
292
+ currentCamera = self.frontCamera!
293
+ case .rear:
294
+ currentCamera = self.rearCamera!
295
+ default: break
296
+ }
297
+
298
+ guard
299
+ let device = currentCamera
300
+ else {
301
+ throw CameraControllerError.noCamerasAvailable
302
+ }
303
+
304
+ var supportedFlashModesAsStrings: [String] = []
305
+ if device.hasFlash {
306
+ guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
307
+ throw CameraControllerError.noCamerasAvailable
308
+ }
309
+
310
+ for flashMode in supportedFlashModes {
311
+ var flashModeValue: String?
312
+ switch flashMode {
313
+ case AVCaptureDevice.FlashMode.off:
314
+ flashModeValue = "off"
315
+ case AVCaptureDevice.FlashMode.on:
316
+ flashModeValue = "on"
317
+ case AVCaptureDevice.FlashMode.auto:
318
+ flashModeValue = "auto"
319
+ default: break
320
+ }
321
+ if flashModeValue != nil {
322
+ supportedFlashModesAsStrings.append(flashModeValue!)
323
+ }
324
+ }
325
+ }
326
+ if device.hasTorch {
327
+ supportedFlashModesAsStrings.append("torch")
328
+ }
329
+ return supportedFlashModesAsStrings
330
+
331
+ }
332
+ func getSupportedCameras() throws -> [String] {
333
+ var supportedCameras: [String] = [];
334
+ if let device = AVCaptureDevice.default(.builtInUltraWideCamera, for: AVMediaType.video, position: .back) {
335
+ supportedCameras.append("ultra")
336
+ }
337
+ if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back) {
338
+ supportedCameras.append("wide")
339
+ }
340
+ if let device = AVCaptureDevice.default(.builtInTelephotoCamera, for: AVMediaType.video, position: .back) {
341
+ supportedCameras.append("tele")
342
+ }
343
+ return supportedCameras;
344
+ }
345
+
346
+ func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
347
+ var currentCamera: AVCaptureDevice?
348
+ switch currentCameraPosition {
349
+ case .front:
350
+ currentCamera = self.frontCamera!
351
+ case .rear:
352
+ currentCamera = self.rearCamera!
353
+ default: break
354
+ }
355
+
356
+ guard let device = currentCamera else {
357
+ throw CameraControllerError.noCamerasAvailable
358
+ }
359
+
360
+ guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
361
+ throw CameraControllerError.invalidOperation
362
+ }
363
+ if supportedFlashModes.contains(flashMode) {
364
+ do {
365
+ try device.lockForConfiguration()
366
+
367
+ if device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on {
368
+ device.torchMode = AVCaptureDevice.TorchMode.off
369
+ }
370
+ self.flashMode = flashMode
371
+ let photoSettings = AVCapturePhotoSettings()
372
+ photoSettings.flashMode = flashMode
373
+ self.photoOutput?.photoSettingsForSceneMonitoring = photoSettings
374
+
375
+ device.unlockForConfiguration()
376
+ } catch {
377
+ throw CameraControllerError.invalidOperation
378
+ }
379
+ } else {
380
+ throw CameraControllerError.invalidOperation
381
+ }
382
+ }
383
+
384
+ func setTorchMode() throws {
385
+ var currentCamera: AVCaptureDevice?
386
+ switch currentCameraPosition {
387
+ case .front:
388
+ currentCamera = self.frontCamera!
389
+ case .rear:
390
+ currentCamera = self.rearCamera!
391
+ default: break
392
+ }
393
+
394
+ guard
395
+ let device = currentCamera,
396
+ device.hasTorch,
397
+ device.isTorchAvailable
398
+ else {
399
+ throw CameraControllerError.invalidOperation
400
+ }
401
+
402
+ do {
403
+ try device.lockForConfiguration()
404
+ if device.isTorchModeSupported(AVCaptureDevice.TorchMode.on) {
405
+ device.torchMode = AVCaptureDevice.TorchMode.on
406
+ } else if device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto) {
407
+ device.torchMode = AVCaptureDevice.TorchMode.auto
408
+ } else {
409
+ device.torchMode = AVCaptureDevice.TorchMode.off
410
+ }
411
+ device.unlockForConfiguration()
412
+ } catch {
413
+ throw CameraControllerError.invalidOperation
414
+ }
415
+
416
+ }
417
+
418
+ func captureVideo(completion: @escaping (URL?, Error?) -> Void) {
419
+ guard let captureSession = self.captureSession, captureSession.isRunning else {
420
+ completion(nil, CameraControllerError.captureSessionIsMissing)
421
+ return
422
+ }
423
+ let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
424
+ let identifier = UUID()
425
+ let randomIdentifier = identifier.uuidString.replacingOccurrences(of: "-", with: "")
426
+ let finalIdentifier = String(randomIdentifier.prefix(8))
427
+ let fileName="cpcp_video_"+finalIdentifier+".mp4"
428
+
429
+ let fileUrl = path.appendingPathComponent(fileName)
430
+ try? FileManager.default.removeItem(at: fileUrl)
431
+ /*videoOutput!.startRecording(to: fileUrl, recordingDelegate: self)
432
+ self.videoRecordCompletionBlock = completion*/
433
+ }
434
+
435
+ func stopRecording(completion: @escaping (Error?) -> Void) {
436
+ guard let captureSession = self.captureSession, captureSession.isRunning else {
437
+ completion(CameraControllerError.captureSessionIsMissing)
438
+ return
439
+ }
440
+ // self.videoOutput?.stopRecording()
441
+ }
442
+ }
443
+
444
+ extension CameraController: UIGestureRecognizerDelegate {
445
+ func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
446
+ return true
447
+ }
448
+
449
+ @objc
450
+ func handleTap(_ tap: UITapGestureRecognizer) {
451
+ guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
452
+
453
+ let point = tap.location(in: tap.view)
454
+ let devicePoint = self.previewLayer?.captureDevicePointConverted(fromLayerPoint: point)
455
+
456
+ do {
457
+ try device.lockForConfiguration()
458
+ defer { device.unlockForConfiguration() }
459
+
460
+ let focusMode = AVCaptureDevice.FocusMode.autoFocus
461
+ if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
462
+ device.focusPointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
463
+ device.focusMode = focusMode
464
+ }
465
+
466
+ let exposureMode = AVCaptureDevice.ExposureMode.autoExpose
467
+ if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
468
+ device.exposurePointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
469
+ device.exposureMode = exposureMode
470
+ }
471
+ } catch {
472
+ debugPrint(error)
473
+ }
474
+ }
475
+
476
+ @objc
477
+ private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
478
+ guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
479
+
480
+ func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(1.0, min(factor, device.activeFormat.videoMaxZoomFactor)) }
481
+
482
+ func update(scale factor: CGFloat) {
483
+ do {
484
+ try device.lockForConfiguration()
485
+ defer { device.unlockForConfiguration() }
486
+
487
+ device.videoZoomFactor = factor
488
+ } catch {
489
+ debugPrint(error)
490
+ }
491
+ }
492
+
493
+ switch pinch.state {
494
+ case .began: fallthrough
495
+ case .changed:
496
+ let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
497
+ update(scale: newScaleFactor)
498
+ case .ended:
499
+ zoomFactor = device.videoZoomFactor
500
+ default: break
501
+ }
502
+ }
503
+ }
504
+
505
+ extension CameraController: AVCapturePhotoCaptureDelegate {
506
+ public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
507
+ resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
508
+ if let error = error { self.photoCaptureCompletionBlock?(nil, error) } else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
509
+ let image = UIImage(data: data) {
510
+ self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
511
+ } else {
512
+ self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
513
+ }
514
+ }
515
+ }
516
+
517
+ extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
518
+ func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
519
+ guard let completion = sampleBufferCaptureCompletionBlock else { return }
520
+
521
+ guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
522
+ completion(nil, CameraControllerError.unknown)
523
+ return
524
+ }
525
+
526
+ CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
527
+ defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
528
+
529
+ let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
530
+ let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
531
+ let width = CVPixelBufferGetWidth(imageBuffer)
532
+ let height = CVPixelBufferGetHeight(imageBuffer)
533
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
534
+ let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
535
+ CGImageAlphaInfo.premultipliedFirst.rawValue
536
+
537
+ let context = CGContext(
538
+ data: baseAddress,
539
+ width: width,
540
+ height: height,
541
+ bitsPerComponent: 8,
542
+ bytesPerRow: bytesPerRow,
543
+ space: colorSpace,
544
+ bitmapInfo: bitmapInfo
545
+ )
546
+
547
+ guard let cgImage = context?.makeImage() else {
548
+ completion(nil, CameraControllerError.unknown)
549
+ return
550
+ }
551
+
552
+ let image = UIImage(cgImage: cgImage)
553
+ completion(image.fixedOrientation(), nil)
554
+
555
+ sampleBufferCaptureCompletionBlock = nil
556
+ }
557
+ }
558
+
559
+ enum CameraControllerError: Swift.Error {
560
+ case captureSessionAlreadyRunning
561
+ case captureSessionIsMissing
562
+ case inputsAreInvalid
563
+ case invalidOperation
564
+ case noCamerasAvailable
565
+ case unknown
566
+ }
567
+
568
+ public enum CameraPosition {
569
+ case front
570
+ case rear
571
+ }
572
+
573
+ extension CameraControllerError: LocalizedError {
574
+ public var errorDescription: String? {
575
+ switch self {
576
+ case .captureSessionAlreadyRunning:
577
+ return NSLocalizedString("Capture Session is Already Running", comment: "Capture Session Already Running")
578
+ case .captureSessionIsMissing:
579
+ return NSLocalizedString("Capture Session is Missing", comment: "Capture Session Missing")
580
+ case .inputsAreInvalid:
581
+ return NSLocalizedString("Inputs Are Invalid", comment: "Inputs Are Invalid")
582
+ case .invalidOperation:
583
+ return NSLocalizedString("Invalid Operation", comment: "invalid Operation")
584
+ case .noCamerasAvailable:
585
+ return NSLocalizedString("Failed to access device camera(s)", comment: "No Cameras Available")
586
+ case .unknown:
587
+ return NSLocalizedString("Unknown", comment: "Unknown")
588
+
589
+ }
590
+ }
591
+ }
592
+
593
+ extension UIImage {
594
+
595
+ func fixedOrientation() -> UIImage? {
596
+
597
+ guard imageOrientation != UIImage.Orientation.up else {
598
+ // This is default orientation, don't need to do anything
599
+ return self.copy() as? UIImage
600
+ }
601
+
602
+ guard let cgImage = self.cgImage else {
603
+ // CGImage is not available
604
+ return nil
605
+ }
606
+
607
+ 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 {
608
+ return nil // Not able to create CGContext
609
+ }
610
+
611
+ var transform: CGAffineTransform = CGAffineTransform.identity
612
+ switch imageOrientation {
613
+ case .down, .downMirrored:
614
+ transform = transform.translatedBy(x: size.width, y: size.height)
615
+ transform = transform.rotated(by: CGFloat.pi)
616
+ print("down")
617
+ break
618
+ case .left, .leftMirrored:
619
+ transform = transform.translatedBy(x: size.width, y: 0)
620
+ transform = transform.rotated(by: CGFloat.pi / 2.0)
621
+ print("left")
622
+ break
623
+ case .right, .rightMirrored:
624
+ transform = transform.translatedBy(x: 0, y: size.height)
625
+ transform = transform.rotated(by: CGFloat.pi / -2.0)
626
+ print("right")
627
+ break
628
+ case .up, .upMirrored:
629
+ break
630
+ }
631
+
632
+ // Flip image one more time if needed to, this is to prevent flipped image
633
+ switch imageOrientation {
634
+ case .upMirrored, .downMirrored:
635
+ transform.translatedBy(x: size.width, y: 0)
636
+ transform.scaledBy(x: -1, y: 1)
637
+ break
638
+ case .leftMirrored, .rightMirrored:
639
+ transform.translatedBy(x: size.height, y: 0)
640
+ transform.scaledBy(x: -1, y: 1)
641
+ case .up, .down, .left, .right:
642
+ break
643
+ }
644
+
645
+ ctx.concatenate(transform)
646
+
647
+ switch imageOrientation {
648
+ case .left, .leftMirrored, .right, .rightMirrored:
649
+ ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
650
+ default:
651
+ ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
652
+ break
653
+ }
654
+ guard let newCGImage = ctx.makeImage() else { return nil }
655
+ return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
656
+ }
657
+ }
658
+
659
+ extension CameraController: AVCaptureFileOutputRecordingDelegate {
660
+ func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
661
+ /*if error == nil {
662
+ self.videoRecordCompletionBlock?(outputFileURL, nil)
663
+ } else {
664
+ self.videoRecordCompletionBlock?(nil, error)
665
+ }*/
666
+ }
667
+ }
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>$(EXECUTABLE_NAME)</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>$(PRODUCT_NAME)</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>FMWK</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>1.0</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>$(CURRENT_PROJECT_VERSION)</string>
21
+ <key>NSPrincipalClass</key>
22
+ <string></string>
23
+ </dict>
24
+ </plist>
@@ -0,0 +1,10 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ //! Project version number for Plugin.
4
+ FOUNDATION_EXPORT double PluginVersionNumber;
5
+
6
+ //! Project version string for Plugin.
7
+ FOUNDATION_EXPORT const unsigned char PluginVersionString[];
8
+
9
+ // In this header, you should import all the public headers of your framework using statements like #import <Plugin/PublicHeader.h>
10
+