capacitor-camera-view 2.3.1 → 3.0.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.
Files changed (44) hide show
  1. package/CapacitorCameraView.podspec +1 -1
  2. package/Package.swift +1 -1
  3. package/README.md +341 -48
  4. package/android/build.gradle +0 -1
  5. package/android/src/main/AndroidManifest.xml +0 -1
  6. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraError.kt +42 -0
  7. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +946 -205
  8. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +87 -43
  9. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +28 -2
  10. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraDevice.kt +6 -1
  11. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +15 -1
  12. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/TorchModeState.kt +15 -0
  13. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/WebBoundingRect.kt +1 -1
  14. package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +73 -19
  15. package/dist/docs.json +487 -30
  16. package/dist/esm/definitions.d.ts +494 -27
  17. package/dist/esm/definitions.js.map +1 -1
  18. package/dist/esm/utils.d.ts +59 -15
  19. package/dist/esm/utils.js +79 -38
  20. package/dist/esm/utils.js.map +1 -1
  21. package/dist/esm/web.d.ts +191 -13
  22. package/dist/esm/web.js +626 -142
  23. package/dist/esm/web.js.map +1 -1
  24. package/dist/plugin.cjs.js +713 -180
  25. package/dist/plugin.cjs.js.map +1 -1
  26. package/dist/plugin.js +713 -180
  27. package/dist/plugin.js.map +1 -1
  28. package/ios/Sources/CameraViewPlugin/CameraError.swift +147 -2
  29. package/ios/Sources/CameraViewPlugin/CameraEvents.swift +41 -19
  30. package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +29 -1
  31. package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +78 -23
  32. package/ios/Sources/CameraViewPlugin/CameraViewManager+DeferredStart.swift +37 -0
  33. package/ios/Sources/CameraViewPlugin/CameraViewManager+Focus.swift +131 -0
  34. package/ios/Sources/CameraViewPlugin/CameraViewManager+Lifecycle.swift +156 -0
  35. package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +73 -41
  36. package/ios/Sources/CameraViewPlugin/CameraViewManager+ResolutionSelection.swift +57 -0
  37. package/ios/Sources/CameraViewPlugin/CameraViewManager+Rotation.swift +195 -0
  38. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +24 -12
  39. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +22 -73
  40. package/ios/Sources/CameraViewPlugin/CameraViewManager+Zoom.swift +113 -0
  41. package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +450 -403
  42. package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +111 -69
  43. package/ios/Sources/CameraViewPlugin/Utils.swift +61 -7
  44. package/package.json +25 -9
@@ -2,12 +2,21 @@ import type { PermissionState, PluginListenerHandle } from '@capacitor/core';
2
2
  /**
3
3
  * Main plugin interface for Capacitor Camera View functionality.
4
4
  *
5
+ * When a method below rejects, the resulting error carries a `code` property
6
+ * set to one of the stable `CameraErrorCode` strings so consumers can
7
+ * `switch` on `error.code` instead of matching on the human-readable message.
8
+ * See the `CameraErrorCode` type for the full vocabulary, including the one
9
+ * divergence on web for methods with no web implementation.
10
+ *
5
11
  * @since 1.0.0
6
12
  */
