capacitor-camera-view 2.2.0-rc.1 → 2.2.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.
- package/README.md +17 -202
- package/android/build.gradle +0 -1
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +4 -287
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +8 -112
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +1 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +1 -21
- package/dist/docs.json +21 -201
- package/dist/esm/definitions.d.ts +23 -85
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +4 -20
- package/dist/esm/web.js +16 -151
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +16 -151
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +16 -151
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CameraViewPlugin/CameraError.swift +0 -28
- package/ios/Sources/CameraViewPlugin/CameraEvents.swift +27 -3
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +8 -8
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +34 -3
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +14 -13
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +150 -159
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +15 -114
- package/ios/Sources/CameraViewPlugin/TempFileManager.swift +34 -68
- package/package.json +12 -12
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/VideoRecordingQuality.kt +0 -10
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +0 -302
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
package com.michaelwolz.capacitorcameraview
|
|
2
2
|
|
|
3
|
-
import android.Manifest
|
|
4
3
|
import android.content.Context
|
|
5
4
|
import android.content.Context.CAMERA_SERVICE
|
|
6
|
-
import android.content.pm.PackageManager
|
|
7
5
|
import android.graphics.Bitmap
|
|
8
6
|
import android.hardware.camera2.CameraCharacteristics
|
|
9
7
|
import android.hardware.camera2.CameraManager
|
|
@@ -25,16 +23,8 @@ import androidx.camera.core.TorchState
|
|
|
25
23
|
import androidx.camera.core.resolutionselector.AspectRatioStrategy
|
|
26
24
|
import androidx.camera.core.resolutionselector.ResolutionSelector
|
|
27
25
|
import androidx.camera.mlkit.vision.MlKitAnalyzer
|
|
28
|
-
import androidx.camera.video.FallbackStrategy
|
|
29
|
-
import androidx.camera.video.FileOutputOptions
|
|
30
|
-
import androidx.camera.video.Quality
|
|
31
|
-
import androidx.camera.video.QualitySelector
|
|
32
|
-
import androidx.camera.video.Recording
|
|
33
|
-
import androidx.camera.video.VideoRecordEvent
|
|
34
|
-
import androidx.camera.view.CameraController
|
|
35
26
|
import androidx.camera.view.LifecycleCameraController
|
|
36
27
|
import androidx.camera.view.PreviewView
|
|
37
|
-
import androidx.camera.view.video.AudioConfig
|
|
38
28
|
import androidx.core.content.ContextCompat
|
|
39
29
|
import androidx.lifecycle.LifecycleOwner
|
|
40
30
|
import com.getcapacitor.FileUtils
|
|
@@ -48,9 +38,7 @@ import com.michaelwolz.capacitorcameraview.model.BarcodeDetectionResult
|
|
|
48
38
|
import com.michaelwolz.capacitorcameraview.model.CameraDevice
|
|
49
39
|
import com.michaelwolz.capacitorcameraview.model.CameraResult
|
|
50
40
|
import com.michaelwolz.capacitorcameraview.model.CameraSessionConfiguration
|
|
51
|
-
import com.michaelwolz.capacitorcameraview.model.VideoRecordingQuality
|
|
52
41
|
import com.michaelwolz.capacitorcameraview.model.ZoomFactors
|
|
53
|
-
import kotlinx.coroutines.CancellableContinuation
|
|
54
42
|
import kotlinx.coroutines.CoroutineScope
|
|
55
43
|
import kotlinx.coroutines.Dispatchers
|
|
56
44
|
import kotlinx.coroutines.SupervisorJob
|
|
@@ -67,7 +55,6 @@ import java.io.File
|
|
|
67
55
|
import java.io.FileOutputStream
|
|
68
56
|
import java.util.concurrent.ExecutorService
|
|
69
57
|
import java.util.concurrent.Executors
|
|
70
|
-
import java.util.concurrent.atomic.AtomicBoolean
|
|
71
58
|
import java.util.concurrent.atomic.AtomicLong
|
|
72
59
|
import java.util.concurrent.atomic.AtomicReference
|
|
73
60
|
import kotlin.coroutines.resume
|
|
@@ -85,9 +72,7 @@ class CameraView(plugin: Plugin) {
|
|
|
85
72
|
// Camera components (using atomic reference for thread safety)
|
|
86
73
|
private var cameraController: LifecycleCameraController?
|
|
87
74
|
get() = cameraControllerRef.get()
|
|
88
|
-
set(value) {
|
|
89
|
-
cameraControllerRef.set(value)
|
|
90
|
-
}
|
|
75
|
+
set(value) { cameraControllerRef.set(value) }
|
|
91
76
|
|
|
92
77
|
private val cameraExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() }
|
|
93
78
|
private var previewView: PreviewView? = null
|
|
@@ -96,18 +81,6 @@ class CameraView(plugin: Plugin) {
|
|
|
96
81
|
private var currentCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
|
97
82
|
private var currentFlashMode: Int = ImageCapture.FLASH_MODE_OFF
|
|
98
83
|
|
|
99
|
-
// Active video recording
|
|
100
|
-
private var activeRecording: Recording? = null
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Holds the pending stop-recording continuation result handler.
|
|
104
|
-
* Needed because CameraX delivers the final recording outcome asynchronously via Finalize.
|
|
105
|
-
*/
|
|
106
|
-
private var pendingStopCallback: ((CameraResult<JSObject>) -> Unit)? = null
|
|
107
|
-
|
|
108
|
-
// Track the output file for the current recording
|
|
109
|
-
private var currentRecordingFile: File? = null
|
|
110
|
-
|
|
111
84
|
// Plugin context
|
|
112
85
|
private var lifecycleOwner: LifecycleOwner? = null
|
|
113
86
|
private var pluginDelegate: Plugin = plugin
|
|
@@ -157,15 +130,6 @@ class CameraView(plugin: Plugin) {
|
|
|
157
130
|
/** Stop the camera session and release resources. */
|
|
158
131
|
suspend fun stopSessionAsync(): CameraResult<Unit> = withContext(Dispatchers.Main) {
|
|
159
132
|
try {
|
|
160
|
-
// Stop any active recording before unbinding
|
|
161
|
-
activeRecording?.stop()
|
|
162
|
-
activeRecording = null
|
|
163
|
-
pendingStopCallback?.invoke(
|
|
164
|
-
CameraResult.Error(Exception("Recording was interrupted because the camera session stopped"))
|
|
165
|
-
)
|
|
166
|
-
pendingStopCallback = null
|
|
167
|
-
currentRecordingFile = null
|
|
168
|
-
|
|
169
133
|
cameraController?.unbind()
|
|
170
134
|
|
|
171
135
|
previewView?.let { view ->
|
|
@@ -278,15 +242,11 @@ class CameraView(plugin: Plugin) {
|
|
|
278
242
|
"Image captured successfully in ${System.currentTimeMillis() - startTime}ms"
|
|
279
243
|
)
|
|
280
244
|
try {
|
|
281
|
-
val base64String =
|
|
282
|
-
imageProxyToBase64(image, quality, imageRotationDegrees)
|
|
245
|
+
val base64String = imageProxyToBase64(image, quality, imageRotationDegrees)
|
|
283
246
|
val result = JSObject().apply {
|
|
284
247
|
put("photo", base64String)
|
|
285
248
|
}
|
|
286
|
-
Log.d(
|
|
287
|
-
TAG,
|
|
288
|
-
"Image processed to Base64 in ${System.currentTimeMillis() - startTime}ms"
|
|
289
|
-
)
|
|
249
|
+
Log.d(TAG, "Image processed to Base64 in ${System.currentTimeMillis() - startTime}ms")
|
|
290
250
|
continuation.resume(CameraResult.Success(result))
|
|
291
251
|
} catch (e: Exception) {
|
|
292
252
|
Log.e(TAG, "Error processing captured image", e)
|
|
@@ -386,244 +346,6 @@ class CameraView(plugin: Plugin) {
|
|
|
386
346
|
}
|
|
387
347
|
}
|
|
388
348
|
|
|
389
|
-
/**
|
|
390
|
-
* Starts video recording to a temporary file.
|
|
391
|
-
*/
|
|
392
|
-
suspend fun startRecordingAsync(
|
|
393
|
-
enableAudio: Boolean,
|
|
394
|
-
videoQuality: VideoRecordingQuality,
|
|
395
|
-
): CameraResult<Unit> = suspendCancellableCoroutine { continuation ->
|
|
396
|
-
mainHandler.post {
|
|
397
|
-
startRecordingOnMainThread(enableAudio, videoQuality, continuation)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
private fun startRecordingOnMainThread(
|
|
402
|
-
enableAudio: Boolean,
|
|
403
|
-
videoQuality: VideoRecordingQuality,
|
|
404
|
-
continuation: CancellableContinuation<CameraResult<Unit>>
|
|
405
|
-
) {
|
|
406
|
-
val controller = validateRecordingPreconditions(continuation) ?: return
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
controller.videoCaptureQualitySelector = videoQuality.toQualitySelector()
|
|
410
|
-
|
|
411
|
-
// Enable VIDEO_CAPTURE use case alongside IMAGE_CAPTURE
|
|
412
|
-
controller.setEnabledUseCases(
|
|
413
|
-
CameraController.IMAGE_CAPTURE or CameraController.VIDEO_CAPTURE
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
val outputOptions = createRecordingOutputOptions()
|
|
417
|
-
val audioConfig = resolveAudioConfig(enableAudio, continuation) ?: return
|
|
418
|
-
|
|
419
|
-
startCameraRecording(controller, outputOptions, audioConfig, continuation)
|
|
420
|
-
} catch (e: SecurityException) {
|
|
421
|
-
Log.e(TAG, "Security exception when starting recording. Missing permission?", e)
|
|
422
|
-
// Restore normal use cases on permission error
|
|
423
|
-
cameraController?.setEnabledUseCases(CameraController.IMAGE_CAPTURE)
|
|
424
|
-
continuation.resume(CameraResult.Error(e))
|
|
425
|
-
} catch (e: Exception) {
|
|
426
|
-
Log.e(TAG, "Error starting recording", e)
|
|
427
|
-
// Restore normal use cases on error
|
|
428
|
-
cameraController?.setEnabledUseCases(CameraController.IMAGE_CAPTURE)
|
|
429
|
-
continuation.resume(CameraResult.Error(e))
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
private fun validateRecordingPreconditions(
|
|
434
|
-
continuation: CancellableContinuation<CameraResult<Unit>>
|
|
435
|
-
): LifecycleCameraController? {
|
|
436
|
-
val controller = cameraController
|
|
437
|
-
if (controller == null) {
|
|
438
|
-
continuation.resume(CameraResult.Error(CameraError.CameraNotInitialized()))
|
|
439
|
-
return null
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (activeRecording != null) {
|
|
443
|
-
continuation.resume(CameraResult.Error(Exception("Recording is already in progress")))
|
|
444
|
-
return null
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return controller
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
private fun createRecordingOutputOptions(): FileOutputOptions {
|
|
451
|
-
val tempFile = File.createTempFile(
|
|
452
|
-
"camera_recording_",
|
|
453
|
-
".mp4",
|
|
454
|
-
context.cacheDir
|
|
455
|
-
)
|
|
456
|
-
currentRecordingFile = tempFile
|
|
457
|
-
return FileOutputOptions.Builder(tempFile).build()
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
private fun resolveAudioConfig(
|
|
461
|
-
enableAudio: Boolean,
|
|
462
|
-
continuation: CancellableContinuation<CameraResult<Unit>>
|
|
463
|
-
): AudioConfig? {
|
|
464
|
-
if (!enableAudio) {
|
|
465
|
-
return AudioConfig.AUDIO_DISABLED
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (hasMicrophonePermission()) {
|
|
469
|
-
return try {
|
|
470
|
-
AudioConfig.create(true)
|
|
471
|
-
} catch (e: SecurityException) {
|
|
472
|
-
continuation.resume(CameraResult.Error(e))
|
|
473
|
-
null
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
continuation.resume(
|
|
478
|
-
CameraResult.Error(
|
|
479
|
-
SecurityException("Microphone permission is required for audio recording")
|
|
480
|
-
)
|
|
481
|
-
)
|
|
482
|
-
return null
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
private fun hasMicrophonePermission(): Boolean {
|
|
486
|
-
return ContextCompat.checkSelfPermission(
|
|
487
|
-
context,
|
|
488
|
-
Manifest.permission.RECORD_AUDIO
|
|
489
|
-
) == PackageManager.PERMISSION_GRANTED
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
private fun startCameraRecording(
|
|
493
|
-
controller: LifecycleCameraController,
|
|
494
|
-
outputOptions: FileOutputOptions,
|
|
495
|
-
audioConfig: AudioConfig,
|
|
496
|
-
continuation: CancellableContinuation<CameraResult<Unit>>
|
|
497
|
-
) {
|
|
498
|
-
val startResumed = AtomicBoolean(false)
|
|
499
|
-
activeRecording = controller.startRecording(
|
|
500
|
-
outputOptions,
|
|
501
|
-
audioConfig,
|
|
502
|
-
cameraExecutor
|
|
503
|
-
) { event ->
|
|
504
|
-
when (event) {
|
|
505
|
-
is VideoRecordEvent.Start -> handleRecordingStartEvent(startResumed, continuation)
|
|
506
|
-
is VideoRecordEvent.Finalize -> {
|
|
507
|
-
handleRecordingFinalizeEvent(event, startResumed, continuation)
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
else -> Unit
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
private fun handleRecordingStartEvent(
|
|
516
|
-
startResumed: AtomicBoolean,
|
|
517
|
-
continuation: CancellableContinuation<CameraResult<Unit>>
|
|
518
|
-
) {
|
|
519
|
-
Log.d(TAG, "Video recording started")
|
|
520
|
-
if (continuation.isActive && startResumed.compareAndSet(false, true)) {
|
|
521
|
-
continuation.resume(CameraResult.Success(Unit))
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
private fun handleRecordingFinalizeEvent(
|
|
526
|
-
event: VideoRecordEvent.Finalize,
|
|
527
|
-
startResumed: AtomicBoolean,
|
|
528
|
-
continuation: CancellableContinuation<CameraResult<Unit>>
|
|
529
|
-
) {
|
|
530
|
-
// If recording finalized before Start was emitted, resume the
|
|
531
|
-
// startRecording continuation with an error
|
|
532
|
-
if (continuation.isActive && startResumed.compareAndSet(false, true)) {
|
|
533
|
-
continuation.resume(
|
|
534
|
-
CameraResult.Error(
|
|
535
|
-
Exception("Recording failed to start: error code ${event.error}")
|
|
536
|
-
)
|
|
537
|
-
)
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
finalizeRecordingAndNotifyStopCallback(event)
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
private fun finalizeRecordingAndNotifyStopCallback(event: VideoRecordEvent.Finalize) {
|
|
544
|
-
mainHandler.post {
|
|
545
|
-
// CameraX requires use case changes on the main thread.
|
|
546
|
-
cameraController?.setEnabledUseCases(CameraController.IMAGE_CAPTURE)
|
|
547
|
-
|
|
548
|
-
val callback = pendingStopCallback
|
|
549
|
-
pendingStopCallback = null
|
|
550
|
-
// Always clean up recording state
|
|
551
|
-
activeRecording = null
|
|
552
|
-
|
|
553
|
-
if (event.hasError()) {
|
|
554
|
-
Log.e(TAG, "Recording error: ${event.error}")
|
|
555
|
-
currentRecordingFile = null
|
|
556
|
-
callback?.invoke(CameraResult.Error(Exception("Recording failed with error code: ${event.error}")))
|
|
557
|
-
return@post
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
val file = currentRecordingFile
|
|
561
|
-
currentRecordingFile = null
|
|
562
|
-
if (file == null) {
|
|
563
|
-
callback?.invoke(CameraResult.Error(Exception("Recording file not found")))
|
|
564
|
-
return@post
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
val capacitorFilePath = FileUtils.getPortablePath(
|
|
568
|
-
context,
|
|
569
|
-
pluginDelegate.bridge.localUrl,
|
|
570
|
-
Uri.fromFile(file)
|
|
571
|
-
)
|
|
572
|
-
val result = JSObject().apply {
|
|
573
|
-
put("webPath", capacitorFilePath)
|
|
574
|
-
}
|
|
575
|
-
callback?.invoke(CameraResult.Success(result))
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
private fun VideoRecordingQuality.toQualitySelector(): QualitySelector {
|
|
580
|
-
return when (this) {
|
|
581
|
-
VideoRecordingQuality.LOWEST -> QualitySelector.from(Quality.LOWEST)
|
|
582
|
-
VideoRecordingQuality.SD -> QualitySelector.from(
|
|
583
|
-
Quality.SD,
|
|
584
|
-
FallbackStrategy.lowerQualityOrHigherThan(Quality.SD)
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
VideoRecordingQuality.HD -> QualitySelector.from(
|
|
588
|
-
Quality.HD,
|
|
589
|
-
FallbackStrategy.lowerQualityOrHigherThan(Quality.HD)
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
VideoRecordingQuality.FHD -> QualitySelector.from(
|
|
593
|
-
Quality.FHD,
|
|
594
|
-
FallbackStrategy.lowerQualityOrHigherThan(Quality.FHD)
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
VideoRecordingQuality.UHD -> QualitySelector.from(
|
|
598
|
-
Quality.UHD,
|
|
599
|
-
FallbackStrategy.lowerQualityOrHigherThan(Quality.UHD)
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
VideoRecordingQuality.HIGHEST -> QualitySelector.from(Quality.HIGHEST)
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* Stops the current video recording and returns the file path.
|
|
608
|
-
*/
|
|
609
|
-
suspend fun stopRecordingAsync(): CameraResult<JSObject> =
|
|
610
|
-
suspendCancellableCoroutine { continuation ->
|
|
611
|
-
mainHandler.post {
|
|
612
|
-
val recording = activeRecording
|
|
613
|
-
if (recording == null) {
|
|
614
|
-
continuation.resume(CameraResult.Error(Exception("No recording is in progress")))
|
|
615
|
-
return@post
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
pendingStopCallback = { result ->
|
|
619
|
-
continuation.resume(result)
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
activeRecording = null
|
|
623
|
-
recording.stop()
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
349
|
/** Flip between front and back cameras */
|
|
628
350
|
fun flipCamera(callback: (Exception?) -> Unit) {
|
|
629
351
|
currentCameraSelector = when (currentCameraSelector) {
|
|
@@ -811,12 +533,6 @@ class CameraView(plugin: Plugin) {
|
|
|
811
533
|
|
|
812
534
|
mainHandler.post {
|
|
813
535
|
try {
|
|
814
|
-
// Stop any active recording before cleanup
|
|
815
|
-
activeRecording?.stop()
|
|
816
|
-
activeRecording = null
|
|
817
|
-
pendingStopCallback = null
|
|
818
|
-
currentRecordingFile = null
|
|
819
|
-
|
|
820
536
|
// Stop camera session
|
|
821
537
|
cameraController?.unbind()
|
|
822
538
|
cameraController = null
|
|
@@ -1007,6 +723,7 @@ class CameraView(plugin: Plugin) {
|
|
|
1007
723
|
val barcodeResult =
|
|
1008
724
|
BarcodeDetectionResult(
|
|
1009
725
|
value = barcode.rawValue ?: "",
|
|
726
|
+
rawBytes = barcode.rawBytes ?: ByteArray(0),
|
|
1010
727
|
displayValue = barcode.displayValue ?: "",
|
|
1011
728
|
type = getBarcodeFormatString(barcode.format),
|
|
1012
729
|
boundingRect = webBoundingRect
|
|
@@ -12,7 +12,6 @@ import com.getcapacitor.annotation.CapacitorPlugin
|
|
|
12
12
|
import com.getcapacitor.annotation.Permission
|
|
13
13
|
import com.getcapacitor.annotation.PermissionCallback
|
|
14
14
|
import com.michaelwolz.capacitorcameraview.model.BarcodeDetectionResult
|
|
15
|
-
import com.michaelwolz.capacitorcameraview.model.VideoRecordingQuality
|
|
16
15
|
import kotlinx.coroutines.CoroutineScope
|
|
17
16
|
import kotlinx.coroutines.Dispatchers
|
|
18
17
|
import kotlinx.coroutines.Job
|
|
@@ -22,10 +21,7 @@ import kotlinx.coroutines.launch
|
|
|
22
21
|
|
|
23
22
|
@CapacitorPlugin(
|
|
24
23
|
name = "CameraView",
|
|
25
|
-
permissions = [
|
|
26
|
-
Permission(strings = [Manifest.permission.CAMERA], alias = "camera"),
|
|
27
|
-
Permission(strings = [Manifest.permission.RECORD_AUDIO], alias = "microphone")
|
|
28
|
-
]
|
|
24
|
+
permissions = [Permission(strings = [Manifest.permission.CAMERA], alias = "camera")]
|
|
29
25
|
)
|
|
30
26
|
class CameraViewPlugin : Plugin() {
|
|
31
27
|
// Coroutine scope for async operations
|
|
@@ -148,113 +144,7 @@ class CameraViewPlugin : Plugin() {
|
|
|
148
144
|
},
|
|
149
145
|
onError = { error ->
|
|
150
146
|
call.reject("Failed to capture frame: ${error.message}", error)
|
|
151
|
-
Log.d(
|
|
152
|
-
TAG,
|
|
153
|
-
"captureSample failed after ${System.currentTimeMillis() - timeStart}ms"
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
@PluginMethod
|
|
161
|
-
override fun requestPermissions(call: PluginCall) {
|
|
162
|
-
val permissionsList = call.getArray("permissions")
|
|
163
|
-
?.toList<String>()
|
|
164
|
-
?: listOf("camera")
|
|
165
|
-
|
|
166
|
-
// Determine which aliases still need to be requested
|
|
167
|
-
val aliasesToRequest = permissionsList.filter { alias ->
|
|
168
|
-
getPermissionState(alias) != PermissionState.GRANTED
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (aliasesToRequest.isEmpty()) {
|
|
172
|
-
checkPermissions(call)
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Store which permissions to request so callback can continue the chain
|
|
177
|
-
call.data.put("_pendingAliases", com.getcapacitor.JSArray(aliasesToRequest))
|
|
178
|
-
requestPermissionForAlias(aliasesToRequest.first(), call, "requestedPermsCallback")
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
@PermissionCallback
|
|
182
|
-
private fun requestedPermsCallback(call: PluginCall) {
|
|
183
|
-
val pendingAliases = call.getArray("_pendingAliases")?.toList<String>() ?: emptyList()
|
|
184
|
-
|
|
185
|
-
// Find remaining aliases that still need requesting
|
|
186
|
-
val remaining = pendingAliases.drop(1).filter { alias ->
|
|
187
|
-
getPermissionState(alias) != PermissionState.GRANTED
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (remaining.isNotEmpty()) {
|
|
191
|
-
call.data.put("_pendingAliases", com.getcapacitor.JSArray(remaining))
|
|
192
|
-
requestPermissionForAlias(remaining.first(), call, "requestedPermsCallback")
|
|
193
|
-
} else {
|
|
194
|
-
checkPermissions(call)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
@PluginMethod
|
|
199
|
-
fun startRecording(call: PluginCall) {
|
|
200
|
-
val enableAudio = call.getBoolean("enableAudio") ?: false
|
|
201
|
-
val videoQuality =
|
|
202
|
-
parseVideoRecordingQuality(call.getString("videoQuality"))
|
|
203
|
-
?: run {
|
|
204
|
-
call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest")
|
|
205
|
-
return
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (enableAudio && getPermissionState("microphone") != PermissionState.GRANTED) {
|
|
209
|
-
requestPermissionForAlias("microphone", call, "microphonePermsCallback")
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
doStartRecording(call, enableAudio, videoQuality)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
@PermissionCallback
|
|
217
|
-
private fun microphonePermsCallback(call: PluginCall) {
|
|
218
|
-
if (getPermissionState("microphone") == PermissionState.GRANTED) {
|
|
219
|
-
val enableAudio = call.getBoolean("enableAudio") ?: false
|
|
220
|
-
val videoQuality =
|
|
221
|
-
parseVideoRecordingQuality(call.getString("videoQuality"))
|
|
222
|
-
?: run {
|
|
223
|
-
call.reject("Invalid videoQuality. Use one of: lowest, sd, hd, fhd, uhd, highest")
|
|
224
|
-
return
|
|
225
|
-
}
|
|
226
|
-
doStartRecording(call, enableAudio, videoQuality)
|
|
227
|
-
} else {
|
|
228
|
-
call.reject("Microphone permission is required for audio recording")
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Helper method to start recording after ensuring permissions are granted.
|
|
235
|
-
*/
|
|
236
|
-
private fun doStartRecording(
|
|
237
|
-
call: PluginCall,
|
|
238
|
-
enableAudio: Boolean,
|
|
239
|
-
videoQuality: VideoRecordingQuality
|
|
240
|
-
) {
|
|
241
|
-
pluginScope.launch {
|
|
242
|
-
implementation.startRecordingAsync(enableAudio, videoQuality).fold(
|
|
243
|
-
onSuccess = { call.resolve() },
|
|
244
|
-
onError = { error ->
|
|
245
|
-
call.reject("Failed to start recording: ${error.message}", error)
|
|
246
|
-
}
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@PluginMethod
|
|
252
|
-
fun stopRecording(call: PluginCall) {
|
|
253
|
-
pluginScope.launch {
|
|
254
|
-
implementation.stopRecordingAsync().fold(
|
|
255
|
-
onSuccess = { result -> call.resolve(result) },
|
|
256
|
-
onError = { error ->
|
|
257
|
-
call.reject("Failed to stop recording: ${error.message}", error)
|
|
147
|
+
Log.d(TAG, "captureSample failed after ${System.currentTimeMillis() - timeStart}ms")
|
|
258
148
|
}
|
|
259
149
|
)
|
|
260
150
|
}
|
|
@@ -399,8 +289,14 @@ class CameraViewPlugin : Plugin() {
|
|
|
399
289
|
* Called by the CameraView when a barcode is detected.
|
|
400
290
|
*/
|
|
401
291
|
fun notifyBarcodeDetected(result: BarcodeDetectionResult) {
|
|
292
|
+
val rawBytesArray = JSArray().apply {
|
|
293
|
+
result.rawBytes.forEach { put(it.toInt() and 0xFF) }
|
|
294
|
+
}
|
|
295
|
+
|
|
402
296
|
val jsObject = JSObject().apply {
|
|
403
297
|
put("value", result.value)
|
|
298
|
+
put("displayValue", result.displayValue)
|
|
299
|
+
put("rawBytes", rawBytesArray)
|
|
404
300
|
put("type", result.type)
|
|
405
301
|
put("boundingRect", JSObject().apply {
|
|
406
302
|
put("x", result.boundingRect.x)
|
|
@@ -13,7 +13,6 @@ import androidx.camera.view.PreviewView
|
|
|
13
13
|
import com.getcapacitor.PluginCall
|
|
14
14
|
import com.google.mlkit.vision.barcode.common.Barcode
|
|
15
15
|
import com.michaelwolz.capacitorcameraview.model.CameraSessionConfiguration
|
|
16
|
-
import com.michaelwolz.capacitorcameraview.model.VideoRecordingQuality
|
|
17
16
|
import com.michaelwolz.capacitorcameraview.model.WebBoundingRect
|
|
18
17
|
import java.io.ByteArrayOutputStream
|
|
19
18
|
|
|
@@ -172,7 +171,7 @@ fun sessionConfigFromPluginCall(call: PluginCall): CameraSessionConfiguration {
|
|
|
172
171
|
jsonArray.optString(i)?.let { stringTypes.add(it) }
|
|
173
172
|
}
|
|
174
173
|
val converted = convertToNativeBarcodeFormats(stringTypes)
|
|
175
|
-
converted.
|
|
174
|
+
if (converted.isNotEmpty()) converted else null
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
return CameraSessionConfiguration(
|
|
@@ -251,23 +250,4 @@ fun imageProxyToBase64(image: ImageProxy, quality: Int, rotationDegrees: Int): S
|
|
|
251
250
|
// Ensure bitmap is always recycled
|
|
252
251
|
bitmap.recycle()
|
|
253
252
|
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Parses a string representation of video recording quality into a [VideoRecordingQuality] enum.
|
|
258
|
-
*/
|
|
259
|
-
fun parseVideoRecordingQuality(rawValue: String?): VideoRecordingQuality? {
|
|
260
|
-
if (rawValue == null) {
|
|
261
|
-
return VideoRecordingQuality.HIGHEST
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return when (rawValue) {
|
|
265
|
-
"lowest" -> VideoRecordingQuality.LOWEST
|
|
266
|
-
"sd" -> VideoRecordingQuality.SD
|
|
267
|
-
"hd" -> VideoRecordingQuality.HD
|
|
268
|
-
"fhd" -> VideoRecordingQuality.FHD
|
|
269
|
-
"uhd" -> VideoRecordingQuality.UHD
|
|
270
|
-
"highest" -> VideoRecordingQuality.HIGHEST
|
|
271
|
-
else -> null
|
|
272
|
-
}
|
|
273
253
|
}
|