@capgo/camera-preview 8.5.1 → 8.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +134 -14
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +316 -2
- package/android/src/main/java/app/capgo/capacitor/camera/preview/model/CameraSessionConfiguration.java +9 -0
- package/dist/docs.json +245 -1
- package/dist/esm/definitions.d.ts +96 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +20 -1
- package/dist/esm/web.js +22 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +21 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +21 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +294 -9
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +90 -4
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// swiftlint:disable file_length cyclomatic_complexity identifier_name
|
|
1
|
+
// swiftlint:disable file_length type_body_length cyclomatic_complexity identifier_name function_body_length
|
|
2
2
|
import AVFoundation
|
|
3
3
|
import UIKit
|
|
4
4
|
import CoreLocation
|
|
@@ -57,7 +57,6 @@ class CameraController: NSObject {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
61
60
|
private func setVideoOrientation(_ orientation: AVCaptureVideoOrientation, on connection: AVCaptureConnection) {
|
|
62
61
|
guard connection.isVideoOrientationSupported else { return }
|
|
63
62
|
connection.videoOrientation = orientation
|
|
@@ -151,10 +150,13 @@ class CameraController: NSObject {
|
|
|
151
150
|
|
|
152
151
|
var videoFileURL: URL?
|
|
153
152
|
var recordingFinishedCallback: ((URL, String) -> Void)?
|
|
153
|
+
private var stopRecordingCompletion: ((URL?, String?, Error?) -> Void)?
|
|
154
154
|
private let saneMaxZoomFactor: CGFloat = 25.5
|
|
155
155
|
|
|
156
156
|
var videoQuality: String = "high"
|
|
157
|
+
private var configuredVideoFrameRate: Int?
|
|
157
158
|
var videoCodec: String = "avc1"
|
|
159
|
+
var videoStabilizationMode: String = "off"
|
|
158
160
|
|
|
159
161
|
private static let allVideoQualities = ["low", "medium", "high", "2160p", "1080p", "720p", "480p", "4:3"]
|
|
160
162
|
|
|
@@ -213,6 +215,138 @@ class CameraController: NSObject {
|
|
|
213
215
|
return self.videoCodec
|
|
214
216
|
}
|
|
215
217
|
|
|
218
|
+
func isVideoStabilizationSupported() -> Bool {
|
|
219
|
+
guard let connection = self.fileVideoOutput?.connection(with: .video) else {
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
return connection.isVideoStabilizationSupported
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
func getSupportedVideoStabilizationModes() -> [String] {
|
|
226
|
+
guard let connection = self.fileVideoOutput?.connection(with: .video),
|
|
227
|
+
connection.isVideoStabilizationSupported else {
|
|
228
|
+
return ["off"]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let savedPreferred = connection.preferredVideoStabilizationMode
|
|
232
|
+
var supported: [String] = []
|
|
233
|
+
|
|
234
|
+
for candidate in Self.allVideoStabilizationModeCandidates() {
|
|
235
|
+
connection.preferredVideoStabilizationMode = candidate.mode
|
|
236
|
+
if connection.preferredVideoStabilizationMode == candidate.mode {
|
|
237
|
+
supported.append(candidate.name)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
connection.preferredVideoStabilizationMode = savedPreferred
|
|
242
|
+
return supported.isEmpty ? ["off"] : supported
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
func getVideoStabilizationMode() -> String {
|
|
246
|
+
if let connection = self.fileVideoOutput?.connection(with: .video),
|
|
247
|
+
connection.isVideoStabilizationSupported {
|
|
248
|
+
return self.videoStabilizationModeString(from: connection.activeVideoStabilizationMode)
|
|
249
|
+
}
|
|
250
|
+
return self.videoStabilizationMode
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func setVideoStabilizationMode(_ mode: String) throws {
|
|
254
|
+
if self.fileVideoOutput?.isRecording == true {
|
|
255
|
+
throw CameraControllerError.recordingInProgress
|
|
256
|
+
}
|
|
257
|
+
guard let avMode = self.avVideoStabilizationMode(from: mode) else {
|
|
258
|
+
throw CameraControllerError.invalidOperation
|
|
259
|
+
}
|
|
260
|
+
let supportedModes = self.getSupportedVideoStabilizationModes()
|
|
261
|
+
guard supportedModes.contains(mode) else {
|
|
262
|
+
throw CameraControllerError.invalidOperation
|
|
263
|
+
}
|
|
264
|
+
self.videoStabilizationMode = mode
|
|
265
|
+
self.applyVideoStabilizationMode(avMode)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private static func allVideoStabilizationModeCandidates() -> [(name: String, mode: AVCaptureVideoStabilizationMode)] {
|
|
269
|
+
var candidates: [(String, AVCaptureVideoStabilizationMode)] = [
|
|
270
|
+
("off", .off),
|
|
271
|
+
("standard", .standard),
|
|
272
|
+
("cinematic", .cinematic),
|
|
273
|
+
("cinematicExtended", .cinematicExtended),
|
|
274
|
+
("auto", .auto)
|
|
275
|
+
]
|
|
276
|
+
if #available(iOS 17.0, *) {
|
|
277
|
+
candidates.append(("previewOptimized", .previewOptimized))
|
|
278
|
+
}
|
|
279
|
+
if #available(iOS 18.0, *) {
|
|
280
|
+
candidates.append(("cinematicExtendedEnhanced", .cinematicExtendedEnhanced))
|
|
281
|
+
}
|
|
282
|
+
return candidates
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func applyVideoStabilizationMode(_ mode: AVCaptureVideoStabilizationMode? = nil) {
|
|
286
|
+
guard let connection = self.fileVideoOutput?.connection(with: .video),
|
|
287
|
+
connection.isVideoStabilizationSupported else {
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
let targetMode = mode ?? self.avVideoStabilizationMode(from: self.videoStabilizationMode) ?? .off
|
|
291
|
+
connection.preferredVideoStabilizationMode = targetMode
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private func avVideoStabilizationMode(from mode: String) -> AVCaptureVideoStabilizationMode? {
|
|
295
|
+
switch mode {
|
|
296
|
+
case "off":
|
|
297
|
+
return .off
|
|
298
|
+
case "standard":
|
|
299
|
+
return .standard
|
|
300
|
+
case "cinematic":
|
|
301
|
+
return .cinematic
|
|
302
|
+
case "cinematicExtended":
|
|
303
|
+
return .cinematicExtended
|
|
304
|
+
case "auto":
|
|
305
|
+
return .auto
|
|
306
|
+
case "previewOptimized":
|
|
307
|
+
if #available(iOS 17.0, *) {
|
|
308
|
+
return .previewOptimized
|
|
309
|
+
}
|
|
310
|
+
return nil
|
|
311
|
+
case "cinematicExtendedEnhanced":
|
|
312
|
+
if #available(iOS 18.0, *) {
|
|
313
|
+
return .cinematicExtendedEnhanced
|
|
314
|
+
}
|
|
315
|
+
return nil
|
|
316
|
+
case "lowLatency":
|
|
317
|
+
return nil
|
|
318
|
+
default:
|
|
319
|
+
return nil
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private func videoStabilizationModeString(from mode: AVCaptureVideoStabilizationMode) -> String {
|
|
324
|
+
switch mode {
|
|
325
|
+
case .off:
|
|
326
|
+
return "off"
|
|
327
|
+
case .standard:
|
|
328
|
+
return "standard"
|
|
329
|
+
case .cinematic:
|
|
330
|
+
return "cinematic"
|
|
331
|
+
case .cinematicExtended:
|
|
332
|
+
return "cinematicExtended"
|
|
333
|
+
case .auto:
|
|
334
|
+
return "auto"
|
|
335
|
+
default:
|
|
336
|
+
if #available(iOS 17.0, *) {
|
|
337
|
+
if mode == .previewOptimized {
|
|
338
|
+
return "previewOptimized"
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if #available(iOS 18.0, *) {
|
|
342
|
+
if mode == .cinematicExtendedEnhanced {
|
|
343
|
+
return "cinematicExtendedEnhanced"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return self.videoStabilizationMode
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
216
350
|
private func avVideoCodecType(for codec: String) -> AVVideoCodecType? {
|
|
217
351
|
switch codec.lowercased() {
|
|
218
352
|
case "avc1":
|
|
@@ -515,6 +649,7 @@ extension CameraController {
|
|
|
515
649
|
captureSession.addOutput(fileVideoOutput)
|
|
516
650
|
// Set orientation immediately
|
|
517
651
|
self.setVideoOrientation(videoOrientation, on: fileVideoOutput.connections)
|
|
652
|
+
self.applyVideoStabilizationMode()
|
|
518
653
|
}
|
|
519
654
|
|
|
520
655
|
// Set up preview layer session in the same configuration block
|
|
@@ -987,6 +1122,7 @@ extension CameraController {
|
|
|
987
1122
|
}
|
|
988
1123
|
|
|
989
1124
|
func switchCameras() throws {
|
|
1125
|
+
configuredVideoFrameRate = nil
|
|
990
1126
|
guard let currentCameraPosition = currentCameraPosition,
|
|
991
1127
|
let captureSession = self.captureSession else {
|
|
992
1128
|
throw CameraControllerError.captureSessionIsMissing
|
|
@@ -1094,6 +1230,7 @@ extension CameraController {
|
|
|
1094
1230
|
captureSession.removeOutput(fileVideoOutput)
|
|
1095
1231
|
if captureSession.canAddOutput(fileVideoOutput) {
|
|
1096
1232
|
captureSession.addOutput(fileVideoOutput)
|
|
1233
|
+
self.applyVideoStabilizationMode()
|
|
1097
1234
|
}
|
|
1098
1235
|
}
|
|
1099
1236
|
|
|
@@ -2240,6 +2377,7 @@ extension CameraController {
|
|
|
2240
2377
|
}
|
|
2241
2378
|
|
|
2242
2379
|
func cleanup() {
|
|
2380
|
+
configuredVideoFrameRate = nil
|
|
2243
2381
|
stopBarcodeScanner()
|
|
2244
2382
|
if let captureSession = self.captureSession {
|
|
2245
2383
|
captureSession.stopRunning()
|
|
@@ -2447,6 +2585,129 @@ extension CameraController {
|
|
|
2447
2585
|
}
|
|
2448
2586
|
}
|
|
2449
2587
|
|
|
2588
|
+
private func activeVideoDevice() -> AVCaptureDevice? {
|
|
2589
|
+
switch currentCameraPosition {
|
|
2590
|
+
case .front:
|
|
2591
|
+
return self.frontCamera
|
|
2592
|
+
case .rear:
|
|
2593
|
+
return self.rearCamera
|
|
2594
|
+
default:
|
|
2595
|
+
return nil
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
private func frameRates(from format: AVCaptureDevice.Format) -> [Int] {
|
|
2600
|
+
var rates = Set<Int>()
|
|
2601
|
+
let standardRates = [24, 25, 30, 50, 60, 120]
|
|
2602
|
+
|
|
2603
|
+
for range in format.videoSupportedFrameRateRanges {
|
|
2604
|
+
let minFps = Int(ceil(range.minFrameRate))
|
|
2605
|
+
let maxFps = Int(floor(range.maxFrameRate))
|
|
2606
|
+
|
|
2607
|
+
if minFps == maxFps {
|
|
2608
|
+
rates.insert(minFps)
|
|
2609
|
+
continue
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
rates.insert(minFps)
|
|
2613
|
+
rates.insert(maxFps)
|
|
2614
|
+
|
|
2615
|
+
for rate in standardRates where rate >= minFps && rate <= maxFps {
|
|
2616
|
+
rates.insert(rate)
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
return rates.sorted()
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
private func formatSupportsFrameRate(_ format: AVCaptureDevice.Format, frameRate: Int) -> Bool {
|
|
2624
|
+
let fps = Double(frameRate)
|
|
2625
|
+
return format.videoSupportedFrameRateRanges.contains { range in
|
|
2626
|
+
fps >= range.minFrameRate && fps <= range.maxFrameRate
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
private func frameRate(from duration: CMTime) -> Int {
|
|
2631
|
+
guard duration.value > 0 else { return 30 }
|
|
2632
|
+
return Int(round(Double(duration.timescale) / Double(duration.value)))
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
private func allSupportedFrameRates(for device: AVCaptureDevice) -> [Int] {
|
|
2636
|
+
var rates = Set<Int>()
|
|
2637
|
+
for format in device.formats {
|
|
2638
|
+
for rate in frameRates(from: format) {
|
|
2639
|
+
rates.insert(rate)
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
return rates.sorted()
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
func getSupportedVideoFrameRates() throws -> [Int] {
|
|
2646
|
+
guard let device = activeVideoDevice() else {
|
|
2647
|
+
throw CameraControllerError.noCamerasAvailable
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
return allSupportedFrameRates(for: device)
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
func getVideoFrameRate() throws -> Int {
|
|
2654
|
+
if let configuredVideoFrameRate = self.configuredVideoFrameRate {
|
|
2655
|
+
return configuredVideoFrameRate
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
guard let device = activeVideoDevice() else {
|
|
2659
|
+
throw CameraControllerError.noCamerasAvailable
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
return frameRate(from: device.activeVideoMinFrameDuration)
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
func setVideoFrameRate(_ frameRate: Int) throws {
|
|
2666
|
+
guard frameRate > 0 else {
|
|
2667
|
+
throw NSError(domain: "CameraPreview", code: 0, userInfo: [NSLocalizedDescriptionKey: "frameRate must be greater than 0"])
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
if let fileVideoOutput = self.fileVideoOutput, fileVideoOutput.isRecording {
|
|
2671
|
+
throw NSError(domain: "CameraPreview", code: 0, userInfo: [NSLocalizedDescriptionKey: "Cannot change video frame rate while recording"])
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
guard let device = activeVideoDevice() else {
|
|
2675
|
+
throw CameraControllerError.noCamerasAvailable
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
let supportedRates = allSupportedFrameRates(for: device)
|
|
2679
|
+
guard supportedRates.contains(frameRate) else {
|
|
2680
|
+
throw NSError(
|
|
2681
|
+
domain: "CameraPreview",
|
|
2682
|
+
code: 0,
|
|
2683
|
+
userInfo: [NSLocalizedDescriptionKey: "Unsupported frame rate \(frameRate). Supported values: \(supportedRates.map(String.init).joined(separator: ", "))"]
|
|
2684
|
+
)
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
guard let targetFormat = device.formats.first(where: { formatSupportsFrameRate($0, frameRate: frameRate) }) else {
|
|
2688
|
+
throw NSError(
|
|
2689
|
+
domain: "CameraPreview",
|
|
2690
|
+
code: 0,
|
|
2691
|
+
userInfo: [NSLocalizedDescriptionKey: "Unsupported frame rate \(frameRate) for the active camera"]
|
|
2692
|
+
)
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
let frameDuration = CMTime(value: 1, timescale: CMTimeScale(frameRate))
|
|
2696
|
+
|
|
2697
|
+
do {
|
|
2698
|
+
try device.lockForConfiguration()
|
|
2699
|
+
if device.activeFormat != targetFormat {
|
|
2700
|
+
device.activeFormat = targetFormat
|
|
2701
|
+
}
|
|
2702
|
+
device.activeVideoMinFrameDuration = frameDuration
|
|
2703
|
+
device.activeVideoMaxFrameDuration = frameDuration
|
|
2704
|
+
device.unlockForConfiguration()
|
|
2705
|
+
self.configuredVideoFrameRate = frameRate
|
|
2706
|
+
} catch {
|
|
2707
|
+
throw CameraControllerError.invalidOperation
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2450
2711
|
func captureVideo(maxDuration: Float? = nil, maxFileSize: Int? = nil) throws {
|
|
2451
2712
|
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
2452
2713
|
throw CameraControllerError.captureSessionIsMissing
|
|
@@ -2479,6 +2740,7 @@ extension CameraController {
|
|
|
2479
2740
|
captureSession.beginConfiguration()
|
|
2480
2741
|
if captureSession.canAddOutput(fileVideoOutput) {
|
|
2481
2742
|
captureSession.addOutput(fileVideoOutput)
|
|
2743
|
+
self.applyVideoStabilizationMode()
|
|
2482
2744
|
} else {
|
|
2483
2745
|
captureSession.commitConfiguration()
|
|
2484
2746
|
throw CameraControllerError.invalidOperation
|
|
@@ -2520,6 +2782,8 @@ extension CameraController {
|
|
|
2520
2782
|
fileVideoOutput.setOutputSettings([AVVideoCodecKey: codecType], for: connection)
|
|
2521
2783
|
}
|
|
2522
2784
|
|
|
2785
|
+
self.applyVideoStabilizationMode()
|
|
2786
|
+
|
|
2523
2787
|
// Start recording video
|
|
2524
2788
|
fileVideoOutput.startRecording(to: fileUrl, recordingDelegate: self)
|
|
2525
2789
|
|
|
@@ -2527,21 +2791,26 @@ extension CameraController {
|
|
|
2527
2791
|
self.videoFileURL = fileUrl
|
|
2528
2792
|
}
|
|
2529
2793
|
|
|
2530
|
-
func stopRecording(completion: @escaping (URL?, Error?) -> Void) {
|
|
2794
|
+
func stopRecording(completion: @escaping (URL?, String?, Error?) -> Void) {
|
|
2531
2795
|
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
2532
|
-
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
2796
|
+
completion(nil, nil, CameraControllerError.captureSessionIsMissing)
|
|
2533
2797
|
return
|
|
2534
2798
|
}
|
|
2535
2799
|
guard let fileVideoOutput = self.fileVideoOutput else {
|
|
2536
|
-
completion(nil, CameraControllerError.fileVideoOutputNotFound)
|
|
2800
|
+
completion(nil, nil, CameraControllerError.fileVideoOutputNotFound)
|
|
2801
|
+
return
|
|
2802
|
+
}
|
|
2803
|
+
guard fileVideoOutput.isRecording else {
|
|
2804
|
+
completion(nil, nil, CameraControllerError.invalidOperation)
|
|
2805
|
+
return
|
|
2806
|
+
}
|
|
2807
|
+
guard self.stopRecordingCompletion == nil else {
|
|
2808
|
+
completion(nil, nil, CameraControllerError.invalidOperation)
|
|
2537
2809
|
return
|
|
2538
2810
|
}
|
|
2539
2811
|
|
|
2540
|
-
|
|
2812
|
+
self.stopRecordingCompletion = completion
|
|
2541
2813
|
fileVideoOutput.stopRecording()
|
|
2542
|
-
|
|
2543
|
-
// Return the video file URL in the completion handler
|
|
2544
|
-
completion(self.videoFileURL, nil)
|
|
2545
2814
|
}
|
|
2546
2815
|
}
|
|
2547
2816
|
|
|
@@ -2824,6 +3093,7 @@ enum CameraControllerError: Swift.Error {
|
|
|
2824
3093
|
case invalidOperation
|
|
2825
3094
|
case noCamerasAvailable
|
|
2826
3095
|
case cannotFindDocumentsDirectory
|
|
3096
|
+
case recordingInProgress
|
|
2827
3097
|
case fileVideoOutputNotFound
|
|
2828
3098
|
case unknown
|
|
2829
3099
|
case invalidZoomLevel(min: CGFloat, max: CGFloat, requested: CGFloat)
|
|
@@ -2851,6 +3121,8 @@ extension CameraControllerError: LocalizedError {
|
|
|
2851
3121
|
return NSLocalizedString("Unknown", comment: "Unknown")
|
|
2852
3122
|
case .cannotFindDocumentsDirectory:
|
|
2853
3123
|
return NSLocalizedString("Cannot find documents directory", comment: "This should never happen")
|
|
3124
|
+
case .recordingInProgress:
|
|
3125
|
+
return NSLocalizedString("Cannot change video stabilization while recording is in progress.", comment: "Recording in progress")
|
|
2854
3126
|
case .fileVideoOutputNotFound:
|
|
2855
3127
|
return NSLocalizedString("Video recording is not available. Make sure the camera is properly initialized.", comment: "Video recording not available")
|
|
2856
3128
|
case .invalidZoomLevel(let min, let max, let requested):
|
|
@@ -2934,6 +3206,19 @@ extension UIImage {
|
|
|
2934
3206
|
extension CameraController: AVCaptureFileOutputRecordingDelegate {
|
|
2935
3207
|
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
|
|
2936
3208
|
let reason = recordingFinishReason(from: error)
|
|
3209
|
+
let stopCompletion = self.stopRecordingCompletion
|
|
3210
|
+
self.stopRecordingCompletion = nil
|
|
3211
|
+
|
|
3212
|
+
if let stopCompletion = stopCompletion {
|
|
3213
|
+
DispatchQueue.main.async {
|
|
3214
|
+
if reason == "error" {
|
|
3215
|
+
stopCompletion(nil, nil, error ?? CameraControllerError.unknown)
|
|
3216
|
+
} else {
|
|
3217
|
+
stopCompletion(outputFileURL, reason, nil)
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
|
|
2937
3222
|
if reason == "error" {
|
|
2938
3223
|
print("Error recording movie: \(error?.localizedDescription ?? "unknown")")
|
|
2939
3224
|
return
|
|
@@ -35,7 +35,7 @@ extension UIWindow {
|
|
|
35
35
|
*/
|
|
36
36
|
@objc(CameraPreview)
|
|
37
37
|
public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
|
|
38
|
-
private let pluginVersion: String = "8.
|
|
38
|
+
private let pluginVersion: String = "8.7.1"
|
|
39
39
|
public let identifier = "CameraPreviewPlugin"
|
|
40
40
|
public let jsName = "CameraPreview"
|
|
41
41
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -54,6 +54,10 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
54
54
|
CAPPluginMethod(name: "getSupportedVideoCodecs", returnType: CAPPluginReturnPromise),
|
|
55
55
|
CAPPluginMethod(name: "getVideoCodec", returnType: CAPPluginReturnPromise),
|
|
56
56
|
CAPPluginMethod(name: "setVideoCodec", returnType: CAPPluginReturnPromise),
|
|
57
|
+
CAPPluginMethod(name: "isVideoStabilizationSupported", returnType: CAPPluginReturnPromise),
|
|
58
|
+
CAPPluginMethod(name: "getSupportedVideoStabilizationModes", returnType: CAPPluginReturnPromise),
|
|
59
|
+
CAPPluginMethod(name: "getVideoStabilizationMode", returnType: CAPPluginReturnPromise),
|
|
60
|
+
CAPPluginMethod(name: "setVideoStabilizationMode", returnType: CAPPluginReturnPromise),
|
|
57
61
|
CAPPluginMethod(name: "getSupportedVideoQualities", returnType: CAPPluginReturnPromise),
|
|
58
62
|
CAPPluginMethod(name: "getVideoQuality", returnType: CAPPluginReturnPromise),
|
|
59
63
|
CAPPluginMethod(name: "setVideoQuality", returnType: CAPPluginReturnPromise),
|
|
@@ -86,6 +90,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
86
90
|
CAPPluginMethod(name: "getExposureCompensationRange", returnType: CAPPluginReturnPromise),
|
|
87
91
|
CAPPluginMethod(name: "getExposureCompensation", returnType: CAPPluginReturnPromise),
|
|
88
92
|
CAPPluginMethod(name: "setExposureCompensation", returnType: CAPPluginReturnPromise),
|
|
93
|
+
CAPPluginMethod(name: "getSupportedVideoFrameRates", returnType: CAPPluginReturnPromise),
|
|
94
|
+
CAPPluginMethod(name: "getVideoFrameRate", returnType: CAPPluginReturnPromise),
|
|
95
|
+
CAPPluginMethod(name: "setVideoFrameRate", returnType: CAPPluginReturnPromise),
|
|
89
96
|
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
90
97
|
]
|
|
91
98
|
// Camera state tracking
|
|
@@ -1643,12 +1650,45 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1643
1650
|
call.resolve(["codecs": self.cameraController.getSupportedVideoCodecs()])
|
|
1644
1651
|
}
|
|
1645
1652
|
|
|
1653
|
+
@objc func isVideoStabilizationSupported(_ call: CAPPluginCall) {
|
|
1654
|
+
call.resolve(["supported": self.cameraController.isVideoStabilizationSupported()])
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
@objc func getSupportedVideoStabilizationModes(_ call: CAPPluginCall) {
|
|
1658
|
+
call.resolve(["modes": self.cameraController.getSupportedVideoStabilizationModes()])
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
@objc func getVideoStabilizationMode(_ call: CAPPluginCall) {
|
|
1662
|
+
call.resolve(["mode": self.cameraController.getVideoStabilizationMode()])
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
@objc func setVideoStabilizationMode(_ call: CAPPluginCall) {
|
|
1666
|
+
guard let mode = call.getString("mode") else {
|
|
1667
|
+
call.reject("mode is required")
|
|
1668
|
+
return
|
|
1669
|
+
}
|
|
1670
|
+
do {
|
|
1671
|
+
try self.cameraController.setVideoStabilizationMode(mode)
|
|
1672
|
+
call.resolve()
|
|
1673
|
+
} catch {
|
|
1674
|
+
call.reject("Failed to set video stabilization mode: \(error.localizedDescription)")
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1646
1678
|
@objc func startRecordVideo(_ call: CAPPluginCall) {
|
|
1647
1679
|
let maxDuration = call.getFloat("maxDuration")
|
|
1648
1680
|
let maxFileSize = call.getInt("maxFileSize")
|
|
1649
1681
|
if let videoCodec = call.getString("videoCodec") {
|
|
1650
1682
|
try? self.cameraController.setVideoCodec(videoCodec)
|
|
1651
1683
|
}
|
|
1684
|
+
if let videoStabilizationMode = call.getString("videoStabilizationMode") {
|
|
1685
|
+
do {
|
|
1686
|
+
try self.cameraController.setVideoStabilizationMode(videoStabilizationMode)
|
|
1687
|
+
} catch {
|
|
1688
|
+
call.reject("Failed to set video stabilization mode: \(error.localizedDescription)")
|
|
1689
|
+
return
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1652
1692
|
self.cameraController.recordingFinishedCallback = { [weak self] fileURL, reason in
|
|
1653
1693
|
DispatchQueue.main.async {
|
|
1654
1694
|
self?.notifyListeners("recordingFinished", data: ["videoFilePath": fileURL.absoluteString, "reason": reason])
|
|
@@ -1656,6 +1696,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1656
1696
|
}
|
|
1657
1697
|
|
|
1658
1698
|
do {
|
|
1699
|
+
if let frameRate = call.getInt("frameRate") {
|
|
1700
|
+
try self.cameraController.setVideoFrameRate(frameRate)
|
|
1701
|
+
}
|
|
1659
1702
|
try self.cameraController.captureVideo(maxDuration: maxDuration, maxFileSize: maxFileSize)
|
|
1660
1703
|
call.resolve()
|
|
1661
1704
|
} catch {
|
|
@@ -1664,8 +1707,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1664
1707
|
}
|
|
1665
1708
|
|
|
1666
1709
|
@objc func stopRecordVideo(_ call: CAPPluginCall) {
|
|
1667
|
-
self.cameraController.stopRecording { (fileURL, error) in
|
|
1668
|
-
guard let fileURL = fileURL else {
|
|
1710
|
+
self.cameraController.stopRecording { (fileURL, reason, error) in
|
|
1711
|
+
guard let fileURL = fileURL, let reason = reason else {
|
|
1669
1712
|
print(error ?? "Video capture error")
|
|
1670
1713
|
guard let error = error else {
|
|
1671
1714
|
call.reject("Video capture error")
|
|
@@ -1675,7 +1718,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1675
1718
|
return
|
|
1676
1719
|
}
|
|
1677
1720
|
|
|
1678
|
-
call.resolve(["videoFilePath": fileURL.absoluteString, "reason":
|
|
1721
|
+
call.resolve(["videoFilePath": fileURL.absoluteString, "reason": reason])
|
|
1679
1722
|
}
|
|
1680
1723
|
}
|
|
1681
1724
|
|
|
@@ -2436,6 +2479,49 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
2436
2479
|
}
|
|
2437
2480
|
}
|
|
2438
2481
|
|
|
2482
|
+
@objc func getSupportedVideoFrameRates(_ call: CAPPluginCall) {
|
|
2483
|
+
guard isInitialized else {
|
|
2484
|
+
call.reject("Camera not initialized")
|
|
2485
|
+
return
|
|
2486
|
+
}
|
|
2487
|
+
do {
|
|
2488
|
+
let frameRates = try self.cameraController.getSupportedVideoFrameRates()
|
|
2489
|
+
call.resolve(["frameRates": frameRates])
|
|
2490
|
+
} catch {
|
|
2491
|
+
call.reject("Failed to get supported video frame rates: \(error.localizedDescription)")
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
@objc func getVideoFrameRate(_ call: CAPPluginCall) {
|
|
2496
|
+
guard isInitialized else {
|
|
2497
|
+
call.reject("Camera not initialized")
|
|
2498
|
+
return
|
|
2499
|
+
}
|
|
2500
|
+
do {
|
|
2501
|
+
let frameRate = try self.cameraController.getVideoFrameRate()
|
|
2502
|
+
call.resolve(["frameRate": frameRate])
|
|
2503
|
+
} catch {
|
|
2504
|
+
call.reject("Failed to get video frame rate: \(error.localizedDescription)")
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
@objc func setVideoFrameRate(_ call: CAPPluginCall) {
|
|
2509
|
+
guard isInitialized else {
|
|
2510
|
+
call.reject("Camera not initialized")
|
|
2511
|
+
return
|
|
2512
|
+
}
|
|
2513
|
+
guard let frameRate = call.getInt("frameRate") else {
|
|
2514
|
+
call.reject("frameRate parameter is required")
|
|
2515
|
+
return
|
|
2516
|
+
}
|
|
2517
|
+
do {
|
|
2518
|
+
try self.cameraController.setVideoFrameRate(frameRate)
|
|
2519
|
+
call.resolve()
|
|
2520
|
+
} catch {
|
|
2521
|
+
call.reject(error.localizedDescription)
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2439
2525
|
private var lastOrientation: String?
|
|
2440
2526
|
|
|
2441
2527
|
@objc private func handleOrientationChange() {
|