7
13
  export interface CameraViewPlugin {
8
14
  /**
9
15
  * Start the camera view with optional configuration.
10
16
  *
17
+ * Rejects if a camera session is already running. Call `stop()` first if you need to
18
+ * start a new session with different options.
19
+ *
11
20
  * @param options - Configuration options for the camera session
12
21
  * @returns A promise that resolves when the camera has started
13
22
  *
@@ -38,7 +47,9 @@ export interface CameraViewPlugin {
38
47
  *
39
48
  * @since 1.0.0
40
49
  */
41
- capture<T extends CaptureOptions>(options: T): Promise<CaptureResponse<T>>;
50
+ capture<T extends CaptureOptions = CaptureOptions & {
51
+ saveToFile?: undefined;
52
+ }>(options?: T): Promise<CaptureResponse<T>>;
42
53
  /**
43
54
  * Captures a frame from the current camera preview without using the full camera capture pipeline.
44
55
  *
@@ -55,7 +66,9 @@ export interface CameraViewPlugin {
55
66
  *
56
67
  * @since 1.0.0
57
68
  */
58
- captureSample<T extends CaptureOptions>(options: T): Promise<CaptureResponse<T>>;
69
+ captureSample<T extends CaptureOptions = CaptureOptions & {
70
+ saveToFile?: undefined;
71
+ }>(options?: T): Promise<CaptureResponse<T>>;
59
72
  /**
60
73
  * Start recording video from the current camera.
61
74
  * Camera must be running. Throws if already recording.
@@ -78,6 +91,11 @@ export interface CameraViewPlugin {
78
91
  /**
79
92
  * Switch between front and back camera.
80
93
  *
94
+ * Rejects with `RECORDING_ALREADY_IN_PROGRESS` while a video recording is active, on
95
+ * iOS, Android, and web alike: swapping the camera input/device mid-recording would
96
+ * either drop the audio track or interrupt the recording outright, so the recording is
97
+ * left intact and must be stopped before flipping.
98
+ *
81
99
  * @returns A promise that resolves when the camera has been flipped
82
100
  *
83
101
  * @since 1.0.0
@@ -94,6 +112,14 @@ export interface CameraViewPlugin {
94
112
  /**
95
113
  * Get current zoom level information and available range.
96
114
  *
115
+ * On iOS, when the camera is a virtual device (e.g. the triple camera enabled via
116
+ * `useTripleCameraIfAvailable`), the returned values are in the device's raw zoom domain and are
117
+ * not UI multipliers like "0.5x"/"1x"/"2x". A `min` of `1.0` corresponds to the widest constituent
118
+ * lens (the ultra-wide "0.5x" lens), so a session started at the default zoom reports a `current`
119
+ * of the wide-lens switch-over factor (typically `2.0`) rather than `1.0`. Treat these numbers as
120
+ * device-relative and derive the usable range from `min`/`max` instead of assuming `1.0` is the
121
+ * default.
122
+ *
97
123
  * @remarks
98
124
  * Make sure the camera is properly initialized before calling this method. Otherwise, this might
99
125
  * lead to returning default values on android.
@@ -106,6 +132,10 @@ export interface CameraViewPlugin {
106
132
  /**
107
133
  * Set the camera zoom level.
108
134
  *
135
+ * On iOS virtual devices (e.g. the triple camera) `level` is a raw device zoom factor, not a UI
136
+ * multiplier. Derive valid values from the `min`/`max` returned by `getZoom()` rather than assuming
137
+ * `1.0` maps to the wide "1x" lens.
138
+ *
109
139
  * @param options - Zoom configuration options
110
140
  * @param options.level - The zoom level to set
111
141
  * @param options.ramp - Whether to animate the zoom level change, defaults to false (iOS only)
@@ -121,6 +151,44 @@ export interface CameraViewPlugin {
121
151
  level: number;
122
152
  ramp?: boolean;
123
153
  }): Promise<void>;
154
+ /**
155
+ * Focus and meter the camera at a specific point (tap-to-focus).
156
+ *
157
+ * Because the WebView sits above the native camera preview and consumes every
158
+ * touch, the native preview can never receive tap gestures itself. Instead,
159
+ * the app catches the tap in the DOM and forwards its coordinates here; since
160
+ * the native preview is always rendered fullscreen behind the WebView, the
161
+ * mapping to the sensor is deterministic.
162
+ *
163
+ * The camera runs a one-shot focus/exposure at the given point and then
164
+ * automatically restores continuous auto-focus/auto-exposure (immediately on a
165
+ * subsequent tap, or after a short timeout), so focus is never left
166
+ * permanently locked.
167
+ *
168
+ * @param options - The point to focus on
169
+ * @param options.x - The horizontal coordinate in CSS/viewport pixels, measured
170
+ * from the left edge of the viewport. This is the same coordinate space the
171
+ * plugin emits for barcode `boundingRect`, just in the opposite direction.
172
+ * @param options.y - The vertical coordinate in CSS/viewport pixels, measured
173
+ * from the top edge of the viewport.
174
+ * @returns A promise that resolves when the focus/metering point has been applied
175
+ *
176
+ * @remarks
177
+ * On fixed-focus cameras (e.g. some front cameras) the plugin degrades to
178
+ * exposure-only metering where possible. If neither focus nor exposure metering
179
+ * at a point is supported, the promise rejects with the `FOCUS_NOT_SUPPORTED`
180
+ * error code. Rejects with `SESSION_NOT_RUNNING` when the camera is not running.
181
+ *
182
+ * Not supported on web: this method rejects with an `unimplemented` error there,
183
+ * because the `pointsOfInterest` media-track constraint has effectively no
184
+ * browser support.
185
+ *
186
+ * @since 3.0.0
187
+ */
188
+ setFocusPoint(options: {
189
+ x: number;
190
+ y: number;
191
+ }): Promise<void>;
124
192
  /**
125
193
  * Get current flash mode setting.
126
194
  *
@@ -182,11 +250,13 @@ export interface CameraViewPlugin {
182
250
  * functionality. This method will throw an exception if torch is not supported.
183
251
  *
184
252
  * The torch provides continuous illumination, unlike flash which only activates during photo capture.
185
- * On iOS, you can control the torch intensity level. On Android, the torch is either on or off.
253
+ * You can control the torch intensity level on both iOS and, on API 33+ devices with
254
+ * multi-level torch hardware, Android. On older Android versions or single-level torch
255
+ * hardware, `level` is best-effort and ignored - the torch is simply switched on or off.
186
256
  *
187
257
  * @param options - Torch configuration options
188
258
  * @param options.enabled - Whether to enable or disable the torch
189
- * @param options.level - The torch intensity level (0.0 to 1.0, iOS only). Defaults to 1.0 when enabled
259
+ * @param options.level - The torch intensity level (0.0 to 1.0). Defaults to 1.0 when enabled
190
260
  * @returns A promise that resolves when the torch mode has been set
191
261
  *
192
262
  * @since 1.2.0
@@ -221,6 +291,15 @@ export interface CameraViewPlugin {
221
291
  * Listen for barcode detection events.
222
292
  * This event is emitted when a barcode is detected in the camera preview.
223
293
  *
294
+ * @remarks
295
+ * Events are rate-controlled to avoid flooding the bridge. Repeated detections
296
+ * of the same barcode (identical `value` and `type`) are suppressed while the
297
+ * code stays in view: after an initial event, the same code re-emits at most
298
+ * once per ~500 ms suppression window. Pointing the camera at a different
299
+ * barcode (a different `value` or `type`) emits immediately rather than waiting
300
+ * for the window to elapse. This behavior is consistent across iOS, Android,
301
+ * and web.
302
+ *
224
303
  * @param eventName - The name of the event to listen for ('barcodeDetected')
225
304
  * @param listenerFunc - The callback function to execute when a barcode is detected
226
305
  * @returns A promise that resolves with an event subscription
@@ -228,15 +307,72 @@ export interface CameraViewPlugin {
228
307
  * @since 1.0.0
229
308
  */
230
309
  addListener(eventName: 'barcodeDetected', listenerFunc: (data: BarcodeDetectionData) => void): Promise<PluginListenerHandle>;
310
+ /**
311
+ * Listen for camera interruption events.
312
+ *
313
+ * Emitted when the capture session is interrupted by the system, for example
314
+ * an incoming phone call, another app claiming the camera or microphone,
315
+ * losing the camera in iPad Split View, or system pressure. The preview
316
+ * typically freezes for the duration of the interruption.
317
+ *
318
+ * @remarks
319
+ * Currently emitted on iOS only. Android and web will follow.
320
+ *
321
+ * @param eventName - The name of the event to listen for ('cameraInterrupted')
322
+ * @param listenerFunc - The callback function to execute when the camera is interrupted
323
+ * @returns A promise that resolves with an event subscription
324
+ *
325
+ * @since 3.0.0
326
+ */
327
+ addListener(eventName: 'cameraInterrupted', listenerFunc: (data: CameraInterruptedData) => void): Promise<PluginListenerHandle>;
328
+ /**
329
+ * Listen for camera resume events.
330
+ *
331
+ * Emitted when a previous interruption ends and the capture session resumes,
332
+ * for example after an incoming phone call finishes. Pair this with
333
+ * `cameraInterrupted` to update your UI when the preview recovers.
334
+ *
335
+ * @remarks
336
+ * Currently emitted on iOS only. Android and web will follow.
337
+ *
338
+ * @param eventName - The name of the event to listen for ('cameraResumed')
339
+ * @param listenerFunc - The callback function to execute when the camera resumes
340
+ * @returns A promise that resolves with an event subscription
341
+ *
342
+ * @since 3.0.0
343
+ */
344
+ addListener(eventName: 'cameraResumed', listenerFunc: () => void): Promise<PluginListenerHandle>;
345
+ /**
346
+ * Listen for camera runtime error events.
347
+ *
348
+ * Emitted when the capture session hits a runtime error. When the underlying
349
+ * media services are reset, the plugin restarts the session automatically, so
350
+ * this event is primarily informational for logging and diagnostics.
351
+ *
352
+ * @remarks
353
+ * Currently emitted on iOS only. Android and web will follow.
354
+ *
355
+ * @param eventName - The name of the event to listen for ('cameraRuntimeError')
356
+ * @param listenerFunc - The callback function to execute when a runtime error occurs
357
+ * @returns A promise that resolves with an event subscription
358
+ *
359
+ * @since 3.0.0
360
+ */
361
+ addListener(eventName: 'cameraRuntimeError', listenerFunc: (data: CameraRuntimeErrorData) => void): Promise<PluginListenerHandle>;
231
362
  /**
232
363
  * Remove all listeners for this plugin.
233
364
  *
234
- * @param eventName - Optional event name to remove listeners for
365
+ * @remarks
366
+ * This removes *every* listener registered on this plugin instance, regardless of event
367
+ * name, on iOS, Android, and web. There is no way to remove listeners for a single event
368
+ * name only; if you need that, keep track of the `PluginListenerHandle` returned by
369
+ * `addListener()` and call `remove()` on it instead.
370
+ *
235
371
  * @returns A promise that resolves when the listeners are removed
236
372
  *
237
373
  * @since 1.0.0
238
374
  */
239
- removeAllListeners(eventName?: string): Promise<void>;
375
+ removeAllListeners(): Promise<void>;
240
376
  }
