@capgo/camera-preview 7.4.0-beta.19 → 7.4.0-beta.20

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.
@@ -36,6 +36,10 @@ class CameraController: NSObject {
36
36
  var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
37
37
 
38
38
  var sampleBufferCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
39
+
40
+ // Add callback for detecting when first frame is ready
41
+ var firstFrameReadyCallback: (() -> Void)?
42
+ var hasReceivedFirstFrame = false
39
43
 
40
44
  var audioDevice: AVCaptureDevice?
41
45
  var audioInput: AVCaptureDeviceInput?
@@ -266,13 +270,24 @@ extension CameraController {
266
270
 
267
271
  print("[CameraPreview] Fast prepare - using pre-initialized session")
268
272
 
269
- // Pre-create outputs asynchronously to avoid blocking camera opening
270
- outputPreparationQueue.async { [weak self] in
271
- self?.prepareOutputs()
272
- }
273
+ // Ensure outputs are prepared synchronously before starting session
274
+ self.prepareOutputs()
275
+ self.waitForOutputsToBeReady()
273
276
 
274
- // // Configure device inputs for the requested camera
277
+ // Configure device inputs for the requested camera
275
278
  try self.configureDeviceInputs(cameraPosition: cameraPosition, deviceId: deviceId, disableAudio: disableAudio)
279
+
280
+ // Add data output early to detect first frame
281
+ captureSession.beginConfiguration()
282
+ if let dataOutput = self.dataOutput, captureSession.canAddOutput(dataOutput) {
283
+ captureSession.addOutput(dataOutput)
284
+ // Set delegate to detect first frame
285
+ dataOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
286
+ }
287
+ captureSession.commitConfiguration()
288
+
289
+ // Reset first frame detection
290
+ self.hasReceivedFirstFrame = false
276
291
 
277
292
  // Start the session on background thread (AVCaptureSession.startRunning() is thread-safe)
278
293
  captureSession.startRunning()
@@ -293,13 +308,10 @@ extension CameraController {
293
308
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
294
309
  guard let self = self else { return }
295
310
 
296
- // Wait for outputs to be prepared before proceeding
297
- self.waitForOutputsToBeReady()
298
-
299
- // Add outputs to session and apply user settings
311
+ // Add remaining outputs to session and apply user settings
300
312
  do {
301
- try self.addOutputsToSession(cameraMode: cameraMode, aspectRatio: aspectRatio)
302
- print("[CameraPreview] Outputs successfully added to session")
313
+ try self.addRemainingOutputsToSession(cameraMode: cameraMode, aspectRatio: aspectRatio)
314
+ print("[CameraPreview] Remaining outputs successfully added to session")
303
315
  } catch {
304
316
  print("[CameraPreview] Error adding outputs to session: \(error)")
305
317
  }
@@ -411,7 +423,7 @@ extension CameraController {
411
423
  }
412
424
  }
413
425
 
414
- private func addOutputsToSession(cameraMode: Bool, aspectRatio: String?) throws {
426
+ private func addRemainingOutputsToSession(cameraMode: Bool, aspectRatio: String?) throws {
415
427
  guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
416
428
 
417
429
  // Begin configuration to batch all changes
@@ -452,6 +464,50 @@ extension CameraController {
452
464
  if cameraMode, let videoOutput = self.fileVideoOutput, captureSession.canAddOutput(videoOutput) {
453
465
  captureSession.addOutput(videoOutput)
454
466
  }
467
+ // Data output was already added in prepare() to detect first frame
468
+ }
469
+
470
+ private func addOutputsToSession(cameraMode: Bool, aspectRatio: String?) throws {
471
+ guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
472
+
473
+ // Begin configuration to batch all changes
474
+ captureSession.beginConfiguration()
475
+ defer { captureSession.commitConfiguration() }
476
+
477
+ // Update session preset based on aspect ratio if needed
478
+ var targetPreset: AVCaptureSession.Preset = .high // Default to high quality
479
+
480
+ if let aspectRatio = aspectRatio {
481
+ switch aspectRatio {
482
+ case "16:9":
483
+ targetPreset = captureSession.canSetSessionPreset(.hd1920x1080) ? .hd1920x1080 : .high
484
+ case "4:3":
485
+ targetPreset = captureSession.canSetSessionPreset(.photo) ? .photo : .high
486
+ default:
487
+ targetPreset = .high
488
+ }
489
+ }
490
+
491
+ // Always try to set the best preset available
492
+ if captureSession.canSetSessionPreset(targetPreset) {
493
+ captureSession.sessionPreset = targetPreset
494
+ print("[CameraPreview] Updated preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
495
+ } else if captureSession.canSetSessionPreset(.high) {
496
+ // Fallback to high if target preset not available
497
+ captureSession.sessionPreset = .high
498
+ print("[CameraPreview] Fallback to high preset")
499
+ }
500
+
501
+ // Add photo output (already created in prepareOutputs)
502
+ if let photoOutput = self.photoOutput, captureSession.canAddOutput(photoOutput) {
503
+ photoOutput.isHighResolutionCaptureEnabled = true
504
+ captureSession.addOutput(photoOutput)
505
+ }
506
+
507
+ // Add video output only if camera mode is enabled
508
+ if cameraMode, let videoOutput = self.fileVideoOutput, captureSession.canAddOutput(videoOutput) {
509
+ captureSession.addOutput(videoOutput)
510
+ }
455
511
 
456
512
  // Add data output
457
513
  if let dataOutput = self.dataOutput, captureSession.canAddOutput(dataOutput) {
@@ -1357,6 +1413,10 @@ extension CameraController {
1357
1413
 
1358
1414
  // Reset output preparation status
1359
1415
  self.outputsPrepared = false
1416
+
1417
+ // Reset first frame detection
1418
+ self.hasReceivedFirstFrame = false
1419
+ self.firstFrameReadyCallback = nil
1360
1420
  }
1361
1421
 
1362
1422
  func captureVideo() throws {
@@ -1571,6 +1631,17 @@ extension CameraController: AVCapturePhotoCaptureDelegate {
1571
1631
 
1572
1632
  extension CameraController: AVCaptureVideoDataOutputSampleBufferDelegate {
1573
1633
  func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
1634
+ // Check if we're waiting for the first frame
1635
+ if !hasReceivedFirstFrame, let firstFrameCallback = firstFrameReadyCallback {
1636
+ hasReceivedFirstFrame = true
1637
+ firstFrameCallback()
1638
+ firstFrameReadyCallback = nil
1639
+ // If no capture is in progress, we can return early
1640
+ if sampleBufferCaptureCompletionBlock == nil {
1641
+ return
1642
+ }
1643
+ }
1644
+
1574
1645
  guard let completion = sampleBufferCaptureCompletionBlock else { return }
1575
1646
 
1576
1647
  guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
@@ -502,12 +502,29 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelega
502
502
  self.isInitializing = false
503
503
  self.isInitialized = true
504
504
 
505
- var returnedObject = JSObject()
506
- returnedObject["width"] = self.previewView.frame.width as any JSValue
507
- returnedObject["height"] = self.previewView.frame.height as any JSValue
508
- returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
509
- returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
510
- call.resolve(returnedObject)
505
+ // Set up callback to wait for first frame before resolving
506
+ self.cameraController.firstFrameReadyCallback = { [weak self] in
507
+ guard let self = self else { return }
508
+
509
+ DispatchQueue.main.async {
510
+ var returnedObject = JSObject()
511
+ returnedObject["width"] = self.previewView.frame.width as any JSValue
512
+ returnedObject["height"] = self.previewView.frame.height as any JSValue
513
+ returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
514
+ returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
515
+ call.resolve(returnedObject)
516
+ }
517
+ }
518
+
519
+ // If already received first frame (unlikely but possible), resolve immediately
520
+ if self.cameraController.hasReceivedFirstFrame {
521
+ var returnedObject = JSObject()
522
+ returnedObject["width"] = self.previewView.frame.width as any JSValue
523
+ returnedObject["height"] = self.previewView.frame.height as any JSValue
524
+ returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
525
+ returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
526
+ call.resolve(returnedObject)
527
+ }
511
528
  }
512
529
 
513
530
  @objc func flip(_ call: CAPPluginCall) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.4.0-beta.19",
3
+ "version": "7.4.0-beta.20",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {