@carviz/capacitor-camera-preview 7.0.0
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/CarvizCapacitorCameraPreview.podspec +17 -0
- package/LICENSE +22 -0
- package/README.md +229 -0
- package/android/.project +17 -0
- package/android/build.gradle +57 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/android/gradle.properties +18 -0
- package/android/gradlew +248 -0
- package/android/gradlew.bat +92 -0
- package/android/proguard-rules.pro +21 -0
- package/android/settings.gradle +2 -0
- package/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +26 -0
- package/android/src/main/AndroidManifest.xml +5 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +831 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +396 -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 +384 -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/android/src/test/java/com/getcapacitor/ExampleUnitTest.java +18 -0
- package/dist/esm/definitions.d.ts +86 -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 +24 -0
- package/dist/esm/web.js +120 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +134 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +137 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/CameraController.swift +483 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/Plugin.h +10 -0
- package/ios/Plugin/Plugin.m +16 -0
- package/ios/Plugin/Plugin.swift +324 -0
- package/ios/Plugin/UIImage.swift +56 -0
- package/ios/Plugin.xcodeproj/project.pbxproj +599 -0
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +10 -0
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/PluginTests/Info.plist +22 -0
- package/ios/PluginTests/PluginTests.swift +16 -0
- package/ios/Podfile +16 -0
- package/ios/Podfile.lock +22 -0
- package/package.json +85 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
enum CameraControllerError: Swift.Error {
|
|
5
|
+
case captureSessionAlreadyRunning
|
|
6
|
+
case captureSessionIsMissing
|
|
7
|
+
case inputsAreInvalid
|
|
8
|
+
case invalidOperation
|
|
9
|
+
case noCamerasAvailable
|
|
10
|
+
case unknown
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public enum CameraPosition {
|
|
14
|
+
case front
|
|
15
|
+
case rear
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
extension CameraControllerError: LocalizedError {
|
|
20
|
+
public var errorDescription: String? {
|
|
21
|
+
switch self {
|
|
22
|
+
case .captureSessionAlreadyRunning:
|
|
23
|
+
return NSLocalizedString("Capture Session is Already Running", comment: "Capture Session Already Running")
|
|
24
|
+
case .captureSessionIsMissing:
|
|
25
|
+
return NSLocalizedString("Capture Session is Missing", comment: "Capture Session Missing")
|
|
26
|
+
case .inputsAreInvalid:
|
|
27
|
+
return NSLocalizedString("Inputs Are Invalid", comment: "Inputs Are Invalid")
|
|
28
|
+
case .invalidOperation:
|
|
29
|
+
return NSLocalizedString("Invalid Operation", comment: "invalid Operation")
|
|
30
|
+
case .noCamerasAvailable:
|
|
31
|
+
return NSLocalizedString("Failed to access device camera(s)", comment: "No Cameras Available")
|
|
32
|
+
case .unknown:
|
|
33
|
+
return NSLocalizedString("Unknown", comment: "Unknown")
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class CameraController: NSObject {
|
|
40
|
+
var captureSession: AVCaptureSession?
|
|
41
|
+
|
|
42
|
+
var photoOutput = AVCapturePhotoOutput()
|
|
43
|
+
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
44
|
+
var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
45
|
+
|
|
46
|
+
var previewLayer = AVCaptureVideoPreviewLayer()
|
|
47
|
+
|
|
48
|
+
var frontCamera: AVCaptureDevice?
|
|
49
|
+
var rearCamera: AVCaptureDevice?
|
|
50
|
+
var currentCamera: AVCaptureDevice?
|
|
51
|
+
var cameraInput: AVCaptureDeviceInput?
|
|
52
|
+
|
|
53
|
+
var flashMode = AVCaptureDevice.FlashMode.off
|
|
54
|
+
|
|
55
|
+
/** Video zoom factor that is used for manually zooming in and out via pinch gesture */
|
|
56
|
+
var videoZoomFactor: CGFloat = 1
|
|
57
|
+
|
|
58
|
+
public func prepare(cameraPosition: CameraPosition?, enableHighResolution isHighResolutionPhotoEnabled: Bool, completionHandler: @escaping (Error?) -> Void) {
|
|
59
|
+
// Set up capture session
|
|
60
|
+
let captureSession = AVCaptureSession()
|
|
61
|
+
self.captureSession = captureSession
|
|
62
|
+
|
|
63
|
+
if (isHighResolutionPhotoEnabled) {
|
|
64
|
+
captureSession.sessionPreset = .photo
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set up preview layer
|
|
68
|
+
previewLayer.session = captureSession
|
|
69
|
+
previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
|
70
|
+
|
|
71
|
+
// Initialize front and back camera
|
|
72
|
+
do {
|
|
73
|
+
try initializeCameraDevices(forPosition: cameraPosition ?? .rear)
|
|
74
|
+
try initializeCameraInput()
|
|
75
|
+
} catch {
|
|
76
|
+
completionHandler(error)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Adding the camera output might take quite some time so it's outsourced into a different queue
|
|
81
|
+
// Configure camera output
|
|
82
|
+
self.photoOutput.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
|
|
83
|
+
self.photoOutput.isHighResolutionCaptureEnabled = isHighResolutionPhotoEnabled
|
|
84
|
+
if captureSession.canAddOutput(self.photoOutput) {
|
|
85
|
+
captureSession.addOutput(self.photoOutput)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
captureSession.startRunning()
|
|
89
|
+
|
|
90
|
+
// Lastly setting the video zoom factor
|
|
91
|
+
do {
|
|
92
|
+
try self.configureCameraSettings()
|
|
93
|
+
completionHandler(nil)
|
|
94
|
+
} catch {
|
|
95
|
+
completionHandler(error)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public func stop() {
|
|
100
|
+
self.captureSession?.stopRunning()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
Initializes the available camera devices by selecting the best fit for the current iOS device.
|
|
105
|
+
|
|
106
|
+
This method sets the current camera device to the requested one and also initializes the opposite camera for easy switching later.
|
|
107
|
+
It configures virtual devices supporting ultra-wide angle and better autofocus to address several focus issues observed with newer iPhones.
|
|
108
|
+
|
|
109
|
+
- Parameters:
|
|
110
|
+
- cameraPosition: The position of the camera to initialize (front or rear).
|
|
111
|
+
- Throws: `CameraControllerError.noCamerasAvailable` if no suitable camera is available.
|
|
112
|
+
*/
|
|
113
|
+
private func initializeCameraDevices(forPosition cameraPosition: CameraPosition) throws {
|
|
114
|
+
if let rearCamera = AVCaptureDevice.default(.builtInTripleCamera, for: .video, position: .back) {
|
|
115
|
+
self.rearCamera = rearCamera
|
|
116
|
+
} else {
|
|
117
|
+
self.rearCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if let frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) {
|
|
121
|
+
self.frontCamera = frontCamera
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
guard cameraPosition == .rear && rearCamera != nil || cameraPosition == .front && frontCamera != nil else {
|
|
125
|
+
throw CameraControllerError.noCamerasAvailable
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
self.currentCamera = cameraPosition == .rear ? rearCamera : frontCamera
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
Configure several camera related properties based on the currently selected camera device.
|
|
133
|
+
This function also keeps care of the default zoom factor for the chosen camera device
|
|
134
|
+
*/
|
|
135
|
+
private func configureCameraSettings() throws {
|
|
136
|
+
guard let cameraDevice = self.currentCamera else {
|
|
137
|
+
throw CameraControllerError.noCamerasAvailable;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try cameraDevice.lockForConfiguration()
|
|
141
|
+
defer { cameraDevice.unlockForConfiguration() }
|
|
142
|
+
|
|
143
|
+
if (cameraDevice.isFocusModeSupported(.continuousAutoFocus)) {
|
|
144
|
+
cameraDevice.focusMode = .continuousAutoFocus
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (cameraDevice.isExposureModeSupported(.continuousAutoExposure)) {
|
|
148
|
+
cameraDevice.exposureMode = .continuousAutoExposure
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (cameraDevice.deviceType == .builtInTripleCamera) {
|
|
152
|
+
// Note that zoomFactor 2 "equals" the regular 1x zoom factor of the native iphone camera app
|
|
153
|
+
// 0.5x however equal a videoZoomFactor of 1. We do not want to use ultra wide angle by default
|
|
154
|
+
// the default videoZoomFactor to 2 in case the current camera device type is .builtInTripleCamera
|
|
155
|
+
cameraDevice.videoZoomFactor = 2.0
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
Initialize the camera inputs
|
|
161
|
+
*/
|
|
162
|
+
private func initializeCameraInput() throws {
|
|
163
|
+
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
164
|
+
|
|
165
|
+
guard let camera = self.currentCamera else { throw CameraControllerError.noCamerasAvailable }
|
|
166
|
+
|
|
167
|
+
do {
|
|
168
|
+
let cameraInput = try AVCaptureDeviceInput(device: camera)
|
|
169
|
+
if captureSession.canAddInput(cameraInput) {
|
|
170
|
+
captureSession.addInput(cameraInput)
|
|
171
|
+
}
|
|
172
|
+
self.cameraInput = cameraInput
|
|
173
|
+
} catch {
|
|
174
|
+
throw CameraControllerError.noCamerasAvailable
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public func displayPreview(on view: UIView) {
|
|
179
|
+
view.layer.insertSublayer(self.previewLayer, at: 0)
|
|
180
|
+
previewLayer.frame = view.frame
|
|
181
|
+
updateVideoOrientation()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
public func updateVideoOrientation() {
|
|
185
|
+
assert(Thread.isMainThread)
|
|
186
|
+
|
|
187
|
+
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let interfaceOrientation = windowScene.interfaceOrientation
|
|
192
|
+
|
|
193
|
+
let videoOrientation: AVCaptureVideoOrientation
|
|
194
|
+
switch interfaceOrientation {
|
|
195
|
+
case .portrait:
|
|
196
|
+
videoOrientation = .portrait
|
|
197
|
+
case .landscapeLeft:
|
|
198
|
+
videoOrientation = .landscapeLeft
|
|
199
|
+
case .landscapeRight:
|
|
200
|
+
videoOrientation = .landscapeRight
|
|
201
|
+
case .portraitUpsideDown:
|
|
202
|
+
videoOrientation = .portraitUpsideDown
|
|
203
|
+
case .unknown:
|
|
204
|
+
videoOrientation = .portrait
|
|
205
|
+
@unknown default:
|
|
206
|
+
videoOrientation = .portrait
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
previewLayer.connection?.videoOrientation = videoOrientation
|
|
210
|
+
photoOutput.connections.forEach { $0.videoOrientation = videoOrientation }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public func switchCameras() throws {
|
|
214
|
+
guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
|
|
215
|
+
|
|
216
|
+
guard let device = self.currentCamera else { throw CameraControllerError.noCamerasAvailable }
|
|
217
|
+
|
|
218
|
+
captureSession.beginConfiguration()
|
|
219
|
+
|
|
220
|
+
func switchToFrontCamera() throws {
|
|
221
|
+
guard let cameraInput = self.cameraInput, captureSession.inputs.contains(cameraInput),
|
|
222
|
+
let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation }
|
|
223
|
+
|
|
224
|
+
captureSession.removeInput(cameraInput)
|
|
225
|
+
|
|
226
|
+
let newCameraInput = try AVCaptureDeviceInput(device: frontCamera)
|
|
227
|
+
|
|
228
|
+
if captureSession.canAddInput(newCameraInput) {
|
|
229
|
+
captureSession.addInput(newCameraInput)
|
|
230
|
+
self.cameraInput = newCameraInput
|
|
231
|
+
self.currentCamera = frontCamera
|
|
232
|
+
} else {
|
|
233
|
+
throw CameraControllerError.invalidOperation
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
func switchToRearCamera() throws {
|
|
238
|
+
guard let cameraInput = self.cameraInput, captureSession.inputs.contains(cameraInput),
|
|
239
|
+
let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation }
|
|
240
|
+
|
|
241
|
+
captureSession.removeInput(cameraInput)
|
|
242
|
+
|
|
243
|
+
let newCameraInput = try AVCaptureDeviceInput(device: rearCamera)
|
|
244
|
+
|
|
245
|
+
if captureSession.canAddInput(newCameraInput) {
|
|
246
|
+
captureSession.addInput(newCameraInput)
|
|
247
|
+
self.cameraInput = newCameraInput
|
|
248
|
+
self.currentCamera = rearCamera
|
|
249
|
+
} else {
|
|
250
|
+
throw CameraControllerError.invalidOperation
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
switch device.position {
|
|
255
|
+
case .front:
|
|
256
|
+
try switchToRearCamera()
|
|
257
|
+
case .back:
|
|
258
|
+
try switchToFrontCamera()
|
|
259
|
+
case .unspecified:
|
|
260
|
+
return
|
|
261
|
+
@unknown default:
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Reconfigure camera settings
|
|
266
|
+
try? self.configureCameraSettings()
|
|
267
|
+
|
|
268
|
+
captureSession.commitConfiguration()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
272
|
+
guard let captureSession = captureSession, captureSession.isRunning else {
|
|
273
|
+
completion(nil, CameraControllerError.captureSessionIsMissing);
|
|
274
|
+
return
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let settings = AVCapturePhotoSettings()
|
|
278
|
+
settings.flashMode = self.flashMode
|
|
279
|
+
|
|
280
|
+
self.photoOutput.capturePhoto(with: settings, delegate: self)
|
|
281
|
+
self.photoCaptureCompletionBlock = completion
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
285
|
+
guard let captureSession = captureSession, captureSession.isRunning else {
|
|
286
|
+
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
self.sampleBufferCaptureCompletionBlock = completion
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func getSupportedFlashModes() throws -> [String] {
|
|
294
|
+
guard let device = self.currentCamera else { throw CameraControllerError.noCamerasAvailable }
|
|
295
|
+
|
|
296
|
+
var supportedFlashModesAsStrings: [String] = []
|
|
297
|
+
if device.hasFlash {
|
|
298
|
+
for flashMode in self.photoOutput.supportedFlashModes {
|
|
299
|
+
var flashModeValue: String?
|
|
300
|
+
|
|
301
|
+
switch flashMode {
|
|
302
|
+
case AVCaptureDevice.FlashMode.off:
|
|
303
|
+
flashModeValue = "off"
|
|
304
|
+
case AVCaptureDevice.FlashMode.on:
|
|
305
|
+
flashModeValue = "on"
|
|
306
|
+
case AVCaptureDevice.FlashMode.auto:
|
|
307
|
+
flashModeValue = "auto"
|
|
308
|
+
default: break
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if flashModeValue != nil {
|
|
312
|
+
supportedFlashModesAsStrings.append(flashModeValue!)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if device.hasTorch {
|
|
318
|
+
supportedFlashModesAsStrings.append("torch")
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return supportedFlashModesAsStrings
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
|
|
325
|
+
guard let device = self.currentCamera else { throw CameraControllerError.noCamerasAvailable }
|
|
326
|
+
|
|
327
|
+
if !self.photoOutput.supportedFlashModes.contains(flashMode) {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
do {
|
|
332
|
+
try device.lockForConfiguration()
|
|
333
|
+
|
|
334
|
+
if device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on {
|
|
335
|
+
device.torchMode = AVCaptureDevice.TorchMode.off
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let photoSettings = AVCapturePhotoSettings()
|
|
339
|
+
photoSettings.flashMode = flashMode
|
|
340
|
+
|
|
341
|
+
self.flashMode = flashMode
|
|
342
|
+
self.photoOutput.photoSettingsForSceneMonitoring = photoSettings
|
|
343
|
+
|
|
344
|
+
device.unlockForConfiguration()
|
|
345
|
+
} catch {
|
|
346
|
+
throw CameraControllerError.invalidOperation
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
func setTorchMode() throws {
|
|
351
|
+
guard let device = self.currentCamera, device.hasTorch, device.isTorchAvailable else {
|
|
352
|
+
throw CameraControllerError.invalidOperation
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
do {
|
|
356
|
+
try device.lockForConfiguration()
|
|
357
|
+
|
|
358
|
+
if device.isTorchModeSupported(AVCaptureDevice.TorchMode.on) {
|
|
359
|
+
device.torchMode = AVCaptureDevice.TorchMode.on
|
|
360
|
+
} else if device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto) {
|
|
361
|
+
device.torchMode = AVCaptureDevice.TorchMode.auto
|
|
362
|
+
} else {
|
|
363
|
+
device.torchMode = AVCaptureDevice.TorchMode.off
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
device.unlockForConfiguration()
|
|
367
|
+
} catch {
|
|
368
|
+
throw CameraControllerError.invalidOperation
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
public func setupGestures(target: UIView, enableZoom: Bool) {
|
|
373
|
+
setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
|
|
374
|
+
if enableZoom {
|
|
375
|
+
setupPinchGesture(target: target, selector: #selector(handlePinch(_:)), delegate: self)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private func setupTapGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
|
|
380
|
+
let tapGesture = UITapGestureRecognizer(target: self, action: selector)
|
|
381
|
+
tapGesture.delegate = delegate
|
|
382
|
+
target.addGestureRecognizer(tapGesture)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private func setupPinchGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
|
|
386
|
+
let pinchGesture = UIPinchGestureRecognizer(target: self, action: selector)
|
|
387
|
+
pinchGesture.delegate = delegate
|
|
388
|
+
target.addGestureRecognizer(pinchGesture)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
extension UIImage {
|
|
394
|
+
func fixedOrientation() -> UIImage {
|
|
395
|
+
if imageOrientation == .up {
|
|
396
|
+
return self
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|
400
|
+
draw(in: CGRect(origin: .zero, size: size))
|
|
401
|
+
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()!
|
|
402
|
+
UIGraphicsEndImageContext()
|
|
403
|
+
|
|
404
|
+
return normalizedImage
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
409
|
+
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: (any Error)?) {
|
|
410
|
+
if let error = error {
|
|
411
|
+
self.photoCaptureCompletionBlock?(nil, error)
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
guard let data = photo.fileDataRepresentation(), let image = UIImage(data: data) else {
|
|
416
|
+
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
extension CameraController: UIGestureRecognizerDelegate {
|
|
425
|
+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
426
|
+
return true
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
@objc func handleTap(_ tap: UITapGestureRecognizer) {
|
|
430
|
+
guard let device = self.currentCamera else { return }
|
|
431
|
+
|
|
432
|
+
let point = tap.location(in: tap.view)
|
|
433
|
+
let devicePoint = self.previewLayer.captureDevicePointConverted(fromLayerPoint: point)
|
|
434
|
+
|
|
435
|
+
do {
|
|
436
|
+
try device.lockForConfiguration()
|
|
437
|
+
defer { device.unlockForConfiguration() }
|
|
438
|
+
|
|
439
|
+
let focusMode = AVCaptureDevice.FocusMode.continuousAutoFocus
|
|
440
|
+
if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
|
|
441
|
+
device.focusPointOfInterest = CGPoint(x: CGFloat(devicePoint.x), y: CGFloat(devicePoint.y))
|
|
442
|
+
device.focusMode = focusMode
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let exposureMode = AVCaptureDevice.ExposureMode.continuousAutoExposure
|
|
446
|
+
if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
|
|
447
|
+
device.exposurePointOfInterest = CGPoint(x: CGFloat(devicePoint.x), y: CGFloat(devicePoint.y))
|
|
448
|
+
device.exposureMode = exposureMode
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
debugPrint(error)
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
@objc private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
|
|
456
|
+
guard let device = self.currentCamera else { return }
|
|
457
|
+
|
|
458
|
+
func minMaxZoom(_ factor: CGFloat) -> CGFloat {
|
|
459
|
+
return max(1.0, min(factor, device.activeFormat.videoMaxZoomFactor))
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
func update(scale factor: CGFloat) {
|
|
463
|
+
do {
|
|
464
|
+
try device.lockForConfiguration()
|
|
465
|
+
defer { device.unlockForConfiguration() }
|
|
466
|
+
|
|
467
|
+
device.videoZoomFactor = factor
|
|
468
|
+
} catch {
|
|
469
|
+
debugPrint(error)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
switch pinch.state {
|
|
474
|
+
case .began: fallthrough
|
|
475
|
+
case .changed:
|
|
476
|
+
let newScaleFactor = minMaxZoom(pinch.scale)
|
|
477
|
+
update(scale: newScaleFactor)
|
|
478
|
+
case .ended:
|
|
479
|
+
videoZoomFactor = device.videoZoomFactor
|
|
480
|
+
default: break
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
@@ -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
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <Capacitor/Capacitor.h>
|
|
3
|
+
|
|
4
|
+
// Define the plugin using the CAP_PLUGIN Macro, and
|
|
5
|
+
// each method the plugin supports using the CAP_PLUGIN_METHOD macro.
|
|
6
|
+
CAP_PLUGIN(CameraPreview, "CameraPreview",
|
|
7
|
+
CAP_PLUGIN_METHOD(start, CAPPluginReturnPromise);
|
|
8
|
+
CAP_PLUGIN_METHOD(stop, CAPPluginReturnPromise);
|
|
9
|
+
CAP_PLUGIN_METHOD(capture, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(captureSample, CAPPluginReturnPromise);
|
|
11
|
+
CAP_PLUGIN_METHOD(flip, CAPPluginReturnPromise);
|
|
12
|
+
CAP_PLUGIN_METHOD(getSupportedFlashModes, CAPPluginReturnPromise);
|
|
13
|
+
CAP_PLUGIN_METHOD(setFlashMode, CAPPluginReturnPromise);
|
|
14
|
+
CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise);
|
|
15
|
+
CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise);
|
|
16
|
+
)
|