@capgo/camera-preview 8.5.1 → 8.6.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.
@@ -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
@@ -155,6 +154,7 @@ class CameraController: NSObject {
155
154
 
156
155
  var videoQuality: String = "high"
157
156
  var videoCodec: String = "avc1"
157
+ var videoStabilizationMode: String = "off"
158
158
 
159
159
  private static let allVideoQualities = ["low", "medium", "high", "2160p", "1080p", "720p", "480p", "4:3"]
160
160
 
@@ -213,6 +213,138 @@ class CameraController: NSObject {
213
213
  return self.videoCodec
214
214
  }
215
215
 
216
+ func isVideoStabilizationSupported() -> Bool {
217
+ guard let connection = self.fileVideoOutput?.connection(with: .video) else {
218
+ return false
219
+ }
220
+ return connection.isVideoStabilizationSupported
221
+ }
222
+
223
+ func getSupportedVideoStabilizationModes() -> [String] {
224
+ guard let connection = self.fileVideoOutput?.connection(with: .video),
225
+ connection.isVideoStabilizationSupported else {
226
+ return ["off"]
227
+ }
228
+
229
+ let savedPreferred = connection.preferredVideoStabilizationMode
230
+ var supported: [String] = []
231
+
232
+ for candidate in Self.allVideoStabilizationModeCandidates() {
233
+ connection.preferredVideoStabilizationMode = candidate.mode
234
+ if connection.preferredVideoStabilizationMode == candidate.mode {
235
+ supported.append(candidate.name)
236
+ }
237
+ }
238
+
239
+ connection.preferredVideoStabilizationMode = savedPreferred
240
+ return supported.isEmpty ? ["off"] : supported
241
+ }
242
+
243
+ func getVideoStabilizationMode() -> String {
244
+ if let connection = self.fileVideoOutput?.connection(with: .video),
245
+ connection.isVideoStabilizationSupported {
246
+ return self.videoStabilizationModeString(from: connection.activeVideoStabilizationMode)
247
+ }
248
+ return self.videoStabilizationMode
249
+ }
250
+
251
+ func setVideoStabilizationMode(_ mode: String) throws {
252
+ if self.fileVideoOutput?.isRecording == true {
253
+ throw CameraControllerError.recordingInProgress
254
+ }
255
+ guard let avMode = self.avVideoStabilizationMode(from: mode) else {
256
+ throw CameraControllerError.invalidOperation
257
+ }
258
+ let supportedModes = self.getSupportedVideoStabilizationModes()
259
+ guard supportedModes.contains(mode) else {
260
+ throw CameraControllerError.invalidOperation
261
+ }
262
+ self.videoStabilizationMode = mode
263
+ self.applyVideoStabilizationMode(avMode)
264
+ }
265
+
266
+ private static func allVideoStabilizationModeCandidates() -> [(name: String, mode: AVCaptureVideoStabilizationMode)] {
267
+ var candidates: [(String, AVCaptureVideoStabilizationMode)] = [
268
+ ("off", .off),
269
+ ("standard", .standard),
270
+ ("cinematic", .cinematic),
271
+ ("cinematicExtended", .cinematicExtended),
272
+ ("auto", .auto),
273
+ ]
274
+ if #available(iOS 17.0, *) {
275
+ candidates.append(("previewOptimized", .previewOptimized))
276
+ }
277
+ if #available(iOS 18.0, *) {
278
+ candidates.append(("cinematicExtendedEnhanced", .cinematicExtendedEnhanced))
279
+ }
280
+ return candidates
281
+ }
282
+
283
+ private func applyVideoStabilizationMode(_ mode: AVCaptureVideoStabilizationMode? = nil) {
284
+ guard let connection = self.fileVideoOutput?.connection(with: .video),
285
+ connection.isVideoStabilizationSupported else {
286
+ return
287
+ }
288
+ let targetMode = mode ?? self.avVideoStabilizationMode(from: self.videoStabilizationMode) ?? .off
289
+ connection.preferredVideoStabilizationMode = targetMode
290
+ }
291
+
292
+ private func avVideoStabilizationMode(from mode: String) -> AVCaptureVideoStabilizationMode? {
293
+ switch mode {
294
+ case "off":
295
+ return .off
296
+ case "standard":
297
+ return .standard
298
+ case "cinematic":
299
+ return .cinematic
300
+ case "cinematicExtended":
301
+ return .cinematicExtended
302
+ case "auto":
303
+ return .auto
304
+ case "previewOptimized":
305
+ if #available(iOS 17.0, *) {
306
+ return .previewOptimized
307
+ }
308
+ return nil
309
+ case "cinematicExtendedEnhanced":
310
+ if #available(iOS 18.0, *) {
311
+ return .cinematicExtendedEnhanced
312
+ }
313
+ return nil
314
+ case "lowLatency":
315
+ return nil
316
+ default:
317
+ return nil
318
+ }
319
+ }
320
+
321
+ private func videoStabilizationModeString(from mode: AVCaptureVideoStabilizationMode) -> String {
322
+ switch mode {
323
+ case .off:
324
+ return "off"
325
+ case .standard:
326
+ return "standard"
327
+ case .cinematic:
328
+ return "cinematic"
329
+ case .cinematicExtended:
330
+ return "cinematicExtended"
331
+ case .auto:
332
+ return "auto"
333
+ default:
334
+ if #available(iOS 17.0, *) {
335
+ if mode == .previewOptimized {
336
+ return "previewOptimized"
337
+ }
338
+ }
339
+ if #available(iOS 18.0, *) {
340
+ if mode == .cinematicExtendedEnhanced {
341
+ return "cinematicExtendedEnhanced"
342
+ }
343
+ }
344
+ return self.videoStabilizationMode
345
+ }
346
+ }
347
+
216
348
  private func avVideoCodecType(for codec: String) -> AVVideoCodecType? {
217
349
  switch codec.lowercased() {
218
350
  case "avc1":
@@ -515,6 +647,7 @@ extension CameraController {
515
647
  captureSession.addOutput(fileVideoOutput)
516
648
  // Set orientation immediately
517
649
  self.setVideoOrientation(videoOrientation, on: fileVideoOutput.connections)
650
+ self.applyVideoStabilizationMode()
518
651
  }
519
652
 
520
653
  // Set up preview layer session in the same configuration block
@@ -1094,6 +1227,7 @@ extension CameraController {
1094
1227
  captureSession.removeOutput(fileVideoOutput)
1095
1228
  if captureSession.canAddOutput(fileVideoOutput) {
1096
1229
  captureSession.addOutput(fileVideoOutput)
1230
+ self.applyVideoStabilizationMode()
1097
1231
  }
1098
1232
  }
1099
1233
 
@@ -2479,6 +2613,7 @@ extension CameraController {
2479
2613
  captureSession.beginConfiguration()
2480
2614
  if captureSession.canAddOutput(fileVideoOutput) {
2481
2615
  captureSession.addOutput(fileVideoOutput)
2616
+ self.applyVideoStabilizationMode()
2482
2617
  } else {
2483
2618
  captureSession.commitConfiguration()
2484
2619
  throw CameraControllerError.invalidOperation
@@ -2520,6 +2655,8 @@ extension CameraController {
2520
2655
  fileVideoOutput.setOutputSettings([AVVideoCodecKey: codecType], for: connection)
2521
2656
  }
2522
2657
 
2658
+ self.applyVideoStabilizationMode()
2659
+
2523
2660
  // Start recording video
2524
2661
  fileVideoOutput.startRecording(to: fileUrl, recordingDelegate: self)
2525
2662
 
@@ -2824,6 +2961,7 @@ enum CameraControllerError: Swift.Error {
2824
2961
  case invalidOperation
2825
2962
  case noCamerasAvailable
2826
2963
  case cannotFindDocumentsDirectory
2964
+ case recordingInProgress
2827
2965
  case fileVideoOutputNotFound
2828
2966
  case unknown
2829
2967
  case invalidZoomLevel(min: CGFloat, max: CGFloat, requested: CGFloat)
@@ -2851,6 +2989,8 @@ extension CameraControllerError: LocalizedError {
2851
2989
  return NSLocalizedString("Unknown", comment: "Unknown")
2852
2990
  case .cannotFindDocumentsDirectory:
2853
2991
  return NSLocalizedString("Cannot find documents directory", comment: "This should never happen")
2992
+ case .recordingInProgress:
2993
+ return NSLocalizedString("Cannot change video stabilization while recording is in progress.", comment: "Recording in progress")
2854
2994
  case .fileVideoOutputNotFound:
2855
2995
  return NSLocalizedString("Video recording is not available. Make sure the camera is properly initialized.", comment: "Video recording not available")
2856
2996
  case .invalidZoomLevel(let min, let max, let requested):
@@ -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.5.1"
38
+ private let pluginVersion: String = "8.6.0"
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),
@@ -1643,12 +1647,45 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
1643
1647
  call.resolve(["codecs": self.cameraController.getSupportedVideoCodecs()])
1644
1648
  }
1645
1649
 
1650
+ @objc func isVideoStabilizationSupported(_ call: CAPPluginCall) {
1651
+ call.resolve(["supported": self.cameraController.isVideoStabilizationSupported()])
1652
+ }
1653
+
1654
+ @objc func getSupportedVideoStabilizationModes(_ call: CAPPluginCall) {
1655
+ call.resolve(["modes": self.cameraController.getSupportedVideoStabilizationModes()])
1656
+ }
1657
+
1658
+ @objc func getVideoStabilizationMode(_ call: CAPPluginCall) {
1659
+ call.resolve(["mode": self.cameraController.getVideoStabilizationMode()])
1660
+ }
1661
+
1662
+ @objc func setVideoStabilizationMode(_ call: CAPPluginCall) {
1663
+ guard let mode = call.getString("mode") else {
1664
+ call.reject("mode is required")
1665
+ return
1666
+ }
1667
+ do {
1668
+ try self.cameraController.setVideoStabilizationMode(mode)
1669
+ call.resolve()
1670
+ } catch {
1671
+ call.reject("Failed to set video stabilization mode: \(error.localizedDescription)")
1672
+ }
1673
+ }
1674
+
1646
1675
  @objc func startRecordVideo(_ call: CAPPluginCall) {
1647
1676
  let maxDuration = call.getFloat("maxDuration")
1648
1677
  let maxFileSize = call.getInt("maxFileSize")
1649
1678
  if let videoCodec = call.getString("videoCodec") {
1650
1679
  try? self.cameraController.setVideoCodec(videoCodec)
1651
1680
  }
1681
+ if let videoStabilizationMode = call.getString("videoStabilizationMode") {
1682
+ do {
1683
+ try self.cameraController.setVideoStabilizationMode(videoStabilizationMode)
1684
+ } catch {
1685
+ call.reject("Failed to set video stabilization mode: \(error.localizedDescription)")
1686
+ return
1687
+ }
1688
+ }
1652
1689
  self.cameraController.recordingFinishedCallback = { [weak self] fileURL, reason in
1653
1690
  DispatchQueue.main.async {
1654
1691
  self?.notifyListeners("recordingFinished", data: ["videoFilePath": fileURL.absoluteString, "reason": reason])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "8.5.1",
3
+ "version": "8.6.0",
4
4
  "description": "Camera preview",
5
5
  "license": "MPL-2.0",
6
6
  "repository": {