241
377
  /**
242
378
  * Position options for the camera.
@@ -265,6 +401,28 @@ export type FlashMode = 'off' | 'on' | 'auto';
265
401
  * @since 2.3.0
266
402
  */
267
403
  export type VideoRecordingQuality = 'lowest' | 'sd' | 'hd' | 'fhd' | 'uhd' | 'highest';
404
+ /**
405
+ * Sensor aspect ratio for a camera session, applied consistently to both the
406
+ * live preview stream and photo capture.
407
+ * - '4:3': The native photo aspect ratio of most mobile camera sensors
408
+ * - '16:9': The typical video aspect ratio
409
+ *
410
+ * @since 3.0.0
411
+ */
412
+ export type CameraAspectRatio = '4:3' | '16:9';
413
+ /**
414
+ * How the camera preview is scaled to fill its container when the sensor
415
+ * aspect ratio differs from the container's aspect ratio.
416
+ * - 'cover': The preview fills the whole container, center-cropping the frame
417
+ * so no empty bars are shown. Parts of the frame outside the container are
418
+ * hidden from the preview (long-standing default behavior).
419
+ * - 'fit': The whole sensor frame is scaled to fit inside the container
420
+ * (letterboxed), so the user sees the entire frame they are about to
421
+ * capture. Empty bars appear on the short axis.
422
+ *
423
+ * @since 3.0.0
424
+ */
425
+ export type PreviewScaleMode = 'cover' | 'fit';
268
426
  /**
269
427
  * Represents a physical camera device on the device.
270
428
  *
@@ -321,6 +479,8 @@ export type BarcodeType =
321
479
  | 'code39Mod43'
322
480
  /** Code 93 barcode */
