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.
- package/CapacitorCommunityMultilensCamerapreview.podspec +17 -0
- package/README.md +210 -0
- package/android/build.gradle +55 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +1005 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +547 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +23 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +29 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +386 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +24 -0
- package/android/src/main/res/layout/bridge_layout_main.xml +15 -0
- package/android/src/main/res/layout/camera_activity.xml +68 -0
- package/android/src/main/res/values/camera_ids.xml +4 -0
- package/android/src/main/res/values/camera_theme.xml +9 -0
- package/android/src/main/res/values/colors.xml +3 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/android/src/main/res/values/styles.xml +3 -0
- package/dist/docs.json +390 -0
- package/dist/esm/definitions.d.ts +79 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +27 -0
- package/dist/esm/web.js +153 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +164 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +167 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/CameraController.swift +667 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Plugin.h +10 -0
- package/ios/Plugin/Plugin.m +17 -0
- package/ios/Plugin/Plugin.swift +300 -0
- 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
|
+
|