@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.
Files changed (47) hide show
  1. package/CapgoCameraPreview.podspec +16 -13
  2. package/README.md +492 -73
  3. package/android/build.gradle +11 -0
  4. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  5. package/android/src/main/AndroidManifest.xml +5 -3
  6. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +968 -505
  7. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +3017 -0
  8. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +119 -0
  9. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraDevice.java +63 -0
  10. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraLens.java +79 -0
  11. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +167 -0
  12. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/LensInfo.java +40 -0
  13. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/ZoomFactors.java +35 -0
  14. package/dist/docs.json +1041 -161
  15. package/dist/esm/definitions.d.ts +484 -84
  16. package/dist/esm/definitions.js +10 -1
  17. package/dist/esm/definitions.js.map +1 -1
  18. package/dist/esm/web.d.ts +78 -3
  19. package/dist/esm/web.js +813 -68
  20. package/dist/esm/web.js.map +1 -1
  21. package/dist/plugin.cjs.js +819 -68
  22. package/dist/plugin.cjs.js.map +1 -1
  23. package/dist/plugin.js +819 -68
  24. package/dist/plugin.js.map +1 -1
  25. package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +1663 -0
  26. package/ios/Sources/CapgoCameraPreviewPlugin/GridOverlayView.swift +65 -0
  27. package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +1550 -0
  28. package/ios/Tests/CameraPreviewPluginTests/CameraPreviewPluginTests.swift +15 -0
  29. package/package.json +2 -2
  30. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraActivity.java +0 -1279
  31. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomSurfaceView.java +0 -29
  32. package/android/src/main/java/com/ahm/capacitor/camera/preview/CustomTextureView.java +0 -39
  33. package/android/src/main/java/com/ahm/capacitor/camera/preview/Preview.java +0 -461
  34. package/android/src/main/java/com/ahm/capacitor/camera/preview/TapGestureDetector.java +0 -24
  35. package/ios/Plugin/CameraController.swift +0 -809
  36. package/ios/Plugin/Info.plist +0 -24
  37. package/ios/Plugin/Plugin.h +0 -10
  38. package/ios/Plugin/Plugin.m +0 -18
  39. package/ios/Plugin/Plugin.swift +0 -511
  40. package/ios/Plugin.xcodeproj/project.pbxproj +0 -593
  41. package/ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  42. package/ios/Plugin.xcworkspace/contents.xcworkspacedata +0 -10
  43. package/ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  44. package/ios/PluginTests/Info.plist +0 -22
  45. package/ios/PluginTests/PluginTests.swift +0 -83
  46. package/ios/Podfile +0 -13
  47. 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
- }