323
481
  | 'code93'
482
+ /** Codabar barcode. Not detectable on iOS below 15.4; the deployment target is 16, so it is available on all supported iOS versions. */
483
+ | 'codabar'
324
484
  /** EAN-8 barcode */
325
485
  | 'ean8'
326
486
  /** EAN-13 barcode */
@@ -335,6 +495,16 @@ export type BarcodeType =
335
495
  | 'aztec'
336
496
  /** Data Matrix code */
337
497
  | 'dataMatrix'
498
+ /**
499
+ * UPC-A barcode.
500
+ *
501
+ * Detected as a distinct `upcA` type on Android (ML Kit) and web
502
+ * (BarcodeDetector). iOS cannot: AVFoundation has no UPC-A metadata type and
503
+ * reports UPC-A codes as `ean13` (a UPC-A value is an EAN-13 with a leading
504
+ * `0`), so requesting `upcA` on iOS has no effect and such codes arrive as
505
+ * `ean13`.
506
+ */
507
+ | 'upcA'
338
508
  /** UPC-E barcode */
339
509
  | 'upce';
340
510
  /**
@@ -369,7 +539,12 @@ export interface CameraSessionConfiguration {
369
539
  */
370
540
  deviceId?: string;
371
541
  /**
372
- * Whether to use the triple camera if available (iPhone Pro models only)
542
+ * Whether to use the triple camera if available (iPhone Pro models only).
543
+ *
544
+ * The session starts directly on the virtual device, which lets iOS switch
545
+ * between the ultra-wide, wide and telephoto lenses automatically. Takes
546
+ * precedence over `preferredCameraDeviceTypes` for the rear camera.
547
+ *
373
548
  * @default false
374
549
  */
375
550
  useTripleCameraIfAvailable?: boolean;
