@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
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
+
// Ensure outputs are prepared synchronously before starting session
|
|
274
|
+
self.prepareOutputs()
|
|
275
|
+
self.waitForOutputsToBeReady()
|
|
273
276
|
|
|
274
|
-
//
|
|
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
|
-
//
|
|
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.
|
|
302
|
-
print("[CameraPreview]
|
|
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
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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) {
|