@capgo/camera-preview 7.3.12 → 7.4.0-alpha.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/CapgoCameraPreview.podspec +16 -13
- package/README.md +492 -73
- package/android/build.gradle +11 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/AndroidManifest.xml +5 -3
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +968 -505
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +3017 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +119 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +63 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +79 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +167 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +40 -0
- package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +35 -0
- package/dist/docs.json +1041 -161
- package/dist/esm/definitions.d.ts +484 -84
- package/dist/esm/definitions.js +10 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +78 -3
- package/dist/esm/web.js +813 -68
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +819 -68
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +819 -68
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +1663 -0
- package/ios/Sources/CapgoCameraPreviewPlugin/GridOverlayView.swift +65 -0
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1550 -0
- package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
- package/package.json +2 -2
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
- package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
- package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
- package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
- package/ios/Plugin/CameraController.swift +0 -809
- package/ios/Plugin/Info.plist +0 -24
- package/ios/Plugin/Plugin.h +0 -10
- package/ios/Plugin/Plugin.m +0 -18
- package/ios/Plugin/Plugin.swift +0 -511
- package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
- package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
- package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/PluginTests/Info.plist +0 -22
- package/ios/PluginTests/PluginTests.swift +0 -83
- package/ios/Podfile +0 -13
- package/ios/Podfile.lock +0 -23
|
@@ -1,809 +0,0 @@
|
|
|
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 fileVideoOutput: AVCaptureMovieFileOutput?
|
|
27
|
-
|
|
28
|
-
var previewLayer: AVCaptureVideoPreviewLayer?
|
|
29
|
-
|
|
30
|
-
var flashMode = AVCaptureDevice.FlashMode.off
|
|
31
|
-
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
32
|
-
|
|
33
|
-
var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
|
|
34
|
-
|
|
35
|
-
var highResolutionOutput: Bool = false
|
|
36
|
-
|
|
37
|
-
var audioDevice: AVCaptureDevice?
|
|
38
|
-
var audioInput: AVCaptureDeviceInput?
|
|
39
|
-
|
|
40
|
-
var zoomFactor: CGFloat = 1.0
|
|
41
|
-
|
|
42
|
-
var videoFileURL: URL?
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
extension CameraController {
|
|
46
|
-
func prepare(cameraPosition: String, disableAudio: Bool, cameraMode: Bool, completionHandler: @escaping (Error?) -> Void) {
|
|
47
|
-
func createCaptureSession() {
|
|
48
|
-
self.captureSession = AVCaptureSession()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func configureCaptureDevices() throws {
|
|
52
|
-
|
|
53
|
-
let session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
|
|
54
|
-
|
|
55
|
-
let cameras = session.devices.compactMap { $0 }
|
|
56
|
-
guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable }
|
|
57
|
-
|
|
58
|
-
for camera in cameras {
|
|
59
|
-
if camera.position == .front {
|
|
60
|
-
self.frontCamera = camera
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if camera.position == .back {
|
|
64
|
-
self.rearCamera = camera
|
|
65
|
-
|
|
66
|
-
try camera.lockForConfiguration()
|
|
67
|
-
camera.focusMode = .continuousAutoFocus
|
|
68
|
-
camera.unlockForConfiguration()
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if disableAudio == false {
|
|
72
|
-
self.audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
func configureDeviceInputs() throws {
|
|
77
|
-
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
78
|
-
|
|
79
|
-
if cameraPosition == "rear" {
|
|
80
|
-
if let rearCamera = self.rearCamera {
|
|
81
|
-
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera)
|
|
82
|
-
|
|
83
|
-
if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) }
|
|
84
|
-
|
|
85
|
-
self.currentCameraPosition = .rear
|
|
86
|
-
}
|
|
87
|
-
} else if cameraPosition == "front" {
|
|
88
|
-
if let frontCamera = self.frontCamera {
|
|
89
|
-
self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera)
|
|
90
|
-
|
|
91
|
-
if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) } else { throw CameraControllerError.inputsAreInvalid }
|
|
92
|
-
|
|
93
|
-
self.currentCameraPosition = .front
|
|
94
|
-
}
|
|
95
|
-
} else { throw CameraControllerError.noCamerasAvailable }
|
|
96
|
-
|
|
97
|
-
// Add audio input
|
|
98
|
-
if disableAudio == false {
|
|
99
|
-
if let audioDevice = self.audioDevice {
|
|
100
|
-
self.audioInput = try AVCaptureDeviceInput(device: audioDevice)
|
|
101
|
-
if captureSession.canAddInput(self.audioInput!) {
|
|
102
|
-
captureSession.addInput(self.audioInput!)
|
|
103
|
-
} else {
|
|
104
|
-
throw CameraControllerError.inputsAreInvalid
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
func configurePhotoOutput(cameraMode: Bool) throws {
|
|
111
|
-
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
112
|
-
|
|
113
|
-
// TODO: check if that really useful
|
|
114
|
-
if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
|
|
115
|
-
captureSession.sessionPreset = .photo
|
|
116
|
-
} else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
|
|
117
|
-
captureSession.sessionPreset = .high
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
self.photoOutput = AVCapturePhotoOutput()
|
|
121
|
-
self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
|
|
122
|
-
self.photoOutput?.isHighResolutionCaptureEnabled = self.highResolutionOutput
|
|
123
|
-
if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) }
|
|
124
|
-
|
|
125
|
-
let fileVideoOutput = AVCaptureMovieFileOutput()
|
|
126
|
-
if captureSession.canAddOutput(fileVideoOutput) {
|
|
127
|
-
captureSession.addOutput(fileVideoOutput)
|
|
128
|
-
self.fileVideoOutput = fileVideoOutput
|
|
129
|
-
}
|
|
130
|
-
captureSession.startRunning()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
func configureDataOutput() throws {
|
|
134
|
-
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
|
|
135
|
-
|
|
136
|
-
self.dataOutput = AVCaptureVideoDataOutput()
|
|
137
|
-
self.dataOutput?.videoSettings = [
|
|
138
|
-
(kCVPixelBufferPixelFormatTypeKey as String): NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)
|
|
139
|
-
]
|
|
140
|
-
self.dataOutput?.alwaysDiscardsLateVideoFrames = true
|
|
141
|
-
if captureSession.canAddOutput(self.dataOutput!) {
|
|
142
|
-
captureSession.addOutput(self.dataOutput!)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
captureSession.commitConfiguration()
|
|
146
|
-
|
|
147
|
-
let queue = DispatchQueue(label: "DataOutput", attributes: [])
|
|
148
|
-
self.dataOutput?.setSampleBufferDelegate(self, queue: queue)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
DispatchQueue(label: "prepare").async {
|
|
152
|
-
do {
|
|
153
|
-
createCaptureSession()
|
|
154
|
-
try configureCaptureDevices()
|
|
155
|
-
try configureDeviceInputs()
|
|
156
|
-
try configurePhotoOutput(cameraMode: cameraMode)
|
|
157
|
-
try configureDataOutput()
|
|
158
|
-
// try configureVideoOutput()
|
|
159
|
-
} catch {
|
|
160
|
-
DispatchQueue.main.async {
|
|
161
|
-
completionHandler(error)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
DispatchQueue.main.async {
|
|
168
|
-
completionHandler(nil)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
func displayPreview(on view: UIView) throws {
|
|
174
|
-
guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing }
|
|
175
|
-
|
|
176
|
-
self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
|
|
177
|
-
self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
|
178
|
-
|
|
179
|
-
view.layer.insertSublayer(self.previewLayer!, at: 0)
|
|
180
|
-
self.previewLayer?.frame = view.frame
|
|
181
|
-
|
|
182
|
-
updateVideoOrientation()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
func setupGestures(target: UIView, enableZoom: Bool) {
|
|
186
|
-
setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
|
|
187
|
-
if enableZoom {
|
|
188
|
-
setupPinchGesture(target: target, selector: #selector(handlePinch(_:)), delegate: self)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
func setupTapGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
|
|
193
|
-
let tapGesture = UITapGestureRecognizer(target: self, action: selector)
|
|
194
|
-
tapGesture.delegate = delegate
|
|
195
|
-
target.addGestureRecognizer(tapGesture)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
func setupPinchGesture(target: UIView, selector: Selector, delegate: UIGestureRecognizerDelegate?) {
|
|
199
|
-
let pinchGesture = UIPinchGestureRecognizer(target: self, action: selector)
|
|
200
|
-
pinchGesture.delegate = delegate
|
|
201
|
-
target.addGestureRecognizer(pinchGesture)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
func updateVideoOrientation() {
|
|
205
|
-
if Thread.isMainThread {
|
|
206
|
-
updateVideoOrientationOnMainThread()
|
|
207
|
-
} else {
|
|
208
|
-
DispatchQueue.main.async { [weak self] in
|
|
209
|
-
self?.updateVideoOrientationOnMainThread()
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private func updateVideoOrientationOnMainThread() {
|
|
215
|
-
let videoOrientation: AVCaptureVideoOrientation
|
|
216
|
-
|
|
217
|
-
// Use window scene interface orientation
|
|
218
|
-
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
219
|
-
switch windowScene.interfaceOrientation {
|
|
220
|
-
case .portrait:
|
|
221
|
-
videoOrientation = .portrait
|
|
222
|
-
case .landscapeLeft:
|
|
223
|
-
videoOrientation = .landscapeLeft
|
|
224
|
-
case .landscapeRight:
|
|
225
|
-
videoOrientation = .landscapeRight
|
|
226
|
-
case .portraitUpsideDown:
|
|
227
|
-
videoOrientation = .portraitUpsideDown
|
|
228
|
-
case .unknown:
|
|
229
|
-
fallthrough
|
|
230
|
-
@unknown default:
|
|
231
|
-
videoOrientation = .portrait
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
videoOrientation = .portrait
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
previewLayer?.connection?.videoOrientation = videoOrientation
|
|
238
|
-
dataOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
|
|
239
|
-
photoOutput?.connections.forEach { $0.videoOrientation = videoOrientation }
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
func switchCameras() throws {
|
|
243
|
-
guard let currentCameraPosition = currentCameraPosition,
|
|
244
|
-
let captureSession = self.captureSession else {
|
|
245
|
-
throw CameraControllerError.captureSessionIsMissing
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Ensure we have the necessary cameras
|
|
249
|
-
guard (currentCameraPosition == .front && rearCamera != nil) ||
|
|
250
|
-
(currentCameraPosition == .rear && frontCamera != nil) else {
|
|
251
|
-
throw CameraControllerError.noCamerasAvailable
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Store the current running state
|
|
255
|
-
let wasRunning = captureSession.isRunning
|
|
256
|
-
if wasRunning {
|
|
257
|
-
captureSession.stopRunning()
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Begin configuration
|
|
261
|
-
captureSession.beginConfiguration()
|
|
262
|
-
defer {
|
|
263
|
-
captureSession.commitConfiguration()
|
|
264
|
-
// Restart the session if it was running before
|
|
265
|
-
if wasRunning {
|
|
266
|
-
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
267
|
-
self?.captureSession?.startRunning()
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Store audio input if it exists
|
|
273
|
-
let audioInput = captureSession.inputs.first { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.audio) ?? false }
|
|
274
|
-
|
|
275
|
-
// Remove only video inputs
|
|
276
|
-
captureSession.inputs.forEach { input in
|
|
277
|
-
if (input as? AVCaptureDeviceInput)?.device.hasMediaType(.video) ?? false {
|
|
278
|
-
captureSession.removeInput(input)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Configure new camera
|
|
283
|
-
switch currentCameraPosition {
|
|
284
|
-
case .front:
|
|
285
|
-
guard let rearCamera = rearCamera else {
|
|
286
|
-
throw CameraControllerError.invalidOperation
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Configure rear camera
|
|
290
|
-
try rearCamera.lockForConfiguration()
|
|
291
|
-
if rearCamera.isFocusModeSupported(.continuousAutoFocus) {
|
|
292
|
-
rearCamera.focusMode = .continuousAutoFocus
|
|
293
|
-
}
|
|
294
|
-
rearCamera.unlockForConfiguration()
|
|
295
|
-
|
|
296
|
-
if let newInput = try? AVCaptureDeviceInput(device: rearCamera),
|
|
297
|
-
captureSession.canAddInput(newInput) {
|
|
298
|
-
captureSession.addInput(newInput)
|
|
299
|
-
rearCameraInput = newInput
|
|
300
|
-
self.currentCameraPosition = .rear
|
|
301
|
-
} else {
|
|
302
|
-
throw CameraControllerError.invalidOperation
|
|
303
|
-
}
|
|
304
|
-
case .rear:
|
|
305
|
-
guard let frontCamera = frontCamera else {
|
|
306
|
-
throw CameraControllerError.invalidOperation
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Configure front camera
|
|
310
|
-
try frontCamera.lockForConfiguration()
|
|
311
|
-
if frontCamera.isFocusModeSupported(.continuousAutoFocus) {
|
|
312
|
-
frontCamera.focusMode = .continuousAutoFocus
|
|
313
|
-
}
|
|
314
|
-
frontCamera.unlockForConfiguration()
|
|
315
|
-
|
|
316
|
-
if let newInput = try? AVCaptureDeviceInput(device: frontCamera),
|
|
317
|
-
captureSession.canAddInput(newInput) {
|
|
318
|
-
captureSession.addInput(newInput)
|
|
319
|
-
frontCameraInput = newInput
|
|
320
|
-
self.currentCameraPosition = .front
|
|
321
|
-
} else {
|
|
322
|
-
throw CameraControllerError.invalidOperation
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Re-add audio input if it existed
|
|
327
|
-
if let audioInput = audioInput, captureSession.canAddInput(audioInput) {
|
|
328
|
-
captureSession.addInput(audioInput)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Update video orientation
|
|
332
|
-
DispatchQueue.main.async { [weak self] in
|
|
333
|
-
self?.updateVideoOrientation()
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
func captureImage(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
338
|
-
guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return }
|
|
339
|
-
let settings = AVCapturePhotoSettings()
|
|
340
|
-
|
|
341
|
-
settings.flashMode = self.flashMode
|
|
342
|
-
settings.isHighResolutionPhotoEnabled = self.highResolutionOutput
|
|
343
|
-
|
|
344
|
-
self.photoOutput?.capturePhoto(with: settings, delegate: self)
|
|
345
|
-
self.photoCaptureCompletionBlock = completion
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
func captureSample(completion: @escaping (UIImage?, Error?) -> Void) {
|
|
349
|
-
guard let captureSession = captureSession,
|
|
350
|
-
captureSession.isRunning else {
|
|
351
|
-
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
352
|
-
return
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
self.sampleBufferCaptureCompletionBlock = completion
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
func getSupportedFlashModes() throws -> [String] {
|
|
359
|
-
var currentCamera: AVCaptureDevice?
|
|
360
|
-
switch currentCameraPosition {
|
|
361
|
-
case .front:
|
|
362
|
-
currentCamera = self.frontCamera!
|
|
363
|
-
case .rear:
|
|
364
|
-
currentCamera = self.rearCamera!
|
|
365
|
-
default: break
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
guard
|
|
369
|
-
let device = currentCamera
|
|
370
|
-
else {
|
|
371
|
-
throw CameraControllerError.noCamerasAvailable
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
var supportedFlashModesAsStrings: [String] = []
|
|
375
|
-
if device.hasFlash {
|
|
376
|
-
guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
|
|
377
|
-
throw CameraControllerError.noCamerasAvailable
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
for flashMode in supportedFlashModes {
|
|
381
|
-
var flashModeValue: String?
|
|
382
|
-
switch flashMode {
|
|
383
|
-
case AVCaptureDevice.FlashMode.off:
|
|
384
|
-
flashModeValue = "off"
|
|
385
|
-
case AVCaptureDevice.FlashMode.on:
|
|
386
|
-
flashModeValue = "on"
|
|
387
|
-
case AVCaptureDevice.FlashMode.auto:
|
|
388
|
-
flashModeValue = "auto"
|
|
389
|
-
default: break
|
|
390
|
-
}
|
|
391
|
-
if flashModeValue != nil {
|
|
392
|
-
supportedFlashModesAsStrings.append(flashModeValue!)
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
if device.hasTorch {
|
|
397
|
-
supportedFlashModesAsStrings.append("torch")
|
|
398
|
-
}
|
|
399
|
-
return supportedFlashModesAsStrings
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
func getHorizontalFov() throws -> Float {
|
|
403
|
-
var currentCamera: AVCaptureDevice?
|
|
404
|
-
switch currentCameraPosition {
|
|
405
|
-
case .front:
|
|
406
|
-
currentCamera = self.frontCamera!
|
|
407
|
-
case .rear:
|
|
408
|
-
currentCamera = self.rearCamera!
|
|
409
|
-
default: break
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
guard
|
|
413
|
-
let device = currentCamera
|
|
414
|
-
else {
|
|
415
|
-
throw CameraControllerError.noCamerasAvailable
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return device.activeFormat.videoFieldOfView
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
func setFlashMode(flashMode: AVCaptureDevice.FlashMode) throws {
|
|
422
|
-
var currentCamera: AVCaptureDevice?
|
|
423
|
-
switch currentCameraPosition {
|
|
424
|
-
case .front:
|
|
425
|
-
currentCamera = self.frontCamera!
|
|
426
|
-
case .rear:
|
|
427
|
-
currentCamera = self.rearCamera!
|
|
428
|
-
default: break
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
guard let device = currentCamera else {
|
|
432
|
-
throw CameraControllerError.noCamerasAvailable
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
guard let supportedFlashModes: [AVCaptureDevice.FlashMode] = self.photoOutput?.supportedFlashModes else {
|
|
436
|
-
throw CameraControllerError.invalidOperation
|
|
437
|
-
}
|
|
438
|
-
if supportedFlashModes.contains(flashMode) {
|
|
439
|
-
do {
|
|
440
|
-
try device.lockForConfiguration()
|
|
441
|
-
|
|
442
|
-
if device.hasTorch && device.isTorchAvailable && device.torchMode == AVCaptureDevice.TorchMode.on {
|
|
443
|
-
device.torchMode = AVCaptureDevice.TorchMode.off
|
|
444
|
-
}
|
|
445
|
-
self.flashMode = flashMode
|
|
446
|
-
let photoSettings = AVCapturePhotoSettings()
|
|
447
|
-
photoSettings.flashMode = flashMode
|
|
448
|
-
self.photoOutput?.photoSettingsForSceneMonitoring = photoSettings
|
|
449
|
-
|
|
450
|
-
device.unlockForConfiguration()
|
|
451
|
-
} catch {
|
|
452
|
-
throw CameraControllerError.invalidOperation
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
throw CameraControllerError.invalidOperation
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
func setTorchMode() throws {
|
|
460
|
-
var currentCamera: AVCaptureDevice?
|
|
461
|
-
switch currentCameraPosition {
|
|
462
|
-
case .front:
|
|
463
|
-
currentCamera = self.frontCamera!
|
|
464
|
-
case .rear:
|
|
465
|
-
currentCamera = self.rearCamera!
|
|
466
|
-
default: break
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
guard
|
|
470
|
-
let device = currentCamera,
|
|
471
|
-
device.hasTorch,
|
|
472
|
-
device.isTorchAvailable
|
|
473
|
-
else {
|
|
474
|
-
throw CameraControllerError.invalidOperation
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
do {
|
|
478
|
-
try device.lockForConfiguration()
|
|
479
|
-
if device.isTorchModeSupported(AVCaptureDevice.TorchMode.on) {
|
|
480
|
-
device.torchMode = AVCaptureDevice.TorchMode.on
|
|
481
|
-
} else if device.isTorchModeSupported(AVCaptureDevice.TorchMode.auto) {
|
|
482
|
-
device.torchMode = AVCaptureDevice.TorchMode.auto
|
|
483
|
-
} else {
|
|
484
|
-
device.torchMode = AVCaptureDevice.TorchMode.off
|
|
485
|
-
}
|
|
486
|
-
device.unlockForConfiguration()
|
|
487
|
-
} catch {
|
|
488
|
-
throw CameraControllerError.invalidOperation
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
func cleanup() {
|
|
493
|
-
if let captureSession = self.captureSession {
|
|
494
|
-
captureSession.stopRunning()
|
|
495
|
-
captureSession.inputs.forEach { captureSession.removeInput($0) }
|
|
496
|
-
captureSession.outputs.forEach { captureSession.removeOutput($0) }
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
self.previewLayer?.removeFromSuperlayer()
|
|
500
|
-
self.previewLayer = nil
|
|
501
|
-
|
|
502
|
-
self.frontCameraInput = nil
|
|
503
|
-
self.rearCameraInput = nil
|
|
504
|
-
self.audioInput = nil
|
|
505
|
-
|
|
506
|
-
self.frontCamera = nil
|
|
507
|
-
self.rearCamera = nil
|
|
508
|
-
self.audioDevice = nil
|
|
509
|
-
|
|
510
|
-
self.dataOutput = nil
|
|
511
|
-
self.photoOutput = nil
|
|
512
|
-
self.fileVideoOutput = nil
|
|
513
|
-
|
|
514
|
-
self.captureSession = nil
|
|
515
|
-
self.currentCameraPosition = nil
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
func captureVideo() throws {
|
|
519
|
-
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
520
|
-
throw CameraControllerError.captureSessionIsMissing
|
|
521
|
-
}
|
|
522
|
-
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
|
523
|
-
throw CameraControllerError.cannotFindDocumentsDirectory
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
guard let fileVideoOutput = self.fileVideoOutput else {
|
|
527
|
-
throw CameraControllerError.fileVideoOutputNotFound
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// cpcp_video_A6C01203 - portrait
|
|
531
|
-
//
|
|
532
|
-
if let connection = fileVideoOutput.connection(with: .video) {
|
|
533
|
-
switch UIDevice.current.orientation {
|
|
534
|
-
case .landscapeRight:
|
|
535
|
-
connection.videoOrientation = .landscapeLeft
|
|
536
|
-
case .landscapeLeft:
|
|
537
|
-
connection.videoOrientation = .landscapeRight
|
|
538
|
-
case .portrait:
|
|
539
|
-
connection.videoOrientation = .portrait
|
|
540
|
-
case .portraitUpsideDown:
|
|
541
|
-
connection.videoOrientation = .portraitUpsideDown
|
|
542
|
-
default:
|
|
543
|
-
connection.videoOrientation = .portrait
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
let identifier = UUID()
|
|
548
|
-
let randomIdentifier = identifier.uuidString.replacingOccurrences(of: "-", with: "")
|
|
549
|
-
let finalIdentifier = String(randomIdentifier.prefix(8))
|
|
550
|
-
let fileName="cpcp_video_"+finalIdentifier+".mp4"
|
|
551
|
-
|
|
552
|
-
let fileUrl = documentsDirectory.appendingPathComponent(fileName)
|
|
553
|
-
try? FileManager.default.removeItem(at: fileUrl)
|
|
554
|
-
|
|
555
|
-
// Start recording video
|
|
556
|
-
fileVideoOutput.startRecording(to: fileUrl, recordingDelegate: self)
|
|
557
|
-
|
|
558
|
-
// Save the file URL for later use
|
|
559
|
-
self.videoFileURL = fileUrl
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
func stopRecording(completion: @escaping (URL?, Error?) -> Void) {
|
|
563
|
-
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
564
|
-
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
565
|
-
return
|
|
566
|
-
}
|
|
567
|
-
guard let fileVideoOutput = self.fileVideoOutput else {
|
|
568
|
-
completion(nil, CameraControllerError.fileVideoOutputNotFound)
|
|
569
|
-
return
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Stop recording video
|
|
573
|
-
fileVideoOutput.stopRecording()
|
|
574
|
-
|
|
575
|
-
// Return the video file URL in the completion handler
|
|
576
|
-
completion(self.videoFileURL, nil)
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
extension CameraController: UIGestureRecognizerDelegate {
|
|
581
|
-
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
582
|
-
return true
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
@objc
|
|
586
|
-
func handleTap(_ tap: UITapGestureRecognizer) {
|
|
587
|
-
guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
|
|
588
|
-
|
|
589
|
-
let point = tap.location(in: tap.view)
|
|
590
|
-
let devicePoint = self.previewLayer?.captureDevicePointConverted(fromLayerPoint: point)
|
|
591
|
-
|
|
592
|
-
do {
|
|
593
|
-
try device.lockForConfiguration()
|
|
594
|
-
defer { device.unlockForConfiguration() }
|
|
595
|
-
|
|
596
|
-
let focusMode = AVCaptureDevice.FocusMode.autoFocus
|
|
597
|
-
if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) {
|
|
598
|
-
device.focusPointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
|
|
599
|
-
device.focusMode = focusMode
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
let exposureMode = AVCaptureDevice.ExposureMode.autoExpose
|
|
603
|
-
if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) {
|
|
604
|
-
device.exposurePointOfInterest = CGPoint(x: CGFloat(devicePoint?.x ?? 0), y: CGFloat(devicePoint?.y ?? 0))
|
|
605
|
-
device.exposureMode = exposureMode
|
|
606
|
-
}
|
|
607
|
-
} catch {
|
|
608
|
-
debugPrint(error)
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
@objc
|
|
613
|
-
private func handlePinch(_ pinch: UIPinchGestureRecognizer) {
|
|
614
|
-
guard let device = self.currentCameraPosition == .rear ? rearCamera : frontCamera else { return }
|
|
615
|
-
|
|
616
|
-
func minMaxZoom(_ factor: CGFloat) -> CGFloat { return max(1.0, min(factor, device.activeFormat.videoMaxZoomFactor)) }
|
|
617
|
-
|
|
618
|
-
func update(scale factor: CGFloat) {
|
|
619
|
-
do {
|
|
620
|
-
try device.lockForConfiguration()
|
|
621
|
-
defer { device.unlockForConfiguration() }
|
|
622
|
-
|
|
623
|
-
device.videoZoomFactor = factor
|
|
624
|
-
} catch {
|
|
625
|
-
debugPrint(error)
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
switch pinch.state {
|
|
630
|
-
case .began: fallthrough
|
|
631
|
-
case .changed:
|
|
632
|
-
let newScaleFactor = minMaxZoom(pinch.scale * zoomFactor)
|
|
633
|
-
update(scale: newScaleFactor)
|
|
634
|
-
case .ended:
|
|
635
|
-
zoomFactor = device.videoZoomFactor
|
|
636
|
-
default: break
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
extension CameraController: AVCapturePhotoCaptureDelegate {
|
|
642
|
-
public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?,
|
|
643
|
-
resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) {
|
|
644
|
-
if let error = error {
|
|
645
|
-
self.photoCaptureCompletionBlock?(nil, error)
|
|
646
|
-
} else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil),
|
|
647
|
-
let image = UIImage(data: data) {
|
|
648
|
-
self.photoCaptureCompletionBlock?(image.fixedOrientation(), nil)
|
|
649
|
-
} else {
|
|
650
|
-
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown)
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
656
|
-
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
|
657
|
-
guard let completion = sampleBufferCaptureCompletionBlock else { return }
|
|
658
|
-
|
|
659
|
-
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
|
|
660
|
-
completion(nil, CameraControllerError.unknown)
|
|
661
|
-
return
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
|
|
665
|
-
defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
|
|
666
|
-
|
|
667
|
-
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
|
|
668
|
-
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
|
|
669
|
-
let width = CVPixelBufferGetWidth(imageBuffer)
|
|
670
|
-
let height = CVPixelBufferGetHeight(imageBuffer)
|
|
671
|
-
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
672
|
-
let bitmapInfo: UInt32 = CGBitmapInfo.byteOrder32Little.rawValue |
|
|
673
|
-
CGImageAlphaInfo.premultipliedFirst.rawValue
|
|
674
|
-
|
|
675
|
-
let context = CGContext(
|
|
676
|
-
data: baseAddress,
|
|
677
|
-
width: width,
|
|
678
|
-
height: height,
|
|
679
|
-
bitsPerComponent: 8,
|
|
680
|
-
bytesPerRow: bytesPerRow,
|
|
681
|
-
space: colorSpace,
|
|
682
|
-
bitmapInfo: bitmapInfo
|
|
683
|
-
)
|
|
684
|
-
|
|
685
|
-
guard let cgImage = context?.makeImage() else {
|
|
686
|
-
completion(nil, CameraControllerError.unknown)
|
|
687
|
-
return
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
let image = UIImage(cgImage: cgImage)
|
|
691
|
-
completion(image.fixedOrientation(), nil)
|
|
692
|
-
|
|
693
|
-
sampleBufferCaptureCompletionBlock = nil
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
enum CameraControllerError: Swift.Error {
|
|
698
|
-
case captureSessionAlreadyRunning
|
|
699
|
-
case captureSessionIsMissing
|
|
700
|
-
case inputsAreInvalid
|
|
701
|
-
case invalidOperation
|
|
702
|
-
case noCamerasAvailable
|
|
703
|
-
case cannotFindDocumentsDirectory
|
|
704
|
-
case fileVideoOutputNotFound
|
|
705
|
-
case unknown
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
public enum CameraPosition {
|
|
709
|
-
case front
|
|
710
|
-
case rear
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
extension CameraControllerError: LocalizedError {
|
|
714
|
-
public var errorDescription: String? {
|
|
715
|
-
switch self {
|
|
716
|
-
case .captureSessionAlreadyRunning:
|
|
717
|
-
return NSLocalizedString("Capture Session is Already Running", comment: "Capture Session Already Running")
|
|
718
|
-
case .captureSessionIsMissing:
|
|
719
|
-
return NSLocalizedString("Capture Session is Missing", comment: "Capture Session Missing")
|
|
720
|
-
case .inputsAreInvalid:
|
|
721
|
-
return NSLocalizedString("Inputs Are Invalid", comment: "Inputs Are Invalid")
|
|
722
|
-
case .invalidOperation:
|
|
723
|
-
return NSLocalizedString("Invalid Operation", comment: "invalid Operation")
|
|
724
|
-
case .noCamerasAvailable:
|
|
725
|
-
return NSLocalizedString("Failed to access device camera(s)", comment: "No Cameras Available")
|
|
726
|
-
case .unknown:
|
|
727
|
-
return NSLocalizedString("Unknown", comment: "Unknown")
|
|
728
|
-
case .cannotFindDocumentsDirectory:
|
|
729
|
-
return NSLocalizedString("Cannot find documents directory", comment: "This should never happen")
|
|
730
|
-
case .fileVideoOutputNotFound:
|
|
731
|
-
return NSLocalizedString("Video recording is not available. Make sure the camera is properly initialized.", comment: "Video recording not available")
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
extension UIImage {
|
|
737
|
-
|
|
738
|
-
func fixedOrientation() -> UIImage? {
|
|
739
|
-
|
|
740
|
-
guard imageOrientation != UIImage.Orientation.up else {
|
|
741
|
-
// This is default orientation, don't need to do anything
|
|
742
|
-
return self.copy() as? UIImage
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
guard let cgImage = self.cgImage else {
|
|
746
|
-
// CGImage is not available
|
|
747
|
-
return nil
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil,
|
|
751
|
-
width: Int(size.width), height: Int(size.height),
|
|
752
|
-
bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0,
|
|
753
|
-
space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
|
|
754
|
-
return nil // Not able to create CGContext
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
var transform: CGAffineTransform = CGAffineTransform.identity
|
|
758
|
-
switch imageOrientation {
|
|
759
|
-
case .down, .downMirrored:
|
|
760
|
-
transform = transform.translatedBy(x: size.width, y: size.height)
|
|
761
|
-
transform = transform.rotated(by: CGFloat.pi)
|
|
762
|
-
print("down")
|
|
763
|
-
case .left, .leftMirrored:
|
|
764
|
-
transform = transform.translatedBy(x: size.width, y: 0)
|
|
765
|
-
transform = transform.rotated(by: CGFloat.pi / 2.0)
|
|
766
|
-
print("left")
|
|
767
|
-
case .right, .rightMirrored:
|
|
768
|
-
transform = transform.translatedBy(x: 0, y: size.height)
|
|
769
|
-
transform = transform.rotated(by: CGFloat.pi / -2.0)
|
|
770
|
-
print("right")
|
|
771
|
-
case .up, .upMirrored:
|
|
772
|
-
break
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Flip image one more time if needed to, this is to prevent flipped image
|
|
776
|
-
switch imageOrientation {
|
|
777
|
-
case .upMirrored, .downMirrored:
|
|
778
|
-
transform.translatedBy(x: size.width, y: 0)
|
|
779
|
-
transform.scaledBy(x: -1, y: 1)
|
|
780
|
-
case .leftMirrored, .rightMirrored:
|
|
781
|
-
transform.translatedBy(x: size.height, y: 0)
|
|
782
|
-
transform.scaledBy(x: -1, y: 1)
|
|
783
|
-
case .up, .down, .left, .right:
|
|
784
|
-
break
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
ctx.concatenate(transform)
|
|
788
|
-
|
|
789
|
-
switch imageOrientation {
|
|
790
|
-
case .left, .leftMirrored, .right, .rightMirrored:
|
|
791
|
-
ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
|
|
792
|
-
default:
|
|
793
|
-
ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
|
|
794
|
-
}
|
|
795
|
-
guard let newCGImage = ctx.makeImage() else { return nil }
|
|
796
|
-
return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
extension CameraController: AVCaptureFileOutputRecordingDelegate {
|
|
801
|
-
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
|
|
802
|
-
if let error = error {
|
|
803
|
-
print("Error recording movie: \(error.localizedDescription)")
|
|
804
|
-
} else {
|
|
805
|
-
print("Movie recorded successfully: \(outputFileURL)")
|
|
806
|
-
// You can save the file to the library, upload it, etc.
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|