@@ -386,10 +561,115 @@ export interface CameraSessionConfiguration {
386
561
  */
387
562
  preferredCameraDeviceTypes?: CameraDeviceType[];
388
563
  /**
389
- * The initial zoom factor to use
564
+ * The sensor aspect ratio to use for the camera session, applied to both
565
+ * the live preview stream and photo capture so the captured image matches
566
+ * the framing the user sees.
567
+ *
568
+ * **Preview-vs-capture framing contract** (identical on iOS, Android and
569
+ * web when this option is set):
570
+ *
571
+ * - By default (`previewScaleMode: 'cover'`) the preview fills its container
572
+ * (the fullscreen view behind the WebView on iOS/Android, the container
573
+ * element on web) using cover semantics: when the chosen sensor ratio
574
+ * differs from the container ratio, the preview is center-cropped to fill
575
+ * it — never letterboxed. `capture()` returns the full sensor-ratio image
576
+ * (matching this option), NOT the on-screen crop. Parts of the image that
577
+ * were cropped out of the preview by cover-scaling are therefore included
578
+ * in the capture.
579
+ * - With `previewScaleMode: 'fit'` the whole sensor frame is letterboxed to
580
+ * fit inside the container, so the preview shows exactly the full captured
581
+ * frame: `capture()` returns what the preview shows, with nothing cropped
582
+ * out of view. See {@link previewScaleMode} for the letterbox-background
583
+ * note.
584
+ *
585
+ * When this option is omitted, each platform keeps its long-standing
586
+ * default behavior: iOS uses the sensor's native photo format (4:3),
587
+ * Android prefers 16:9 with an automatic fallback, and web requests a
588
+ * 16:9 stream and returns the visible (cover-cropped) preview region from
589
+ * `capture()` instead of the full frame. Captured output stays JPEG on all
590
+ * platforms either way.
591
+ *
592
+ * @default undefined - platform default (see above)
593
+ * @since 3.0.0
594
+ */
595
+ aspectRatio?: CameraAspectRatio;
596
+ /**
597
+ * Optional capture-resolution hint: an upper bound, in pixels, for the
598
+ * longer edge of captured photos. The platform picks the largest supported
599
+ * capture resolution whose longer edge does not exceed this value (falling
600
+ * back to the closest supported resolution when none fits) while keeping
601
+ * the configured `aspectRatio`.
602
+ *
603
+ * This is a best-effort hint: the exact output dimensions depend on the
604
+ * resolutions the sensor/browser actually supports.
605
+ *
606
+ * - iOS: constrains `AVCapturePhotoOutput.maxPhotoDimensions`. Only affects
607
+ * `capture()`; `captureSample()` keeps sampling the preview stream.
608
+ * - Android: bounds the CameraX ImageCapture resolution. Only affects
609
+ * `capture()`.
610
+ * - Web: used as the ideal `getUserMedia` width constraint, so it affects
611
+ * the stream (preview and capture alike).
612
+ *
613
+ * @example 1280 // capture photos at most 1280px wide (longer edge)
614
+ * @default undefined - platform default resolution
615
+ * @since 3.0.0
616
+ */
617
+ captureMaxDimension?: number;
618
+ /**
619
+ * How the live preview is scaled into its container when the sensor aspect
620
+ * ratio differs from the container's aspect ratio.
621
+ *
622
+ * - `'cover'` (default): the preview fills the container, center-cropping the
623
+ * frame. This is the long-standing behavior and is unchanged when the
624
+ * option is omitted.
625
+ * - `'fit'`: the whole sensor frame is scaled to fit inside the container
626
+ * (letterboxed), so the user sees the entire frame they are about to
627
+ * capture. In `fit` mode the preview shows exactly the full captured frame,
628
+ * and `capture()` returns what the preview shows.
629
+ *
630
+ * Applied consistently on iOS (`AVCaptureVideoPreviewLayer.videoGravity`),
631
+ * Android (`PreviewView.ScaleType`) and web (`object-fit`). Barcode
632
+ * `boundingRect` and `setFocusPoint` coordinates stay correct in both modes.
633
+ *
634
+ * **Letterbox background**: the empty bars shown in `fit` mode are not painted
635
+ * by the plugin — they show whatever is visually behind/around the preview.
636
+ * On iOS/Android that is the app's own background showing through the
637
+ * transparent WebView; on web it is the container element's background. Style
638
+ * that background (e.g. a black or themed color) to control how the
639
+ * letterbox bars look.
640
+ *
641
+ * @default 'cover'
642
+ * @since 3.0.0
643
+ */
644
+ previewScaleMode?: PreviewScaleMode;
645
+ /**
646
+ * The initial zoom factor to use.
647
+ *
648
+ * Expressed relative to the wide-angle lens, so `1.0` is the familiar "1x" field of view on every
649
+ * camera. On iOS virtual devices whose raw `1.0` is the ultra-wide lens (e.g. the triple camera
650
+ * enabled via `useTripleCameraIfAvailable`) the factor is scaled into the device's own zoom domain,
651
+ * which is why `getZoom()` reports a larger `current` than the value passed here.
652
+ *
390
653
  * @default 1.0
391
654
  */
392
655
  zoomFactor?: number;
656
+ /**
657
+ * Prioritize photo quality over capture responsiveness (iOS 17+ only).
658
+ *
659
+ * By default the plugin opts into the iOS 17+ responsive-capture pipeline
660
+ * (zero-shutter-lag, responsive capture and fast capture prioritization) so
661
+ * consecutive `capture()` calls have a lower shot-to-shot latency. Rapid
662
+ * consecutive captures may then be delivered at a slightly reduced quality
663
+ * instead of queueing.
664
+ *
665
+ * Set this to `true` to opt out of that behavior and always prioritize photo
666
+ * quality. Has no effect on iOS versions or hardware without support for the
667
+ * responsive-capture APIs, and no effect on Android or Web.
668
+ *
669
+ * @default false
670
+ * @since 3.0.0
671
+ */
672
+ prioritizeQuality?: boolean;
393
673
  /**
394
674
  * Optional HTML ID of the container element where the camera view should be rendered.
395
675
  * If not provided, the camera view will be appended to the document body. Web only.
@@ -404,10 +684,15 @@ export interface CameraSessionConfiguration {
404
684
  */
405
685
  export interface CaptureOptions {
406
686
  /**
407
- * The JPEG quality of the captured photo/sample on a scale of 0-100
687
+ * The JPEG quality of the captured photo/sample on a scale of 0-100. Cross-platform note:
688
+ * for `quality >= 90`, iOS returns the original, unmodified JPEG produced by the camera
689
+ * hardware instead of re-encoding it, to avoid unnecessary quality loss and CPU overhead.
690
+ * Android and Web always encode at the exact requested quality. As a result, the same
691
+ * `quality` value (90-100) can produce different file sizes on iOS versus Android/Web.
692
+ * @default 90
408
693
  * @since 1.1.0
409
694
  */
410
- quality: number;
695
+ quality?: number;
411
696
  /**
412
697
  * If true, saves to a temporary file and returns the web path instead of base64.
413
698
  * The web path can be used to set the src attribute of an image for efficient loading and rendering.
@@ -444,12 +729,23 @@ export interface VideoRecordingOptions {
444
729
  */
445
730
  export interface VideoRecordingResponse {
446
731
  /**
447
- * Web-accessible path to the recorded video file.
448
- * On web, this is a blob URL.
449
- * On iOS/Android, this is a path accessible via Capacitor's filesystem.
732
+ * Web-accessible path to the recorded video file that can be used to set the
733
+ * `src` attribute of a video element for efficient loading and rendering.
734
+ * On web, this is a blob URL created with `URL.createObjectURL()`; the plugin does not
735
+ * revoke it automatically, so call `URL.revokeObjectURL(webPath)` once you are done with
736
+ * it (e.g. after playback or upload) to release the underlying memory.
737
+ * On iOS/Android, this is a Capacitor bridge path served by the local web server and does
738
+ * not need to be revoked.
450
739
  * @since 2.3.0
451
740
  */
452
741
  webPath: string;
742
+ /**
743
+ * The full, platform-specific file URL (`file://...`) to the recorded video,
744
+ * usable with the Filesystem API or `Capacitor.convertFileSrc()`.
745
+ * Native only (iOS/Android); `undefined` on web.
746
+ * @since 2.4.0
747
+ */
748
+ path?: string;
453
749
  }
454
750
  /**
455
751
  * Response for checking if the camera view is running.
@@ -461,18 +757,59 @@ export interface IsRunningResponse {
461
757
  isRunning: boolean;
462
758
  }
463
759
  /**
464
- * Response for capturing a photo
465
- * This will contain either a base64 encoded string or a web path to the captured photo,
466
- * depending on the `saveToFile` option in the CaptureOptions.
760
+ * The file-path shaped result returned when `saveToFile` is `true`.
467
761
  * @since 1.0.0
468
762
  */
469
- export type CaptureResponse<T extends CaptureOptions = CaptureOptions> = T['saveToFile'] extends true ? {
470
- /** The web path to the captured photo that can be used to set the src attribute of an image for efficient loading and rendering (when saveToFile is true) */
763
+ export interface CaptureFileResult {
764
+ /**
765
+ * The web path to the captured photo that can be used to set the src attribute of an image
766
+ * for efficient loading and rendering (when saveToFile is true).
767
+ *
768
+ * On web, this is a blob URL created with `URL.createObjectURL()`. The plugin does not revoke
769
+ * it automatically; once you are done with it (e.g. after the image has been displayed or
770
+ * uploaded), call `URL.revokeObjectURL(webPath)` to release the underlying memory. On
771
+ * iOS/Android this is a Capacitor bridge path served by the local web server and does not
772
+ * need to be revoked.
773
+ */
471
774
  webPath: string;
472
- } : {
775
+ /**
776
+ * The full, platform-specific file URL (`file://...`) to the captured photo,
777
+ * usable with the Filesystem API or `Capacitor.convertFileSrc()`.
778
+ * Native only (iOS/Android); `undefined` on web.
779
+ * @since 2.4.0
780
+ */
781
+ path?: string;
782
+ }
783
+ /**
784
+ * The base64 shaped result returned when `saveToFile` is `false` or `undefined`.
785
+ * @since 1.0.0
786
+ */
787
+ export interface CaptureBase64Result {
473
788
  /** The base64 encoded string of the captured photo (when saveToFile is false or undefined) */
474
789
  photo: string;
475
- };
790
+ }
791
+ /**
792
+ * Response for capturing a photo
793
+ * This will contain either a base64 encoded string or a web path to the captured photo,
794
+ * depending on the `saveToFile` option in the CaptureOptions.
795
+ *
796
+ * @remarks
797
+ * The narrowing is three-way on `saveToFile`:
798
+ * - a literal `true` narrows to {@link CaptureFileResult}
799
+ * - a literal `false`, an explicit `undefined`, or an options type that omits the key
800
+ * entirely narrows to {@link CaptureBase64Result}
801
+ * - a non-literal `boolean` (or a `CaptureOptions` with `saveToFile` left generic) resolves
802
+ * to the union of both, since the runtime result can't be known at the type level
803
+ *
804
+ * @since 1.0.0
805
+ */
806
+ export type CaptureResponse<T extends CaptureOptions = CaptureOptions> = SaveToFileOf<T> extends true ? CaptureFileResult : SaveToFileOf<T> extends false | undefined ? CaptureBase64Result : CaptureFileResult | CaptureBase64Result;
807
+ /**
808
+ * `T['saveToFile']` resolves through the `CaptureOptions` constraint to `boolean | undefined`
809
+ * when `T` omits the key, which would widen the result to the union. Treat an absent key as
810
+ * `undefined` instead.
811
+ */
812
+ type SaveToFileOf<T extends CaptureOptions> = 'saveToFile' extends keyof T ? T['saveToFile'] : undefined;
476
813
  /**
477
814
  * Response for getting available camera devices.
478
815
  *
@@ -485,14 +822,19 @@ export interface GetAvailableDevicesResponse {
485
822
  /**
486
823
  * Response for getting zoom level information.
487
824
  *
825
+ * @remarks
826
+ * On iOS virtual devices (e.g. the triple camera) these values are in the device's raw zoom domain,
827
+ * not UI multipliers: `min` of `1.0` is the ultra-wide ("0.5x") lens and `current` reports the
828
+ * wide-lens switch-over factor rather than `1.0` at the default zoom. See {@link CameraViewPlugin.getZoom}.
829
+ *
488
830
  * @since 1.0.0
489
831
  */
490
832
  export interface GetZoomResponse {
491
- /** The minimum zoom level supported */
833
+ /** The minimum zoom level supported. On iOS virtual devices `1.0` maps to the ultra-wide lens. */
492
834
  min: number;
493
- /** The maximum zoom level supported */
835
+ /** The maximum zoom level supported. On iOS virtual devices this is a raw device factor (capped at a 10x wide-equivalent zoom). */
494
836
  max: number;
495
- /** The current zoom level */
837
+ /** The current zoom level. On iOS virtual devices this is a raw device factor, not a UI multiplier. */
496
838
  current: number;
497
839
  }
498
840
  /**
@@ -530,7 +872,13 @@ export interface IsTorchAvailableResponse {
530
872
  export interface GetTorchModeResponse {
531
873
  /** Indicates if the torch is currently enabled */
532
874
  enabled: boolean;
533
- /** The current torch intensity level (0.0 to 1.0, iOS only). Always 1.0 on Android when enabled */
875
+ /**
876
+ * The current torch intensity level (0.0 to 1.0).
877
+ *
878
+ * On Android this reflects the real hardware strength only on API 33+ devices with
879
+ * multi-level torch hardware; below API 33, or on single-level hardware, the torch is
880
+ * binary, so this is always 1.0 when enabled and 0.0 when off.
881
+ */
534
882
  level: number;
535
883
  }
536
884
  /**
@@ -559,14 +907,40 @@ export interface BarcodeDetectionData {
559
907
  * expose a separate display value, so this property is only emitted on Android.
560
908
  */
561
909
  displayValue?: string;
562
- /** The type/format of the barcode (e.g., 'qr', 'code128', etc.) */
563
- type: string;
910
+ /**
911
+ * The type/format of the detected barcode.
912
+ *
913
+ * For formats that are part of the `BarcodeType` union, all platforms emit
914
+ * identical values, so scanning the same barcode yields the same `type` on
915
+ * web, iOS, and Android (e.g. `'qr'`, `'code128'`, `'dataMatrix'`).
916
+ *
917
+ * The type is `BarcodeType | string` rather than `BarcodeType` because a
918
+ * platform detector can occasionally report a format that has no
919
+ * cross-platform union member; in that case the raw platform string is
920
+ * forwarded unchanged instead of being dropped. In practice this is Android's
921
+ * `'unknown'` (ML Kit's `FORMAT_UNKNOWN`) and the equivalent `'unknown'` from
922
+ * the web BarcodeDetector. Narrow against the `BarcodeType` members you care
923
+ * about and treat anything else as an opaque string.
924
+ *
925
+ * Platform-specific notes:
926
+ * - iOS distinguishes `interleaved2of5` from `itf14`, whereas Android and web
927
+ * cannot tell them apart and always report `itf14` for their shared
928
+ * interleaved-2-of-5/ITF detector format.
929
+ * - UPC-A is reported as `'upcA'` on Android and web, but as `'ean13'` on iOS:
930
+ * AVFoundation has no UPC-A metadata type and surfaces UPC-A codes as EAN-13
931
+ * (a UPC-A value is an EAN-13 with a leading `0`). This is a hardware/OS
932
+ * limitation, not a normalization choice.
933
+ * - Codabar is reported as `'codabar'` on all three platforms.
934
+ */
935
+ type: BarcodeType | string;
564
936
  /** The bounding rectangle of the barcode in the camera frame. */
565
937
  boundingRect: BoundingRect;
566
938
  }
