@capgo/camera-preview 8.6.0 → 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 +60 -3
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +69 -16
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +222 -1
- package/dist/docs.json +82 -3
- package/dist/esm/definitions.d.ts +41 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +9 -0
- package/dist/esm/web.js +10 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +9 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +9 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +154 -9
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +53 -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
|
|
@@ -150,9 +150,11 @@ class CameraController: NSObject {
|
|
|
150
150
|
|
|
151
151
|
var videoFileURL: URL?
|
|
152
152
|
var recordingFinishedCallback: ((URL, String) -> Void)?
|
|
153
|
+
private var stopRecordingCompletion: ((URL?, String?, Error?) -> Void)?
|
|
153
154
|
private let saneMaxZoomFactor: CGFloat = 25.5
|
|
154
155
|
|
|
155
156
|
var videoQuality: String = "high"
|
|
157
|
+
private var configuredVideoFrameRate: Int?
|
|
156
158
|
var videoCodec: String = "avc1"
|
|
157
159
|
var videoStabilizationMode: String = "off"
|
|
158
160
|
|
|
@@ -269,7 +271,7 @@ class CameraController: NSObject {
|
|
|
269
271
|
("standard", .standard),
|
|
270
272
|
("cinematic", .cinematic),
|
|
271
273
|
("cinematicExtended", .cinematicExtended),
|
|
272
|
-
("auto", .auto)
|
|
274
|
+
("auto", .auto)
|
|
273
275
|
]
|
|
274
276
|
if #available(iOS 17.0, *) {
|
|
275
277
|
candidates.append(("previewOptimized", .previewOptimized))
|
|
@@ -1120,6 +1122,7 @@ extension CameraController {
|
|
|
1120
1122
|
}
|
|
1121
1123
|
|
|
1122
1124
|
func switchCameras() throws {
|
|
1125
|
+
configuredVideoFrameRate = nil
|
|
1123
1126
|
guard let currentCameraPosition = currentCameraPosition,
|
|
1124
1127
|
let captureSession = self.captureSession else {
|
|
1125
1128
|
throw CameraControllerError.captureSessionIsMissing
|
|
@@ -2374,6 +2377,7 @@ extension CameraController {
|
|
|
2374
2377
|
}
|
|
2375
2378
|
|
|
2376
2379
|
func cleanup() {
|
|
2380
|
+
configuredVideoFrameRate = nil
|
|
2377
2381
|
stopBarcodeScanner()
|
|
2378
2382
|
if let captureSession = self.captureSession {
|
|
2379
2383
|
captureSession.stopRunning()
|
|
@@ -2581,6 +2585,129 @@ extension CameraController {
|
|
|
2581
2585
|
}
|
|
2582
2586
|
}
|
|
2583
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
|
+
|
|
2584
2711
|
func captureVideo(maxDuration: Float? = nil, maxFileSize: Int? = nil) throws {
|
|
2585
2712
|
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
2586
2713
|
throw CameraControllerError.captureSessionIsMissing
|
|
@@ -2664,21 +2791,26 @@ extension CameraController {
|
|
|
2664
2791
|
self.videoFileURL = fileUrl
|
|
2665
2792
|
}
|
|
2666
2793
|
|
|
2667
|
-
func stopRecording(completion: @escaping (URL?, Error?) -> Void) {
|
|
2794
|
+
func stopRecording(completion: @escaping (URL?, String?, Error?) -> Void) {
|
|
2668
2795
|
guard let captureSession = self.captureSession, captureSession.isRunning else {
|
|
2669
|
-
completion(nil, CameraControllerError.captureSessionIsMissing)
|
|
2796
|
+
completion(nil, nil, CameraControllerError.captureSessionIsMissing)
|
|
2670
2797
|
return
|
|
2671
2798
|
}
|
|
2672
2799
|
guard let fileVideoOutput = self.fileVideoOutput else {
|
|
2673
|
-
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)
|
|
2674
2809
|
return
|
|
2675
2810
|
}
|
|
2676
2811
|
|
|
2677
|
-
|
|
2812
|
+
self.stopRecordingCompletion = completion
|
|
2678
2813
|
fileVideoOutput.stopRecording()
|
|
2679
|
-
|
|
2680
|
-
// Return the video file URL in the completion handler
|
|
2681
|
-
completion(self.videoFileURL, nil)
|
|
2682
2814
|
}
|
|
2683
2815
|
}
|
|
2684
2816
|
|
|
@@ -3074,6 +3206,19 @@ extension UIImage {
|
|
|
3074
3206
|
extension CameraController: AVCaptureFileOutputRecordingDelegate {
|
|
3075
3207
|
func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
|
|
3076
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
|
+
|
|
3077
3222
|
if reason == "error" {
|
|
3078
3223
|
print("Error recording movie: \(error?.localizedDescription ?? "unknown")")
|
|
3079
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] = [
|
|
@@ -90,6 +90,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
90
90
|
CAPPluginMethod(name: "getExposureCompensationRange", returnType: CAPPluginReturnPromise),
|
|
91
91
|
CAPPluginMethod(name: "getExposureCompensation", returnType: CAPPluginReturnPromise),
|
|
92
92
|
CAPPluginMethod(name: "setExposureCompensation", returnType: CAPPluginReturnPromise),
|
|
93
|
+
CAPPluginMethod(name: "getSupportedVideoFrameRates", returnType: CAPPluginReturnPromise),
|
|
94
|
+
CAPPluginMethod(name: "getVideoFrameRate", returnType: CAPPluginReturnPromise),
|
|
95
|
+
CAPPluginMethod(name: "setVideoFrameRate", returnType: CAPPluginReturnPromise),
|
|
93
96
|
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
94
97
|
]
|
|
95
98
|
// Camera state tracking
|
|
@@ -1693,6 +1696,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1693
1696
|
}
|
|
1694
1697
|
|
|
1695
1698
|
do {
|
|
1699
|
+
if let frameRate = call.getInt("frameRate") {
|
|
1700
|
+
try self.cameraController.setVideoFrameRate(frameRate)
|
|
1701
|
+
}
|
|
1696
1702
|
try self.cameraController.captureVideo(maxDuration: maxDuration, maxFileSize: maxFileSize)
|
|
1697
1703
|
call.resolve()
|
|
1698
1704
|
} catch {
|
|
@@ -1701,8 +1707,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1701
1707
|
}
|
|
1702
1708
|
|
|
1703
1709
|
@objc func stopRecordVideo(_ call: CAPPluginCall) {
|
|
1704
|
-
self.cameraController.stopRecording { (fileURL, error) in
|
|
1705
|
-
guard let fileURL = fileURL else {
|
|
1710
|
+
self.cameraController.stopRecording { (fileURL, reason, error) in
|
|
1711
|
+
guard let fileURL = fileURL, let reason = reason else {
|
|
1706
1712
|
print(error ?? "Video capture error")
|
|
1707
1713
|
guard let error = error else {
|
|
1708
1714
|
call.reject("Video capture error")
|
|
@@ -1712,7 +1718,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
1712
1718
|
return
|
|
1713
1719
|
}
|
|
1714
1720
|
|
|
1715
|
-
call.resolve(["videoFilePath": fileURL.absoluteString, "reason":
|
|
1721
|
+
call.resolve(["videoFilePath": fileURL.absoluteString, "reason": reason])
|
|
1716
1722
|
}
|
|
1717
1723
|
}
|
|
1718
1724
|
|
|
@@ -2473,6 +2479,49 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
|
|
|
2473
2479
|
}
|
|
2474
2480
|
}
|
|
2475
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
|
+
|
|
2476
2525
|
private var lastOrientation: String?
|
|
2477
2526
|
|
|
2478
2527
|
@objc private func handleOrientationChange() {
|