567
939
  /**
568
940
  * Rectangle defining the boundary of the barcode in the camera frame.
569
- * Coordinates are normalized between 0 and 1 relative to the camera frame.
941
+ * Coordinates are given in display/CSS pixels within the webview (display)
942
+ * coordinate space, not normalized values. This lets you position an overlay
943
+ * directly on top of the detected barcode without further scaling.
570
944
  *
571
945
  * @since 1.0.0
572
946
  */
@@ -580,6 +954,98 @@ export interface BoundingRect {
580
954
  /** Height of the bounding rectangle (should match the actual height of the barcode) */
581
955
  height: number;
582
956
  }
957
+ /**
958
+ * Reason why the camera session was interrupted.
959
+ *
960
+ * Mirrors `AVCaptureSession.InterruptionReason` on iOS. Unknown or future
961
+ * reasons fall back to `'unknown'`.
962
+ *
963
+ * @since 3.0.0
964
+ */
965
+ export type CameraInterruptionReason =
966
+ /** The video device is not available because the app is in the background */
967
+ 'videoDeviceNotAvailableInBackground'
968
+ /** The audio device is in use by another client (e.g. a phone call) */
969
+ | 'audioDeviceInUseByAnotherClient'
970
+ /** The video device is in use by another client */
971
+ | 'videoDeviceInUseByAnotherClient'
972
+ /** The video device is not available while multiple foreground apps share the screen (iPad Split View) */
973
+ | 'videoDeviceNotAvailableWithMultipleForegroundApps'
974
+ /** The video device is not available due to system pressure (e.g. thermal) */
975
+ | 'videoDeviceNotAvailableDueToSystemPressure'
976
+ /** The interruption reason could not be determined */
977
+ | 'unknown';
978
+ /**
979
+ * Data for a camera interruption event.
980
+ *
981
+ * @since 3.0.0
982
+ */
983
+ export interface CameraInterruptedData {
984
+ /** The reason the camera session was interrupted. */
985
+ reason: CameraInterruptionReason;
986
+ }
987
+ /**
988
+ * Data for a camera runtime error event.
989
+ *
990
+ * @since 3.0.0
991
+ */
992
+ export interface CameraRuntimeErrorData {
993
+ /** A human-readable description of the runtime error. */
994
+ message: string;
995
+ /**
996
+ * The underlying platform error code, when available.
997
+ * On iOS this is the `AVError` code.
998
+ */
999
+ code?: number;
1000
+ }
1001
+ /**
1002
+ * Stable error codes returned as the `code` property on the error of a
1003
+ * rejected plugin call.
1004
+ *
1005
+ * These codes are part of the plugin's public contract: they are safe to
1006
+ * `switch` on and will not change between releases the way human-readable
1007
+ * error messages might. Where the same failure class exists across
1008
+ * platforms, iOS, Android, and web emit the same code string.
1009
+ *
1010
+ * Emitted on iOS, Android, and web. One divergence: on web, methods that
1011
+ * have no web implementation (e.g. `setFocusPoint()`, `setTorchMode()`)
1012
+ * reject via Capacitor's own not-implemented convention instead of one of
1013
+ * the codes below - `error.code` is the string `'UNIMPLEMENTED'`, not a
1014
+ * `CameraErrorCode`.
1015
+ *
1016
+ * - 'CAMERA_UNAVAILABLE': No available camera for the requested position.
1017
+ * - 'CONFIGURATION_FAILED': Failed to configure the camera session.
1018
+ * - 'FRAME_CAPTURE_ERROR': Failed to capture a frame from the camera.
1019
+ * - 'INPUT_ADDITION_FAILED': Failed to add an input to the capture session.
1020
+ * - 'OUTPUT_ADDITION_FAILED': Failed to add an output to the capture session.
1021
+ * - 'PHOTO_OUTPUT_ERROR': An error occurred while capturing a photo.
1022
+ * - 'PHOTO_OUTPUT_NOT_CONFIGURED': The photo output has not been configured.
1023
+ * - 'SESSION_NOT_RUNNING': The capture session is not currently running. Call `start()` first.
1024
+ * - 'SESSION_ALREADY_RUNNING': A camera session is already running. Call `stop()` before starting a new one.
1025
+ * - 'UNSUPPORTED_FLASH_MODE': The requested flash mode is not supported by the current camera.
1026
+ * - 'TORCH_UNAVAILABLE': Torch is not available on this device or camera position.
1027
+ * - 'ZOOM_FACTOR_OUT_OF_RANGE': The requested zoom factor is out of the supported range.
1028
+ * - 'FOCUS_NOT_SUPPORTED': The current camera cannot focus or meter at a point (e.g. a fixed-focus camera).
1029
+ * - 'PERMISSION_DENIED': Camera or microphone access has been denied.
1030
+ * - 'DEVICE_LOCKED': The camera device is currently locked by another process.
1031
+ * - 'RECORDING_ALREADY_IN_PROGRESS': A video recording is already in progress.
1032
+ * - 'NO_RECORDING_IN_PROGRESS': `stopRecording()` was called but no recording is in progress.
1033
+ * - 'AUDIO_DEVICE_UNAVAILABLE': No microphone is available on this device.
1034
+ * - 'AUDIO_INPUT_ADDITION_FAILED': Failed to add the microphone input to the capture session.
1035
+ * - 'CAPTURE_IN_PROGRESS': A capture is already in progress.
1036
+ * - 'CAPTURE_TIMEOUT': Timed out waiting for a camera frame.
1037
+ * - 'WEBVIEW_UNAVAILABLE': Could not find the web view to render the camera preview into.
1038
+ * - 'INVALID_ARGUMENT': An argument passed to the method call was missing or invalid.
1039
+ * - 'IMAGE_COMPRESSION_FAILED': Failed to compress the captured image.
1040
+ * - 'PATH_CONVERSION_FAILED': Failed to create a web-accessible path for a captured file.
1041
+ * - 'FILE_WRITE_FAILED': Failed to write the captured file to disk.
1042
+ * - 'CAPTURE_OUTPUT_MISSING': The capture completed but produced no output data.
1043
+ * - 'LIFECYCLE_OWNER_MISSING': (Android only) The WebView's context is not a `LifecycleOwner`, so the camera session cannot be bound.
1044
+ * - 'UNKNOWN_ERROR': An unexpected error that does not map to a known camera error (e.g. an underlying OS error).
1045
+ *
1046
+ * @since 3.0.0
1047
+ */
1048
+ export type CameraErrorCode = 'CAMERA_UNAVAILABLE' | 'CONFIGURATION_FAILED' | 'FRAME_CAPTURE_ERROR' | 'INPUT_ADDITION_FAILED' | 'OUTPUT_ADDITION_FAILED' | 'PHOTO_OUTPUT_ERROR' | 'PHOTO_OUTPUT_NOT_CONFIGURED' | 'SESSION_NOT_RUNNING' | 'SESSION_ALREADY_RUNNING' | 'UNSUPPORTED_FLASH_MODE' | 'TORCH_UNAVAILABLE' | 'ZOOM_FACTOR_OUT_OF_RANGE' | 'FOCUS_NOT_SUPPORTED' | 'PERMISSION_DENIED' | 'DEVICE_LOCKED' | 'RECORDING_ALREADY_IN_PROGRESS' | 'NO_RECORDING_IN_PROGRESS' | 'AUDIO_DEVICE_UNAVAILABLE' | 'AUDIO_INPUT_ADDITION_FAILED' | 'CAPTURE_IN_PROGRESS' | 'CAPTURE_TIMEOUT' | 'WEBVIEW_UNAVAILABLE' | 'INVALID_ARGUMENT' | 'IMAGE_COMPRESSION_FAILED' | 'PATH_CONVERSION_FAILED' | 'FILE_WRITE_FAILED' | 'CAPTURE_OUTPUT_MISSING' | 'LIFECYCLE_OWNER_MISSING' | 'UNKNOWN_ERROR';
583
1049
  /**
584
1050
  * Permission types that can be requested.
585
1051
  * - 'camera': Camera access permission
@@ -599,3 +1065,4 @@ export interface PermissionStatus {
599
1065
  /** The state of the microphone permission */
600
1066
  microphone: PermissionState;
601
1067
  }
1068
+ export {};