capacitor-camera-view 2.4.0 → 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.
- package/CapacitorCameraView.podspec +1 -1
- package/Package.swift +1 -1
- package/README.md +341 -49
- package/android/build.gradle +0 -1
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraError.kt +42 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +945 -207
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +87 -43
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +28 -2
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraDevice.kt +6 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +15 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/TorchModeState.kt +15 -0
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/WebBoundingRect.kt +1 -1
- package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +73 -19
- package/dist/docs.json +475 -30
- package/dist/esm/definitions.d.ts +478 -26
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/utils.d.ts +59 -15
- package/dist/esm/utils.js +79 -38
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/web.d.ts +191 -13
- package/dist/esm/web.js +624 -141
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +711 -179
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +711 -179
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CameraViewPlugin/CameraError.swift +147 -2
- package/ios/Sources/CameraViewPlugin/CameraEvents.swift +41 -19
- package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +29 -1
- package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +78 -23
- package/ios/Sources/CameraViewPlugin/CameraViewManager+DeferredStart.swift +37 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Focus.swift +131 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Lifecycle.swift +156 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +73 -41
- package/ios/Sources/CameraViewPlugin/CameraViewManager+ResolutionSelection.swift +57 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Rotation.swift +195 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +24 -12
- package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoRecording.swift +22 -73
- package/ios/Sources/CameraViewPlugin/CameraViewManager+Zoom.swift +113 -0
- package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +450 -403
- package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +98 -65
- package/ios/Sources/CameraViewPlugin/Utils.swift +61 -7
- package/package.json +25 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Main plugin interface for Capacitor Camera View functionality.\n *\n * @since 1.0.0\n */\nexport interface CameraViewPlugin {\n /**\n * Start the camera view with optional configuration.\n *\n * @param options - Configuration options for the camera session\n * @returns A promise that resolves when the camera has started\n *\n * @since 1.0.0\n */\n start(options?: CameraSessionConfiguration): Promise<void>;\n\n /**\n * Stop the camera view and release resources.\n *\n * @returns A promise that resolves when the camera has stopped\n *\n * @since 1.0.0\n */\n stop(): Promise<void>;\n\n /**\n * Check if the camera view is currently running.\n *\n * @returns A promise that resolves with an object containing the running state of the camera\n *\n * @since 1.0.0\n */\n isRunning(): Promise<IsRunningResponse>;\n\n /**\n * Capture a photo using the current camera configuration.\n *\n * @param options - Capture configuration options\n * @returns A promise that resolves with an object containing either a base64 encoded string or file path of the captured photo\n *\n * @since 1.0.0\n */\n capture<T extends CaptureOptions>(options: T): Promise<CaptureResponse<T>>;\n\n /**\n * Captures a frame from the current camera preview without using the full camera capture pipeline.\n *\n * Unlike `capture()` which may trigger hardware-level photo capture on native platforms,\n * this method quickly samples the current video stream. This is suitable computer vision or\n * simple snapshots where high fidelity is not required.\n *\n * On web this method does exactly the same as `capture()` as it only captures a frame from the video stream\n * because unfortunately [ImageCapture API](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture) is\n * not yet well supported on the web.\n *\n * @param options - Capture configuration options\n * @returns A promise that resolves with an object containing either a base64 encoded string or file path of the captured sample\n *\n * @since 1.0.0\n */\n captureSample<T extends CaptureOptions>(options: T): Promise<CaptureResponse<T>>;\n\n /**\n * Start recording video from the current camera.\n * Camera must be running. Throws if already recording.\n *\n * @param options - Optional recording configuration\n * @returns A promise that resolves when recording has started\n *\n * @since 2.3.0\n */\n startRecording(options?: VideoRecordingOptions): Promise<void>;\n\n /**\n * Stop the current video recording and return the result.\n * Throws if no recording is in progress.\n *\n * @returns A promise that resolves with the recorded video file path\n *\n * @since 2.3.0\n */\n stopRecording(): Promise<VideoRecordingResponse>;\n\n /**\n * Switch between front and back camera.\n *\n * @returns A promise that resolves when the camera has been flipped\n *\n * @since 1.0.0\n */\n flipCamera(): Promise<void>;\n\n /**\n * Get available camera devices for capturing photos.\n *\n * @returns A promise that resolves with an object containing an array of available capture devices\n *\n * @since 1.0.0\n */\n getAvailableDevices(): Promise<GetAvailableDevicesResponse>;\n\n /**\n * Get current zoom level information and available range.\n *\n * @remarks\n * Make sure the camera is properly initialized before calling this method. Otherwise, this might\n * lead to returning default values on android.\n *\n * @returns A promise that resolves with an object containing min, max and current zoom levels\n *\n * @since 1.0.0\n */\n getZoom(): Promise<GetZoomResponse>;\n\n /**\n * Set the camera zoom level.\n *\n * @param options - Zoom configuration options\n * @param options.level - The zoom level to set\n * @param options.ramp - Whether to animate the zoom level change, defaults to false (iOS only)\n * @returns A promise that resolves when the zoom level has been set\n *\n * @remarks\n * On web platforms, zoom functionality may be limited by browser support.\n * When native zoom is not available, a CSS-based zoom simulation is applied.\n *\n * @since 1.0.0\n */\n setZoom(options: { level: number; ramp?: boolean }): Promise<void>;\n\n /**\n * Get current flash mode setting.\n *\n * @returns A promise that resolves with an object containing the current flash mode\n *\n * @since 1.0.0\n */\n getFlashMode(): Promise<GetFlashModeResponse>;\n\n /**\n * Get supported flash modes for the current camera.\n *\n * @returns A promise that resolves with an object containing an array of supported flash modes\n *\n * @since 1.0.0\n */\n getSupportedFlashModes(): Promise<GetSupportedFlashModesResponse>;\n\n /**\n * Set the camera flash mode.\n *\n * @param options - Flash mode configuration options\n * @param options.mode - The flash mode to set\n * @returns A promise that resolves when the flash mode has been set\n *\n * @since 1.0.0\n */\n setFlashMode(options: { mode: FlashMode }): Promise<void>;\n\n /**\n * Check if the device supports torch (flashlight) functionality.\n *\n * @remarks\n * **Important**: You must call this method and verify torch availability before using\n * `setTorchMode()` or `getTorchMode()`. Calling torch methods on devices without\n * torch support will throw an exception.\n *\n * @returns A promise that resolves with an object containing torch availability status\n *\n * @since 1.2.0\n */\n isTorchAvailable(): Promise<IsTorchAvailableResponse>;\n\n /**\n * Get the current torch (flashlight) state.\n *\n * @remarks\n * **Important**: Call `isTorchAvailable()` first to ensure the device supports torch\n * functionality. This method will throw an exception if torch is not supported.\n *\n * @returns A promise that resolves with an object containing the current torch state\n *\n * @since 1.2.0\n */\n getTorchMode(): Promise<GetTorchModeResponse>;\n\n /**\n * Set the torch (flashlight) mode and intensity.\n *\n * @remarks\n * **Important**: Call `isTorchAvailable()` first to ensure the device supports torch\n * functionality. This method will throw an exception if torch is not supported.\n *\n * The torch provides continuous illumination, unlike flash which only activates during photo capture.\n * On iOS, you can control the torch intensity level. On Android, the torch is either on or off.\n *\n * @param options - Torch configuration options\n * @param options.enabled - Whether to enable or disable the torch\n * @param options.level - The torch intensity level (0.0 to 1.0, iOS only). Defaults to 1.0 when enabled\n * @returns A promise that resolves when the torch mode has been set\n *\n * @since 1.2.0\n */\n setTorchMode(options: { enabled: boolean; level?: number }): Promise<void>;\n\n /**\n * Check camera and microphone permission status without requesting permissions.\n *\n * @returns A promise that resolves with an object containing the camera and microphone permission status\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise<PermissionStatus>;\n\n /**\n * Request camera and/or microphone permissions from the user.\n *\n * By default, only camera permission is requested. To also request microphone\n * permission (needed for video recording with audio), pass `{ permissions: ['camera', 'microphone'] }`.\n *\n * @param options - Optional object specifying which permissions to request\n * @returns A promise that resolves with an object containing the camera and microphone permission status\n *\n * @since 1.0.0\n */\n requestPermissions(options?: { permissions?: CameraPermissionType[] }): Promise<PermissionStatus>;\n\n /**\n * Listen for barcode detection events.\n * This event is emitted when a barcode is detected in the camera preview.\n *\n * @param eventName - The name of the event to listen for ('barcodeDetected')\n * @param listenerFunc - The callback function to execute when a barcode is detected\n * @returns A promise that resolves with an event subscription\n *\n * @since 1.0.0\n */\n addListener(\n eventName: 'barcodeDetected',\n listenerFunc: (data: BarcodeDetectionData) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @param eventName - Optional event name to remove listeners for\n * @returns A promise that resolves when the listeners are removed\n *\n * @since 1.0.0\n */\n removeAllListeners(eventName?: string): Promise<void>;\n}\n\n// ------------------------------------------------------------------------------\n// Camera Configuration Types\n// ------------------------------------------------------------------------------\n\n/**\n * Position options for the camera.\n * - 'front': Front-facing camera\n * - 'back': Rear-facing camera\n *\n * @since 1.0.0\n */\nexport type CameraPosition = 'front' | 'back';\n\n/**\n * Flash mode options for the camera.\n * - 'off': Flash disabled\n * - 'on': Flash always on\n * - 'auto': Flash automatically enabled in low-light conditions\n *\n * @since 1.0.0\n */\nexport type FlashMode = 'off' | 'on' | 'auto';\n\n/**\n * Video recording quality presets.\n *\n * @remarks\n * On iOS this maps to `AVCaptureSession.Preset` values.\n * On Android this maps to CameraX `QualitySelector` values.\n *\n * @since 2.3.0\n */\nexport type VideoRecordingQuality = 'lowest' | 'sd' | 'hd' | 'fhd' | 'uhd' | 'highest';\n\n/**\n * Represents a physical camera device on the device.\n *\n * @since 1.0.0\n */\nexport interface CameraDevice {\n /** The unique identifier of the camera device */\n id: string;\n\n /** The human-readable name of the camera device */\n name: string;\n\n /** The position of the camera device (front or back) */\n position: CameraPosition;\n\n /** The type of the camera device (e.g., wide, ultra-wide, telephoto) - iOS only */\n deviceType?: CameraDeviceType;\n}\n\n/**\n * Available camera device types for iOS.\n * Maps to AVCaptureDevice DeviceTypes in iOS.\n *\n * @see https://developer.apple.com/documentation/avfoundation/avcapturedevice/devicetype-swift.struct\n *\n * @since 1.0.0\n */\nexport type CameraDeviceType =\n /** builtInWideAngleCamera - standard camera */\n | 'wideAngle'\n /** builtInUltraWideCamera - 0.5x zoom level */\n | 'ultraWide'\n /** builtInTelephotoCamera - 2x/3x zoom level */\n | 'telephoto'\n /** builtInDualCamera - wide + telephoto combination */\n | 'dual'\n /** builtInDualWideCamera - wide + ultraWide combination */\n | 'dualWide'\n /** builtInTripleCamera - wide + ultraWide + telephoto */\n | 'triple'\n /** builtInTrueDepthCamera - front-facing camera with depth sensing */\n | 'trueDepth';\n\n/**\n * Supported barcode types for detection.\n * Specifying only the barcode types you need can improve performance\n * and reduce battery consumption.\n *\n * @since 2.1.0\n */\nexport type BarcodeType =\n /** QR Code */\n | 'qr'\n /** Code 128 barcode */\n | 'code128'\n /** Code 39 barcode */\n | 'code39'\n /** Code 39 Mod 43 barcode */\n | 'code39Mod43'\n /** Code 93 barcode */\n | 'code93'\n /** EAN-8 barcode */\n | 'ean8'\n /** EAN-13 barcode */\n | 'ean13'\n /** Interleaved 2 of 5 barcode */\n | 'interleaved2of5'\n /** ITF-14 barcode */\n | 'itf14'\n /** PDF417 barcode */\n | 'pdf417'\n /** Aztec code */\n | 'aztec'\n /** Data Matrix code */\n | 'dataMatrix'\n /** UPC-E barcode */\n | 'upce';\n\n/**\n * Configuration options for starting a camera session.\n *\n * @since 1.0.0\n */\nexport interface CameraSessionConfiguration {\n /**\n * Enables the barcode detection functionality\n * @default false\n */\n enableBarcodeDetection?: boolean;\n\n /**\n * Specific barcode types to detect. If not provided, all supported types are detected.\n * Specifying only the types you need can significantly improve performance and reduce\n * battery consumption, especially on mobile devices.\n *\n * @example ['qr', 'code128'] // Only detect QR codes and Code 128 barcodes\n * @default undefined - all supported types are detected\n * @since 2.1.0\n */\n barcodeTypes?: BarcodeType[];\n\n /**\n * Position of the camera to use\n * @default 'back'\n */\n position?: CameraPosition;\n\n /**\n * Specific device ID of the camera to use\n * If provided, takes precedence over position\n */\n deviceId?: string;\n\n /**\n * Whether to use the triple camera if available (iPhone Pro models only)\n * @default false\n */\n useTripleCameraIfAvailable?: boolean;\n\n /**\n * Ordered list of preferred camera device types to use (iOS only).\n * The system will attempt to use the first available camera type in the list.\n * If position is also provided, the system will use the first available camera type\n * that matches the position and is in the list.\n *\n * This will fallback to the default camera type if none of the preferred types are available.\n *\n * @example [CameraDeviceType.WideAngle, CameraDeviceType.UltraWide, CameraDeviceType.Telephoto]\n * @default undefined - system will decide based on position/deviceId\n */\n preferredCameraDeviceTypes?: CameraDeviceType[];\n\n /**\n * The initial zoom factor to use\n * @default 1.0\n */\n zoomFactor?: number;\n\n /**\n * Optional HTML ID of the container element where the camera view should be rendered.\n * If not provided, the camera view will be appended to the document body. Web only.\n * @example 'cameraContainer'\n */\n containerElementId?: string;\n}\n\n/**\n * Configuration options for capturing photos and samples.\n *\n * @since 1.1.0\n */\nexport interface CaptureOptions {\n /**\n * The JPEG quality of the captured photo/sample on a scale of 0-100\n * @since 1.1.0\n */\n quality: number;\n\n /**\n * If true, saves to a temporary file and returns the web path instead of base64.\n * The web path can be used to set the src attribute of an image for efficient loading and rendering.\n * This reduces the data that needs to be transferred over the bridge, which can improve performance\n * especially for high-resolution images.\n * @default false\n * @since 1.1.0\n */\n saveToFile?: boolean;\n}\n\n/**\n * Configuration options for video recording.\n * @since 2.3.0\n */\nexport interface VideoRecordingOptions {\n /**\n * Whether to record audio with the video.\n * Requires microphone permission.\n * @default false\n * @since 2.3.0\n */\n enableAudio?: boolean;\n\n /**\n * Video recording quality preset.\n * Native platforms only (iOS/Android). Ignored on web.\n * @default 'highest'\n * @since 2.3.0\n */\n videoQuality?: VideoRecordingQuality;\n}\n\n/**\n * Response from stopping a video recording.\n * @since 2.3.0\n */\nexport interface VideoRecordingResponse {\n /**\n * Web-accessible path to the recorded video file that can be used to set the\n * `src` attribute of a video element for efficient loading and rendering.\n * On web, this is a blob URL.\n * On iOS/Android, this is a Capacitor bridge path served by the local web server.\n * @since 2.3.0\n */\n webPath: string;\n\n /**\n * The full, platform-specific file URL (`file://...`) to the recorded video,\n * usable with the Filesystem API or `Capacitor.convertFileSrc()`.\n * Native only (iOS/Android); `undefined` on web.\n * @since 2.4.0\n */\n path?: string;\n}\n\n// ------------------------------------------------------------------------------\n// Response Interfaces\n// ------------------------------------------------------------------------------\n\n/**\n * Response for checking if the camera view is running.\n *\n * @since 1.0.0\n */\nexport interface IsRunningResponse {\n /** Indicates if the camera view is currently active and running */\n isRunning: boolean;\n}\n\n/**\n * Response for capturing a photo\n * This will contain either a base64 encoded string or a web path to the captured photo,\n * depending on the `saveToFile` option in the CaptureOptions.\n * @since 1.0.0\n */\nexport type CaptureResponse<T extends CaptureOptions = CaptureOptions> = T['saveToFile'] extends true\n ? {\n /** 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) */\n webPath: string;\n\n /**\n * The full, platform-specific file URL (`file://...`) to the captured photo,\n * usable with the Filesystem API or `Capacitor.convertFileSrc()`.\n * Native only (iOS/Android); `undefined` on web.\n * @since 2.4.0\n */\n path?: string;\n }\n : {\n /** The base64 encoded string of the captured photo (when saveToFile is false or undefined) */\n photo: string;\n };\n\n/**\n * Response for getting available camera devices.\n *\n * @since 1.0.0\n */\nexport interface GetAvailableDevicesResponse {\n /** An array of available camera devices */\n devices: CameraDevice[];\n}\n\n/**\n * Response for getting zoom level information.\n *\n * @since 1.0.0\n */\nexport interface GetZoomResponse {\n /** The minimum zoom level supported */\n min: number;\n\n /** The maximum zoom level supported */\n max: number;\n\n /** The current zoom level */\n current: number;\n}\n\n/**\n * Response for getting the current flash mode.\n *\n * @since 1.0.0\n */\nexport interface GetFlashModeResponse {\n /** The current flash mode setting */\n flashMode: FlashMode;\n}\n\n/**\n * Response for getting supported flash modes.\n *\n * @since 1.0.0\n */\nexport interface GetSupportedFlashModesResponse {\n /** An array of flash modes supported by the current camera */\n flashModes: FlashMode[];\n}\n\n/**\n * Response for checking torch availability.\n *\n * @since 1.2.0\n */\nexport interface IsTorchAvailableResponse {\n /** Indicates if the device supports torch (flashlight) functionality */\n available: boolean;\n}\n\n/**\n * Response for getting the current torch mode.\n *\n * @since 1.2.0\n */\nexport interface GetTorchModeResponse {\n /** Indicates if the torch is currently enabled */\n enabled: boolean;\n /** The current torch intensity level (0.0 to 1.0, iOS only). Always 1.0 on Android when enabled */\n level: number;\n}\n\n/**\n * Data for a detected barcode.\n *\n * @since 1.0.0\n */\nexport interface BarcodeDetectionData {\n /** The decoded string value of the barcode */\n value: string;\n\n /**\n * Raw bytes as they were encoded in the barcode.\n *\n * On Android, this is forwarded from ML Kit.\n * On iOS, this is available for descriptor-backed formats such as QR, Aztec, PDF417, and Data Matrix.\n * On web, this is not available because the Barcode Detection API only exposes the decoded string value.\n *\n * @since 2.2.0\n */\n rawBytes?: number[];\n\n /**\n * The display value of the barcode on Android.\n *\n * This is forwarded from ML Kit and may contain a formatted, human-readable\n * representation that differs from the raw decoded value. iOS and web do not\n * expose a separate display value, so this property is only emitted on Android.\n */\n displayValue?: string;\n\n /** The type/format of the barcode (e.g., 'qr', 'code128', etc.) */\n type: string;\n\n /** The bounding rectangle of the barcode in the camera frame. */\n boundingRect: BoundingRect;\n}\n\n/**\n * Rectangle defining the boundary of the barcode in the camera frame.\n * Coordinates are normalized between 0 and 1 relative to the camera frame.\n *\n * @since 1.0.0\n */\nexport interface BoundingRect {\n /** X-coordinate of the top-left corner */\n x: number;\n /** Y-coordinate of the top-left corner */\n y: number;\n /** Width of the bounding rectangle (should match the actual width of the barcode) */\n width: number;\n /** Height of the bounding rectangle (should match the actual height of the barcode) */\n height: number;\n}\n\n/**\n * Permission types that can be requested.\n * - 'camera': Camera access permission\n * - 'microphone': Microphone access permission (needed for video recording with audio)\n *\n * @since 2.3.0\n */\nexport type CameraPermissionType = 'camera' | 'microphone';\n\n/**\n * Response for the camera and microphone permission status.\n *\n * @since 1.0.0\n */\nexport interface PermissionStatus {\n /** The state of the camera permission */\n camera: PermissionState;\n /** The state of the microphone permission */\n microphone: PermissionState;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Main plugin interface for Capacitor Camera View functionality.\n *\n * When a method below rejects, the resulting error carries a `code` property\n * set to one of the stable `CameraErrorCode` strings so consumers can\n * `switch` on `error.code` instead of matching on the human-readable message.\n * See the `CameraErrorCode` type for the full vocabulary, including the one\n * divergence on web for methods with no web implementation.\n *\n * @since 1.0.0\n */\nexport interface CameraViewPlugin {\n /**\n * Start the camera view with optional configuration.\n *\n * Rejects if a camera session is already running. Call `stop()` first if you need to\n * start a new session with different options.\n *\n * @param options - Configuration options for the camera session\n * @returns A promise that resolves when the camera has started\n *\n * @since 1.0.0\n */\n start(options?: CameraSessionConfiguration): Promise<void>;\n\n /**\n * Stop the camera view and release resources.\n *\n * @returns A promise that resolves when the camera has stopped\n *\n * @since 1.0.0\n */\n stop(): Promise<void>;\n\n /**\n * Check if the camera view is currently running.\n *\n * @returns A promise that resolves with an object containing the running state of the camera\n *\n * @since 1.0.0\n */\n isRunning(): Promise<IsRunningResponse>;\n\n /**\n * Capture a photo using the current camera configuration.\n *\n * @param options - Capture configuration options\n * @returns A promise that resolves with an object containing either a base64 encoded string or file path of the captured photo\n *\n * @since 1.0.0\n */\n capture<T extends CaptureOptions = CaptureOptions & { saveToFile?: undefined }>(\n options?: T,\n ): Promise<CaptureResponse<T>>;\n\n /**\n * Captures a frame from the current camera preview without using the full camera capture pipeline.\n *\n * Unlike `capture()` which may trigger hardware-level photo capture on native platforms,\n * this method quickly samples the current video stream. This is suitable computer vision or\n * simple snapshots where high fidelity is not required.\n *\n * On web this method does exactly the same as `capture()` as it only captures a frame from the video stream\n * because unfortunately [ImageCapture API](https://developer.mozilla.org/en-US/docs/Web/API/ImageCapture) is\n * not yet well supported on the web.\n *\n * @param options - Capture configuration options\n * @returns A promise that resolves with an object containing either a base64 encoded string or file path of the captured sample\n *\n * @since 1.0.0\n */\n captureSample<T extends CaptureOptions = CaptureOptions & { saveToFile?: undefined }>(\n options?: T,\n ): Promise<CaptureResponse<T>>;\n\n /**\n * Start recording video from the current camera.\n * Camera must be running. Throws if already recording.\n *\n * @param options - Optional recording configuration\n * @returns A promise that resolves when recording has started\n *\n * @since 2.3.0\n */\n startRecording(options?: VideoRecordingOptions): Promise<void>;\n\n /**\n * Stop the current video recording and return the result.\n * Throws if no recording is in progress.\n *\n * @returns A promise that resolves with the recorded video file path\n *\n * @since 2.3.0\n */\n stopRecording(): Promise<VideoRecordingResponse>;\n\n /**\n * Switch between front and back camera.\n *\n * Rejects with `RECORDING_ALREADY_IN_PROGRESS` while a video recording is active, on\n * iOS, Android, and web alike: swapping the camera input/device mid-recording would\n * either drop the audio track or interrupt the recording outright, so the recording is\n * left intact and must be stopped before flipping.\n *\n * @returns A promise that resolves when the camera has been flipped\n *\n * @since 1.0.0\n */\n flipCamera(): Promise<void>;\n\n /**\n * Get available camera devices for capturing photos.\n *\n * @returns A promise that resolves with an object containing an array of available capture devices\n *\n * @since 1.0.0\n */\n getAvailableDevices(): Promise<GetAvailableDevicesResponse>;\n\n /**\n * Get current zoom level information and available range.\n *\n * On iOS, when the camera is a virtual device (e.g. the triple camera enabled via\n * `useTripleCameraIfAvailable`), the returned values are in the device's raw zoom domain and are\n * not UI multipliers like \"0.5x\"/\"1x\"/\"2x\". A `min` of `1.0` corresponds to the widest constituent\n * lens (the ultra-wide \"0.5x\" lens), so a session started at the default zoom reports a `current`\n * of the wide-lens switch-over factor (typically `2.0`) rather than `1.0`. Treat these numbers as\n * device-relative and derive the usable range from `min`/`max` instead of assuming `1.0` is the\n * default.\n *\n * @remarks\n * Make sure the camera is properly initialized before calling this method. Otherwise, this might\n * lead to returning default values on android.\n *\n * @returns A promise that resolves with an object containing min, max and current zoom levels\n *\n * @since 1.0.0\n */\n getZoom(): Promise<GetZoomResponse>;\n\n /**\n * Set the camera zoom level.\n *\n * On iOS virtual devices (e.g. the triple camera) `level` is a raw device zoom factor, not a UI\n * multiplier. Derive valid values from the `min`/`max` returned by `getZoom()` rather than assuming\n * `1.0` maps to the wide \"1x\" lens.\n *\n * @param options - Zoom configuration options\n * @param options.level - The zoom level to set\n * @param options.ramp - Whether to animate the zoom level change, defaults to false (iOS only)\n * @returns A promise that resolves when the zoom level has been set\n *\n * @remarks\n * On web platforms, zoom functionality may be limited by browser support.\n * When native zoom is not available, a CSS-based zoom simulation is applied.\n *\n * @since 1.0.0\n */\n setZoom(options: { level: number; ramp?: boolean }): Promise<void>;\n\n /**\n * Focus and meter the camera at a specific point (tap-to-focus).\n *\n * Because the WebView sits above the native camera preview and consumes every\n * touch, the native preview can never receive tap gestures itself. Instead,\n * the app catches the tap in the DOM and forwards its coordinates here; since\n * the native preview is always rendered fullscreen behind the WebView, the\n * mapping to the sensor is deterministic.\n *\n * The camera runs a one-shot focus/exposure at the given point and then\n * automatically restores continuous auto-focus/auto-exposure (immediately on a\n * subsequent tap, or after a short timeout), so focus is never left\n * permanently locked.\n *\n * @param options - The point to focus on\n * @param options.x - The horizontal coordinate in CSS/viewport pixels, measured\n * from the left edge of the viewport. This is the same coordinate space the\n * plugin emits for barcode `boundingRect`, just in the opposite direction.\n * @param options.y - The vertical coordinate in CSS/viewport pixels, measured\n * from the top edge of the viewport.\n * @returns A promise that resolves when the focus/metering point has been applied\n *\n * @remarks\n * On fixed-focus cameras (e.g. some front cameras) the plugin degrades to\n * exposure-only metering where possible. If neither focus nor exposure metering\n * at a point is supported, the promise rejects with the `FOCUS_NOT_SUPPORTED`\n * error code. Rejects with `SESSION_NOT_RUNNING` when the camera is not running.\n *\n * Not supported on web: this method rejects with an `unimplemented` error there,\n * because the `pointsOfInterest` media-track constraint has effectively no\n * browser support.\n *\n * @since 3.0.0\n */\n setFocusPoint(options: { x: number; y: number }): Promise<void>;\n\n /**\n * Get current flash mode setting.\n *\n * @returns A promise that resolves with an object containing the current flash mode\n *\n * @since 1.0.0\n */\n getFlashMode(): Promise<GetFlashModeResponse>;\n\n /**\n * Get supported flash modes for the current camera.\n *\n * @returns A promise that resolves with an object containing an array of supported flash modes\n *\n * @since 1.0.0\n */\n getSupportedFlashModes(): Promise<GetSupportedFlashModesResponse>;\n\n /**\n * Set the camera flash mode.\n *\n * @param options - Flash mode configuration options\n * @param options.mode - The flash mode to set\n * @returns A promise that resolves when the flash mode has been set\n *\n * @since 1.0.0\n */\n setFlashMode(options: { mode: FlashMode }): Promise<void>;\n\n /**\n * Check if the device supports torch (flashlight) functionality.\n *\n * @remarks\n * **Important**: You must call this method and verify torch availability before using\n * `setTorchMode()` or `getTorchMode()`. Calling torch methods on devices without\n * torch support will throw an exception.\n *\n * @returns A promise that resolves with an object containing torch availability status\n *\n * @since 1.2.0\n */\n isTorchAvailable(): Promise<IsTorchAvailableResponse>;\n\n /**\n * Get the current torch (flashlight) state.\n *\n * @remarks\n * **Important**: Call `isTorchAvailable()` first to ensure the device supports torch\n * functionality. This method will throw an exception if torch is not supported.\n *\n * @returns A promise that resolves with an object containing the current torch state\n *\n * @since 1.2.0\n */\n getTorchMode(): Promise<GetTorchModeResponse>;\n\n /**\n * Set the torch (flashlight) mode and intensity.\n *\n * @remarks\n * **Important**: Call `isTorchAvailable()` first to ensure the device supports torch\n * functionality. This method will throw an exception if torch is not supported.\n *\n * The torch provides continuous illumination, unlike flash which only activates during photo capture.\n * You can control the torch intensity level on both iOS and, on API 33+ devices with\n * multi-level torch hardware, Android. On older Android versions or single-level torch\n * hardware, `level` is best-effort and ignored - the torch is simply switched on or off.\n *\n * @param options - Torch configuration options\n * @param options.enabled - Whether to enable or disable the torch\n * @param options.level - The torch intensity level (0.0 to 1.0). Defaults to 1.0 when enabled\n * @returns A promise that resolves when the torch mode has been set\n *\n * @since 1.2.0\n */\n setTorchMode(options: { enabled: boolean; level?: number }): Promise<void>;\n\n /**\n * Check camera and microphone permission status without requesting permissions.\n *\n * @returns A promise that resolves with an object containing the camera and microphone permission status\n *\n * @since 1.0.0\n */\n checkPermissions(): Promise<PermissionStatus>;\n\n /**\n * Request camera and/or microphone permissions from the user.\n *\n * By default, only camera permission is requested. To also request microphone\n * permission (needed for video recording with audio), pass `{ permissions: ['camera', 'microphone'] }`.\n *\n * @param options - Optional object specifying which permissions to request\n * @returns A promise that resolves with an object containing the camera and microphone permission status\n *\n * @since 1.0.0\n */\n requestPermissions(options?: { permissions?: CameraPermissionType[] }): Promise<PermissionStatus>;\n\n /**\n * Listen for barcode detection events.\n * This event is emitted when a barcode is detected in the camera preview.\n *\n * @remarks\n * Events are rate-controlled to avoid flooding the bridge. Repeated detections\n * of the same barcode (identical `value` and `type`) are suppressed while the\n * code stays in view: after an initial event, the same code re-emits at most\n * once per ~500 ms suppression window. Pointing the camera at a different\n * barcode (a different `value` or `type`) emits immediately rather than waiting\n * for the window to elapse. This behavior is consistent across iOS, Android,\n * and web.\n *\n * @param eventName - The name of the event to listen for ('barcodeDetected')\n * @param listenerFunc - The callback function to execute when a barcode is detected\n * @returns A promise that resolves with an event subscription\n *\n * @since 1.0.0\n */\n addListener(\n eventName: 'barcodeDetected',\n listenerFunc: (data: BarcodeDetectionData) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for camera interruption events.\n *\n * Emitted when the capture session is interrupted by the system, for example\n * an incoming phone call, another app claiming the camera or microphone,\n * losing the camera in iPad Split View, or system pressure. The preview\n * typically freezes for the duration of the interruption.\n *\n * @remarks\n * Currently emitted on iOS only. Android and web will follow.\n *\n * @param eventName - The name of the event to listen for ('cameraInterrupted')\n * @param listenerFunc - The callback function to execute when the camera is interrupted\n * @returns A promise that resolves with an event subscription\n *\n * @since 3.0.0\n */\n addListener(\n eventName: 'cameraInterrupted',\n listenerFunc: (data: CameraInterruptedData) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for camera resume events.\n *\n * Emitted when a previous interruption ends and the capture session resumes,\n * for example after an incoming phone call finishes. Pair this with\n * `cameraInterrupted` to update your UI when the preview recovers.\n *\n * @remarks\n * Currently emitted on iOS only. Android and web will follow.\n *\n * @param eventName - The name of the event to listen for ('cameraResumed')\n * @param listenerFunc - The callback function to execute when the camera resumes\n * @returns A promise that resolves with an event subscription\n *\n * @since 3.0.0\n */\n addListener(eventName: 'cameraResumed', listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n /**\n * Listen for camera runtime error events.\n *\n * Emitted when the capture session hits a runtime error. When the underlying\n * media services are reset, the plugin restarts the session automatically, so\n * this event is primarily informational for logging and diagnostics.\n *\n * @remarks\n * Currently emitted on iOS only. Android and web will follow.\n *\n * @param eventName - The name of the event to listen for ('cameraRuntimeError')\n * @param listenerFunc - The callback function to execute when a runtime error occurs\n * @returns A promise that resolves with an event subscription\n *\n * @since 3.0.0\n */\n addListener(\n eventName: 'cameraRuntimeError',\n listenerFunc: (data: CameraRuntimeErrorData) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @remarks\n * This removes *every* listener registered on this plugin instance, regardless of event\n * name, on iOS, Android, and web. There is no way to remove listeners for a single event\n * name only; if you need that, keep track of the `PluginListenerHandle` returned by\n * `addListener()` and call `remove()` on it instead.\n *\n * @returns A promise that resolves when the listeners are removed\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n}\n\n// ------------------------------------------------------------------------------\n// Camera Configuration Types\n// ------------------------------------------------------------------------------\n\n/**\n * Position options for the camera.\n * - 'front': Front-facing camera\n * - 'back': Rear-facing camera\n *\n * @since 1.0.0\n */\nexport type CameraPosition = 'front' | 'back';\n\n/**\n * Flash mode options for the camera.\n * - 'off': Flash disabled\n * - 'on': Flash always on\n * - 'auto': Flash automatically enabled in low-light conditions\n *\n * @since 1.0.0\n */\nexport type FlashMode = 'off' | 'on' | 'auto';\n\n/**\n * Video recording quality presets.\n *\n * @remarks\n * On iOS this maps to `AVCaptureSession.Preset` values.\n * On Android this maps to CameraX `QualitySelector` values.\n *\n * @since 2.3.0\n */\nexport type VideoRecordingQuality = 'lowest' | 'sd' | 'hd' | 'fhd' | 'uhd' | 'highest';\n\n/**\n * Sensor aspect ratio for a camera session, applied consistently to both the\n * live preview stream and photo capture.\n * - '4:3': The native photo aspect ratio of most mobile camera sensors\n * - '16:9': The typical video aspect ratio\n *\n * @since 3.0.0\n */\nexport type CameraAspectRatio = '4:3' | '16:9';\n\n/**\n * How the camera preview is scaled to fill its container when the sensor\n * aspect ratio differs from the container's aspect ratio.\n * - 'cover': The preview fills the whole container, center-cropping the frame\n * so no empty bars are shown. Parts of the frame outside the container are\n * hidden from the preview (long-standing default behavior).\n * - 'fit': The whole sensor frame is scaled to fit inside the container\n * (letterboxed), so the user sees the entire frame they are about to\n * capture. Empty bars appear on the short axis.\n *\n * @since 3.0.0\n */\nexport type PreviewScaleMode = 'cover' | 'fit';\n\n/**\n * Represents a physical camera device on the device.\n *\n * @since 1.0.0\n */\nexport interface CameraDevice {\n /** The unique identifier of the camera device */\n id: string;\n\n /** The human-readable name of the camera device */\n name: string;\n\n /** The position of the camera device (front or back) */\n position: CameraPosition;\n\n /** The type of the camera device (e.g., wide, ultra-wide, telephoto) - iOS only */\n deviceType?: CameraDeviceType;\n}\n\n/**\n * Available camera device types for iOS.\n * Maps to AVCaptureDevice DeviceTypes in iOS.\n *\n * @see https://developer.apple.com/documentation/avfoundation/avcapturedevice/devicetype-swift.struct\n *\n * @since 1.0.0\n */\nexport type CameraDeviceType =\n /** builtInWideAngleCamera - standard camera */\n | 'wideAngle'\n /** builtInUltraWideCamera - 0.5x zoom level */\n | 'ultraWide'\n /** builtInTelephotoCamera - 2x/3x zoom level */\n | 'telephoto'\n /** builtInDualCamera - wide + telephoto combination */\n | 'dual'\n /** builtInDualWideCamera - wide + ultraWide combination */\n | 'dualWide'\n /** builtInTripleCamera - wide + ultraWide + telephoto */\n | 'triple'\n /** builtInTrueDepthCamera - front-facing camera with depth sensing */\n | 'trueDepth';\n\n/**\n * Supported barcode types for detection.\n * Specifying only the barcode types you need can improve performance\n * and reduce battery consumption.\n *\n * @since 2.1.0\n */\nexport type BarcodeType =\n /** QR Code */\n | 'qr'\n /** Code 128 barcode */\n | 'code128'\n /** Code 39 barcode */\n | 'code39'\n /** Code 39 Mod 43 barcode */\n | 'code39Mod43'\n /** Code 93 barcode */\n | 'code93'\n /** Codabar barcode. Not detectable on iOS below 15.4; the deployment target is 16, so it is available on all supported iOS versions. */\n | 'codabar'\n /** EAN-8 barcode */\n | 'ean8'\n /** EAN-13 barcode */\n | 'ean13'\n /** Interleaved 2 of 5 barcode */\n | 'interleaved2of5'\n /** ITF-14 barcode */\n | 'itf14'\n /** PDF417 barcode */\n | 'pdf417'\n /** Aztec code */\n | 'aztec'\n /** Data Matrix code */\n | 'dataMatrix'\n /**\n * UPC-A barcode.\n *\n * Detected as a distinct `upcA` type on Android (ML Kit) and web\n * (BarcodeDetector). iOS cannot: AVFoundation has no UPC-A metadata type and\n * reports UPC-A codes as `ean13` (a UPC-A value is an EAN-13 with a leading\n * `0`), so requesting `upcA` on iOS has no effect and such codes arrive as\n * `ean13`.\n */\n | 'upcA'\n /** UPC-E barcode */\n | 'upce';\n\n/**\n * Configuration options for starting a camera session.\n *\n * @since 1.0.0\n */\nexport interface CameraSessionConfiguration {\n /**\n * Enables the barcode detection functionality\n * @default false\n */\n enableBarcodeDetection?: boolean;\n\n /**\n * Specific barcode types to detect. If not provided, all supported types are detected.\n * Specifying only the types you need can significantly improve performance and reduce\n * battery consumption, especially on mobile devices.\n *\n * @example ['qr', 'code128'] // Only detect QR codes and Code 128 barcodes\n * @default undefined - all supported types are detected\n * @since 2.1.0\n */\n barcodeTypes?: BarcodeType[];\n\n /**\n * Position of the camera to use\n * @default 'back'\n */\n position?: CameraPosition;\n\n /**\n * Specific device ID of the camera to use\n * If provided, takes precedence over position\n */\n deviceId?: string;\n\n /**\n * Whether to use the triple camera if available (iPhone Pro models only).\n *\n * The session starts directly on the virtual device, which lets iOS switch\n * between the ultra-wide, wide and telephoto lenses automatically. Takes\n * precedence over `preferredCameraDeviceTypes` for the rear camera.\n *\n * @default false\n */\n useTripleCameraIfAvailable?: boolean;\n\n /**\n * Ordered list of preferred camera device types to use (iOS only).\n * The system will attempt to use the first available camera type in the list.\n * If position is also provided, the system will use the first available camera type\n * that matches the position and is in the list.\n *\n * This will fallback to the default camera type if none of the preferred types are available.\n *\n * @example [CameraDeviceType.WideAngle, CameraDeviceType.UltraWide, CameraDeviceType.Telephoto]\n * @default undefined - system will decide based on position/deviceId\n */\n preferredCameraDeviceTypes?: CameraDeviceType[];\n\n /**\n * The sensor aspect ratio to use for the camera session, applied to both\n * the live preview stream and photo capture so the captured image matches\n * the framing the user sees.\n *\n * **Preview-vs-capture framing contract** (identical on iOS, Android and\n * web when this option is set):\n *\n * - By default (`previewScaleMode: 'cover'`) the preview fills its container\n * (the fullscreen view behind the WebView on iOS/Android, the container\n * element on web) using cover semantics: when the chosen sensor ratio\n * differs from the container ratio, the preview is center-cropped to fill\n * it — never letterboxed. `capture()` returns the full sensor-ratio image\n * (matching this option), NOT the on-screen crop. Parts of the image that\n * were cropped out of the preview by cover-scaling are therefore included\n * in the capture.\n * - With `previewScaleMode: 'fit'` the whole sensor frame is letterboxed to\n * fit inside the container, so the preview shows exactly the full captured\n * frame: `capture()` returns what the preview shows, with nothing cropped\n * out of view. See {@link previewScaleMode} for the letterbox-background\n * note.\n *\n * When this option is omitted, each platform keeps its long-standing\n * default behavior: iOS uses the sensor's native photo format (4:3),\n * Android prefers 16:9 with an automatic fallback, and web requests a\n * 16:9 stream and returns the visible (cover-cropped) preview region from\n * `capture()` instead of the full frame. Captured output stays JPEG on all\n * platforms either way.\n *\n * @default undefined - platform default (see above)\n * @since 3.0.0\n */\n aspectRatio?: CameraAspectRatio;\n\n /**\n * Optional capture-resolution hint: an upper bound, in pixels, for the\n * longer edge of captured photos. The platform picks the largest supported\n * capture resolution whose longer edge does not exceed this value (falling\n * back to the closest supported resolution when none fits) while keeping\n * the configured `aspectRatio`.\n *\n * This is a best-effort hint: the exact output dimensions depend on the\n * resolutions the sensor/browser actually supports.\n *\n * - iOS: constrains `AVCapturePhotoOutput.maxPhotoDimensions`. Only affects\n * `capture()`; `captureSample()` keeps sampling the preview stream.\n * - Android: bounds the CameraX ImageCapture resolution. Only affects\n * `capture()`.\n * - Web: used as the ideal `getUserMedia` width constraint, so it affects\n * the stream (preview and capture alike).\n *\n * @example 1280 // capture photos at most 1280px wide (longer edge)\n * @default undefined - platform default resolution\n * @since 3.0.0\n */\n captureMaxDimension?: number;\n\n /**\n * How the live preview is scaled into its container when the sensor aspect\n * ratio differs from the container's aspect ratio.\n *\n * - `'cover'` (default): the preview fills the container, center-cropping the\n * frame. This is the long-standing behavior and is unchanged when the\n * option is omitted.\n * - `'fit'`: the whole sensor frame is scaled to fit inside the container\n * (letterboxed), so the user sees the entire frame they are about to\n * capture. In `fit` mode the preview shows exactly the full captured frame,\n * and `capture()` returns what the preview shows.\n *\n * Applied consistently on iOS (`AVCaptureVideoPreviewLayer.videoGravity`),\n * Android (`PreviewView.ScaleType`) and web (`object-fit`). Barcode\n * `boundingRect` and `setFocusPoint` coordinates stay correct in both modes.\n *\n * **Letterbox background**: the empty bars shown in `fit` mode are not painted\n * by the plugin — they show whatever is visually behind/around the preview.\n * On iOS/Android that is the app's own background showing through the\n * transparent WebView; on web it is the container element's background. Style\n * that background (e.g. a black or themed color) to control how the\n * letterbox bars look.\n *\n * @default 'cover'\n * @since 3.0.0\n */\n previewScaleMode?: PreviewScaleMode;\n\n /**\n * The initial zoom factor to use.\n *\n * Expressed relative to the wide-angle lens, so `1.0` is the familiar \"1x\" field of view on every\n * camera. On iOS virtual devices whose raw `1.0` is the ultra-wide lens (e.g. the triple camera\n * enabled via `useTripleCameraIfAvailable`) the factor is scaled into the device's own zoom domain,\n * which is why `getZoom()` reports a larger `current` than the value passed here.\n *\n * @default 1.0\n */\n zoomFactor?: number;\n\n /**\n * Prioritize photo quality over capture responsiveness (iOS 17+ only).\n *\n * By default the plugin opts into the iOS 17+ responsive-capture pipeline\n * (zero-shutter-lag, responsive capture and fast capture prioritization) so\n * consecutive `capture()` calls have a lower shot-to-shot latency. Rapid\n * consecutive captures may then be delivered at a slightly reduced quality\n * instead of queueing.\n *\n * Set this to `true` to opt out of that behavior and always prioritize photo\n * quality. Has no effect on iOS versions or hardware without support for the\n * responsive-capture APIs, and no effect on Android or Web.\n *\n * @default false\n * @since 3.0.0\n */\n prioritizeQuality?: boolean;\n\n /**\n * Optional HTML ID of the container element where the camera view should be rendered.\n * If not provided, the camera view will be appended to the document body. Web only.\n * @example 'cameraContainer'\n */\n containerElementId?: string;\n}\n\n/**\n * Configuration options for capturing photos and samples.\n *\n * @since 1.1.0\n */\nexport interface CaptureOptions {\n /**\n * The JPEG quality of the captured photo/sample on a scale of 0-100. Cross-platform note:\n * for `quality >= 90`, iOS returns the original, unmodified JPEG produced by the camera\n * hardware instead of re-encoding it, to avoid unnecessary quality loss and CPU overhead.\n * Android and Web always encode at the exact requested quality. As a result, the same\n * `quality` value (90-100) can produce different file sizes on iOS versus Android/Web.\n * @default 90\n * @since 1.1.0\n */\n quality?: number;\n\n /**\n * If true, saves to a temporary file and returns the web path instead of base64.\n * The web path can be used to set the src attribute of an image for efficient loading and rendering.\n * This reduces the data that needs to be transferred over the bridge, which can improve performance\n * especially for high-resolution images.\n * @default false\n * @since 1.1.0\n */\n saveToFile?: boolean;\n}\n\n/**\n * Configuration options for video recording.\n * @since 2.3.0\n */\nexport interface VideoRecordingOptions {\n /**\n * Whether to record audio with the video.\n * Requires microphone permission.\n * @default false\n * @since 2.3.0\n */\n enableAudio?: boolean;\n\n /**\n * Video recording quality preset.\n * Native platforms only (iOS/Android). Ignored on web.\n * @default 'highest'\n * @since 2.3.0\n */\n videoQuality?: VideoRecordingQuality;\n}\n\n/**\n * Response from stopping a video recording.\n * @since 2.3.0\n */\nexport interface VideoRecordingResponse {\n /**\n * Web-accessible path to the recorded video file that can be used to set the\n * `src` attribute of a video element for efficient loading and rendering.\n * On web, this is a blob URL created with `URL.createObjectURL()`; the plugin does not\n * revoke it automatically, so call `URL.revokeObjectURL(webPath)` once you are done with\n * it (e.g. after playback or upload) to release the underlying memory.\n * On iOS/Android, this is a Capacitor bridge path served by the local web server and does\n * not need to be revoked.\n * @since 2.3.0\n */\n webPath: string;\n\n /**\n * The full, platform-specific file URL (`file://...`) to the recorded video,\n * usable with the Filesystem API or `Capacitor.convertFileSrc()`.\n * Native only (iOS/Android); `undefined` on web.\n * @since 2.4.0\n */\n path?: string;\n}\n\n// ------------------------------------------------------------------------------\n// Response Interfaces\n// ------------------------------------------------------------------------------\n\n/**\n * Response for checking if the camera view is running.\n *\n * @since 1.0.0\n */\nexport interface IsRunningResponse {\n /** Indicates if the camera view is currently active and running */\n isRunning: boolean;\n}\n\n/**\n * The file-path shaped result returned when `saveToFile` is `true`.\n * @since 1.0.0\n */\nexport interface CaptureFileResult {\n /**\n * The web path to the captured photo that can be used to set the src attribute of an image\n * for efficient loading and rendering (when saveToFile is true).\n *\n * On web, this is a blob URL created with `URL.createObjectURL()`. The plugin does not revoke\n * it automatically; once you are done with it (e.g. after the image has been displayed or\n * uploaded), call `URL.revokeObjectURL(webPath)` to release the underlying memory. On\n * iOS/Android this is a Capacitor bridge path served by the local web server and does not\n * need to be revoked.\n */\n webPath: string;\n\n /**\n * The full, platform-specific file URL (`file://...`) to the captured photo,\n * usable with the Filesystem API or `Capacitor.convertFileSrc()`.\n * Native only (iOS/Android); `undefined` on web.\n * @since 2.4.0\n */\n path?: string;\n}\n\n/**\n * The base64 shaped result returned when `saveToFile` is `false` or `undefined`.\n * @since 1.0.0\n */\nexport interface CaptureBase64Result {\n /** The base64 encoded string of the captured photo (when saveToFile is false or undefined) */\n photo: string;\n}\n\n/**\n * Response for capturing a photo\n * This will contain either a base64 encoded string or a web path to the captured photo,\n * depending on the `saveToFile` option in the CaptureOptions.\n *\n * @remarks\n * The narrowing is three-way on `saveToFile`:\n * - a literal `true` narrows to {@link CaptureFileResult}\n * - a literal `false`, an explicit `undefined`, or an options type that omits the key\n * entirely narrows to {@link CaptureBase64Result}\n * - a non-literal `boolean` (or a `CaptureOptions` with `saveToFile` left generic) resolves\n * to the union of both, since the runtime result can't be known at the type level\n *\n * @since 1.0.0\n */\nexport type CaptureResponse<T extends CaptureOptions = CaptureOptions> =\n SaveToFileOf<T> extends true\n ? CaptureFileResult\n : SaveToFileOf<T> extends false | undefined\n ? CaptureBase64Result\n : CaptureFileResult | CaptureBase64Result;\n\n/**\n * `T['saveToFile']` resolves through the `CaptureOptions` constraint to `boolean | undefined`\n * when `T` omits the key, which would widen the result to the union. Treat an absent key as\n * `undefined` instead.\n */\ntype SaveToFileOf<T extends CaptureOptions> = 'saveToFile' extends keyof T ? T['saveToFile'] : undefined;\n\n/**\n * Response for getting available camera devices.\n *\n * @since 1.0.0\n */\nexport interface GetAvailableDevicesResponse {\n /** An array of available camera devices */\n devices: CameraDevice[];\n}\n\n/**\n * Response for getting zoom level information.\n *\n * @remarks\n * On iOS virtual devices (e.g. the triple camera) these values are in the device's raw zoom domain,\n * not UI multipliers: `min` of `1.0` is the ultra-wide (\"0.5x\") lens and `current` reports the\n * wide-lens switch-over factor rather than `1.0` at the default zoom. See {@link CameraViewPlugin.getZoom}.\n *\n * @since 1.0.0\n */\nexport interface GetZoomResponse {\n /** The minimum zoom level supported. On iOS virtual devices `1.0` maps to the ultra-wide lens. */\n min: number;\n\n /** The maximum zoom level supported. On iOS virtual devices this is a raw device factor (capped at a 10x wide-equivalent zoom). */\n max: number;\n\n /** The current zoom level. On iOS virtual devices this is a raw device factor, not a UI multiplier. */\n current: number;\n}\n\n/**\n * Response for getting the current flash mode.\n *\n * @since 1.0.0\n */\nexport interface GetFlashModeResponse {\n /** The current flash mode setting */\n flashMode: FlashMode;\n}\n\n/**\n * Response for getting supported flash modes.\n *\n * @since 1.0.0\n */\nexport interface GetSupportedFlashModesResponse {\n /** An array of flash modes supported by the current camera */\n flashModes: FlashMode[];\n}\n\n/**\n * Response for checking torch availability.\n *\n * @since 1.2.0\n */\nexport interface IsTorchAvailableResponse {\n /** Indicates if the device supports torch (flashlight) functionality */\n available: boolean;\n}\n\n/**\n * Response for getting the current torch mode.\n *\n * @since 1.2.0\n */\nexport interface GetTorchModeResponse {\n /** Indicates if the torch is currently enabled */\n enabled: boolean;\n /**\n * The current torch intensity level (0.0 to 1.0).\n *\n * On Android this reflects the real hardware strength only on API 33+ devices with\n * multi-level torch hardware; below API 33, or on single-level hardware, the torch is\n * binary, so this is always 1.0 when enabled and 0.0 when off.\n */\n level: number;\n}\n\n/**\n * Data for a detected barcode.\n *\n * @since 1.0.0\n */\nexport interface BarcodeDetectionData {\n /** The decoded string value of the barcode */\n value: string;\n\n /**\n * Raw bytes as they were encoded in the barcode.\n *\n * On Android, this is forwarded from ML Kit.\n * On iOS, this is available for descriptor-backed formats such as QR, Aztec, PDF417, and Data Matrix.\n * On web, this is not available because the Barcode Detection API only exposes the decoded string value.\n *\n * @since 2.2.0\n */\n rawBytes?: number[];\n\n /**\n * The display value of the barcode on Android.\n *\n * This is forwarded from ML Kit and may contain a formatted, human-readable\n * representation that differs from the raw decoded value. iOS and web do not\n * expose a separate display value, so this property is only emitted on Android.\n */\n displayValue?: string;\n\n /**\n * The type/format of the detected barcode.\n *\n * For formats that are part of the `BarcodeType` union, all platforms emit\n * identical values, so scanning the same barcode yields the same `type` on\n * web, iOS, and Android (e.g. `'qr'`, `'code128'`, `'dataMatrix'`).\n *\n * The type is `BarcodeType | string` rather than `BarcodeType` because a\n * platform detector can occasionally report a format that has no\n * cross-platform union member; in that case the raw platform string is\n * forwarded unchanged instead of being dropped. In practice this is Android's\n * `'unknown'` (ML Kit's `FORMAT_UNKNOWN`) and the equivalent `'unknown'` from\n * the web BarcodeDetector. Narrow against the `BarcodeType` members you care\n * about and treat anything else as an opaque string.\n *\n * Platform-specific notes:\n * - iOS distinguishes `interleaved2of5` from `itf14`, whereas Android and web\n * cannot tell them apart and always report `itf14` for their shared\n * interleaved-2-of-5/ITF detector format.\n * - UPC-A is reported as `'upcA'` on Android and web, but as `'ean13'` on iOS:\n * AVFoundation has no UPC-A metadata type and surfaces UPC-A codes as EAN-13\n * (a UPC-A value is an EAN-13 with a leading `0`). This is a hardware/OS\n * limitation, not a normalization choice.\n * - Codabar is reported as `'codabar'` on all three platforms.\n */\n type: BarcodeType | string;\n\n /** The bounding rectangle of the barcode in the camera frame. */\n boundingRect: BoundingRect;\n}\n\n/**\n * Rectangle defining the boundary of the barcode in the camera frame.\n * Coordinates are given in display/CSS pixels within the webview (display)\n * coordinate space, not normalized values. This lets you position an overlay\n * directly on top of the detected barcode without further scaling.\n *\n * @since 1.0.0\n */\nexport interface BoundingRect {\n /** X-coordinate of the top-left corner */\n x: number;\n /** Y-coordinate of the top-left corner */\n y: number;\n /** Width of the bounding rectangle (should match the actual width of the barcode) */\n width: number;\n /** Height of the bounding rectangle (should match the actual height of the barcode) */\n height: number;\n}\n\n/**\n * Reason why the camera session was interrupted.\n *\n * Mirrors `AVCaptureSession.InterruptionReason` on iOS. Unknown or future\n * reasons fall back to `'unknown'`.\n *\n * @since 3.0.0\n */\nexport type CameraInterruptionReason =\n /** The video device is not available because the app is in the background */\n | 'videoDeviceNotAvailableInBackground'\n /** The audio device is in use by another client (e.g. a phone call) */\n | 'audioDeviceInUseByAnotherClient'\n /** The video device is in use by another client */\n | 'videoDeviceInUseByAnotherClient'\n /** The video device is not available while multiple foreground apps share the screen (iPad Split View) */\n | 'videoDeviceNotAvailableWithMultipleForegroundApps'\n /** The video device is not available due to system pressure (e.g. thermal) */\n | 'videoDeviceNotAvailableDueToSystemPressure'\n /** The interruption reason could not be determined */\n | 'unknown';\n\n/**\n * Data for a camera interruption event.\n *\n * @since 3.0.0\n */\nexport interface CameraInterruptedData {\n /** The reason the camera session was interrupted. */\n reason: CameraInterruptionReason;\n}\n\n/**\n * Data for a camera runtime error event.\n *\n * @since 3.0.0\n */\nexport interface CameraRuntimeErrorData {\n /** A human-readable description of the runtime error. */\n message: string;\n\n /**\n * The underlying platform error code, when available.\n * On iOS this is the `AVError` code.\n */\n code?: number;\n}\n\n/**\n * Stable error codes returned as the `code` property on the error of a\n * rejected plugin call.\n *\n * These codes are part of the plugin's public contract: they are safe to\n * `switch` on and will not change between releases the way human-readable\n * error messages might. Where the same failure class exists across\n * platforms, iOS, Android, and web emit the same code string.\n *\n * Emitted on iOS, Android, and web. One divergence: on web, methods that\n * have no web implementation (e.g. `setFocusPoint()`, `setTorchMode()`)\n * reject via Capacitor's own not-implemented convention instead of one of\n * the codes below - `error.code` is the string `'UNIMPLEMENTED'`, not a\n * `CameraErrorCode`.\n *\n * - 'CAMERA_UNAVAILABLE': No available camera for the requested position.\n * - 'CONFIGURATION_FAILED': Failed to configure the camera session.\n * - 'FRAME_CAPTURE_ERROR': Failed to capture a frame from the camera.\n * - 'INPUT_ADDITION_FAILED': Failed to add an input to the capture session.\n * - 'OUTPUT_ADDITION_FAILED': Failed to add an output to the capture session.\n * - 'PHOTO_OUTPUT_ERROR': An error occurred while capturing a photo.\n * - 'PHOTO_OUTPUT_NOT_CONFIGURED': The photo output has not been configured.\n * - 'SESSION_NOT_RUNNING': The capture session is not currently running. Call `start()` first.\n * - 'SESSION_ALREADY_RUNNING': A camera session is already running. Call `stop()` before starting a new one.\n * - 'UNSUPPORTED_FLASH_MODE': The requested flash mode is not supported by the current camera.\n * - 'TORCH_UNAVAILABLE': Torch is not available on this device or camera position.\n * - 'ZOOM_FACTOR_OUT_OF_RANGE': The requested zoom factor is out of the supported range.\n * - 'FOCUS_NOT_SUPPORTED': The current camera cannot focus or meter at a point (e.g. a fixed-focus camera).\n * - 'PERMISSION_DENIED': Camera or microphone access has been denied.\n * - 'DEVICE_LOCKED': The camera device is currently locked by another process.\n * - 'RECORDING_ALREADY_IN_PROGRESS': A video recording is already in progress.\n * - 'NO_RECORDING_IN_PROGRESS': `stopRecording()` was called but no recording is in progress.\n * - 'AUDIO_DEVICE_UNAVAILABLE': No microphone is available on this device.\n * - 'AUDIO_INPUT_ADDITION_FAILED': Failed to add the microphone input to the capture session.\n * - 'CAPTURE_IN_PROGRESS': A capture is already in progress.\n * - 'CAPTURE_TIMEOUT': Timed out waiting for a camera frame.\n * - 'WEBVIEW_UNAVAILABLE': Could not find the web view to render the camera preview into.\n * - 'INVALID_ARGUMENT': An argument passed to the method call was missing or invalid.\n * - 'IMAGE_COMPRESSION_FAILED': Failed to compress the captured image.\n * - 'PATH_CONVERSION_FAILED': Failed to create a web-accessible path for a captured file.\n * - 'FILE_WRITE_FAILED': Failed to write the captured file to disk.\n * - 'CAPTURE_OUTPUT_MISSING': The capture completed but produced no output data.\n * - 'LIFECYCLE_OWNER_MISSING': (Android only) The WebView's context is not a `LifecycleOwner`, so the camera session cannot be bound.\n * - 'UNKNOWN_ERROR': An unexpected error that does not map to a known camera error (e.g. an underlying OS error).\n *\n * @since 3.0.0\n */\nexport type CameraErrorCode =\n | 'CAMERA_UNAVAILABLE'\n | 'CONFIGURATION_FAILED'\n | 'FRAME_CAPTURE_ERROR'\n | 'INPUT_ADDITION_FAILED'\n | 'OUTPUT_ADDITION_FAILED'\n | 'PHOTO_OUTPUT_ERROR'\n | 'PHOTO_OUTPUT_NOT_CONFIGURED'\n | 'SESSION_NOT_RUNNING'\n | 'SESSION_ALREADY_RUNNING'\n | 'UNSUPPORTED_FLASH_MODE'\n | 'TORCH_UNAVAILABLE'\n | 'ZOOM_FACTOR_OUT_OF_RANGE'\n | 'FOCUS_NOT_SUPPORTED'\n | 'PERMISSION_DENIED'\n | 'DEVICE_LOCKED'\n | 'RECORDING_ALREADY_IN_PROGRESS'\n | 'NO_RECORDING_IN_PROGRESS'\n | 'AUDIO_DEVICE_UNAVAILABLE'\n | 'AUDIO_INPUT_ADDITION_FAILED'\n | 'CAPTURE_IN_PROGRESS'\n | 'CAPTURE_TIMEOUT'\n | 'WEBVIEW_UNAVAILABLE'\n | 'INVALID_ARGUMENT'\n | 'IMAGE_COMPRESSION_FAILED'\n | 'PATH_CONVERSION_FAILED'\n | 'FILE_WRITE_FAILED'\n | 'CAPTURE_OUTPUT_MISSING'\n | 'LIFECYCLE_OWNER_MISSING'\n | 'UNKNOWN_ERROR';\n\n/**\n * Permission types that can be requested.\n * - 'camera': Camera access permission\n * - 'microphone': Microphone access permission (needed for video recording with audio)\n *\n * @since 2.3.0\n */\nexport type CameraPermissionType = 'camera' | 'microphone';\n\n/**\n * Response for the camera and microphone permission status.\n *\n * @since 1.0.0\n */\nexport interface PermissionStatus {\n /** The state of the camera permission */\n camera: PermissionState;\n /** The state of the microphone permission */\n microphone: PermissionState;\n}\n"]}
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -3,33 +3,77 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare function canvasToBase64(canvas: HTMLCanvasElement, quality: number): string;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* The visible region of a video element rendered with `object-fit: cover`,
|
|
7
|
+
* plus the target dimensions a capture of that region should be rasterized at.
|
|
8
|
+
*
|
|
9
|
+
* `source*` describe the crop in the video's intrinsic (source) pixel space.
|
|
10
|
+
* `display*` are the CSS-pixel dimensions of the on-screen preview and define
|
|
11
|
+
* the aspect ratio of the crop. `output*` are the pixel dimensions the capture
|
|
12
|
+
* canvas should use.
|
|
7
13
|
*/
|
|
8
|
-
export
|
|
14
|
+
export interface VisibleArea {
|
|
15
|
+
/** Left edge of the visible crop, in intrinsic video pixels. */
|
|
9
16
|
sourceX: number;
|
|
17
|
+
/** Top edge of the visible crop, in intrinsic video pixels. */
|
|
10
18
|
sourceY: number;
|
|
19
|
+
/** Width of the visible crop, in intrinsic video pixels. */
|
|
11
20
|
sourceWidth: number;
|
|
21
|
+
/** Height of the visible crop, in intrinsic video pixels. */
|
|
12
22
|
sourceHeight: number;
|
|
23
|
+
/** CSS-pixel width of the on-screen preview. */
|
|
13
24
|
displayWidth: number;
|
|
25
|
+
/** CSS-pixel height of the on-screen preview. */
|
|
14
26
|
displayHeight: number;
|
|
15
|
-
|
|
27
|
+
/** Target canvas width for a capture of the visible crop, in device pixels. */
|
|
28
|
+
outputWidth: number;
|
|
29
|
+
/** Target canvas height for a capture of the visible crop, in device pixels. */
|
|
30
|
+
outputHeight: number;
|
|
31
|
+
}
|
|
16
32
|
/**
|
|
17
|
-
*
|
|
33
|
+
* Calculates the visible area of the video based on object-fit: cover.
|
|
34
|
+
*
|
|
35
|
+
* The `output*` dimensions default to the visible crop's intrinsic (source)
|
|
36
|
+
* pixel size, so a capture keeps the full detail the stream delivers for that
|
|
37
|
+
* region instead of being downscaled to CSS pixels.
|
|
18
38
|
*/
|
|
19
|
-
export declare function
|
|
20
|
-
sourceX: number;
|
|
21
|
-
sourceY: number;
|
|
22
|
-
sourceWidth: number;
|
|
23
|
-
sourceHeight: number;
|
|
24
|
-
displayWidth: number;
|
|
25
|
-
displayHeight: number;
|
|
26
|
-
}): void;
|
|
39
|
+
export declare function calculateVisibleArea(video: HTMLVideoElement): VisibleArea;
|
|
27
40
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
41
|
+
* The full video frame as a {@link VisibleArea}: no cropping, rasterized at
|
|
42
|
+
* the stream's intrinsic resolution.
|
|
43
|
+
*
|
|
44
|
+
* Used when the session was started with an explicit `aspectRatio`, where the
|
|
45
|
+
* cross-platform contract is that `capture()` returns the full sensor-ratio
|
|
46
|
+
* frame rather than the on-screen cover-cropped region.
|
|
47
|
+
*/
|
|
48
|
+
export declare function calculateFullFrameArea(video: HTMLVideoElement): VisibleArea;
|
|
49
|
+
/**
|
|
50
|
+
* Further crops a {@link VisibleArea} to account for a CSS `transform: scale()`
|
|
51
|
+
* zoom applied to the video element.
|
|
52
|
+
*
|
|
53
|
+
* The CSS-fallback zoom magnifies the preview about its center, but
|
|
54
|
+
* `getBoundingClientRect()` reports the post-transform size and therefore
|
|
55
|
+
* leaves the crop unchanged. Tightening the source rectangle by `scale`, kept
|
|
56
|
+
* centered, makes a capture match what the user actually sees.
|
|
57
|
+
*
|
|
58
|
+
* A `scale <= 1` (no zoom) returns the area unchanged.
|
|
59
|
+
*/
|
|
60
|
+
export declare function applyCssZoomCrop(area: VisibleArea, scale: number): VisibleArea;
|
|
61
|
+
/**
|
|
62
|
+
* Draws the visible area of the video to the canvas at the crop's target
|
|
63
|
+
* output resolution (see {@link VisibleArea.outputWidth}).
|
|
64
|
+
*/
|
|
65
|
+
export declare function drawVisibleAreaToCanvas(canvas: HTMLCanvasElement, videoElement: HTMLVideoElement, area: VisibleArea): void;
|
|
66
|
+
/**
|
|
67
|
+
* Transforms barcode coordinates from the video source space to display space,
|
|
68
|
+
* accounting for how the video element is scaled into its container.
|
|
69
|
+
*
|
|
70
|
+
* Both `object-fit` modes are a single-scale, centered mapping and share one
|
|
71
|
+
* formula, differing only in which axis scale wins: `'cover'` takes the larger
|
|
72
|
+
* (center-cropped), `'fit'` the smaller (letterboxed).
|
|
30
73
|
*
|
|
31
74
|
* @param barcodeBoundingBox The original barcode bounding box from the detector
|
|
32
75
|
* @param videoElement The video element with the camera stream
|
|
76
|
+
* @param scaleMode Whether the preview uses `cover` or `fit` scaling
|
|
33
77
|
* @returns The transformed bounding box coordinates in display space
|
|
34
78
|
*/
|
|
35
79
|
export declare function transformBarcodeBoundingBox(barcodeBoundingBox: {
|
|
@@ -37,7 +81,7 @@ export declare function transformBarcodeBoundingBox(barcodeBoundingBox: {
|
|
|
37
81
|
y: number;
|
|
38
82
|
width: number;
|
|
39
83
|
height: number;
|
|
40
|
-
}, videoElement: HTMLVideoElement): {
|
|
84
|
+
}, videoElement: HTMLVideoElement, scaleMode?: 'cover' | 'fit'): {
|
|
41
85
|
x: number;
|
|
42
86
|
y: number;
|
|
43
87
|
width: number;
|
package/dist/esm/utils.js
CHANGED
|
@@ -6,7 +6,11 @@ export function canvasToBase64(canvas, quality) {
|
|
|
6
6
|
return dataUrl.split(',')[1];
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Calculates the visible area of the video based on object-fit: cover
|
|
9
|
+
* Calculates the visible area of the video based on object-fit: cover.
|
|
10
|
+
*
|
|
11
|
+
* The `output*` dimensions default to the visible crop's intrinsic (source)
|
|
12
|
+
* pixel size, so a capture keeps the full detail the stream delivers for that
|
|
13
|
+
* region instead of being downscaled to CSS pixels.
|
|
10
14
|
*/
|
|
11
15
|
export function calculateVisibleArea(video) {
|
|
12
16
|
// Get the displayed dimensions of the video element
|
|
@@ -34,6 +38,10 @@ export function calculateVisibleArea(video) {
|
|
|
34
38
|
sourceHeight = videoWidth / displayAspect;
|
|
35
39
|
sourceY = (videoHeight - sourceHeight) / 2;
|
|
36
40
|
}
|
|
41
|
+
// Rasterize the capture at the visible crop's native resolution. Rounding
|
|
42
|
+
// keeps the canvas dimensions integral without distorting the aspect ratio.
|
|
43
|
+
const outputWidth = Math.round(sourceWidth);
|
|
44
|
+
const outputHeight = Math.round(sourceHeight);
|
|
37
45
|
return {
|
|
38
46
|
sourceX,
|
|
39
47
|
sourceY,
|
|
@@ -41,32 +49,82 @@ export function calculateVisibleArea(video) {
|
|
|
41
49
|
sourceHeight,
|
|
42
50
|
displayWidth,
|
|
43
51
|
displayHeight,
|
|
52
|
+
outputWidth,
|
|
53
|
+
outputHeight,
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
56
|
/**
|
|
47
|
-
*
|
|
57
|
+
* The full video frame as a {@link VisibleArea}: no cropping, rasterized at
|
|
58
|
+
* the stream's intrinsic resolution.
|
|
59
|
+
*
|
|
60
|
+
* Used when the session was started with an explicit `aspectRatio`, where the
|
|
61
|
+
* cross-platform contract is that `capture()` returns the full sensor-ratio
|
|
62
|
+
* frame rather than the on-screen cover-cropped region.
|
|
63
|
+
*/
|
|
64
|
+
export function calculateFullFrameArea(video) {
|
|
65
|
+
const videoRect = video.getBoundingClientRect();
|
|
66
|
+
return {
|
|
67
|
+
sourceX: 0,
|
|
68
|
+
sourceY: 0,
|
|
69
|
+
sourceWidth: video.videoWidth,
|
|
70
|
+
sourceHeight: video.videoHeight,
|
|
71
|
+
displayWidth: videoRect.width,
|
|
72
|
+
displayHeight: videoRect.height,
|
|
73
|
+
outputWidth: video.videoWidth,
|
|
74
|
+
outputHeight: video.videoHeight,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Further crops a {@link VisibleArea} to account for a CSS `transform: scale()`
|
|
79
|
+
* zoom applied to the video element.
|
|
80
|
+
*
|
|
81
|
+
* The CSS-fallback zoom magnifies the preview about its center, but
|
|
82
|
+
* `getBoundingClientRect()` reports the post-transform size and therefore
|
|
83
|
+
* leaves the crop unchanged. Tightening the source rectangle by `scale`, kept
|
|
84
|
+
* centered, makes a capture match what the user actually sees.
|
|
85
|
+
*
|
|
86
|
+
* A `scale <= 1` (no zoom) returns the area unchanged.
|
|
87
|
+
*/
|
|
88
|
+
export function applyCssZoomCrop(area, scale) {
|
|
89
|
+
if (!(scale > 1)) {
|
|
90
|
+
return area;
|
|
91
|
+
}
|
|
92
|
+
const sourceWidth = area.sourceWidth / scale;
|
|
93
|
+
const sourceHeight = area.sourceHeight / scale;
|
|
94
|
+
return Object.assign(Object.assign({}, area), { sourceX: area.sourceX + (area.sourceWidth - sourceWidth) / 2, sourceY: area.sourceY + (area.sourceHeight - sourceHeight) / 2, sourceWidth,
|
|
95
|
+
sourceHeight, outputWidth: Math.round(sourceWidth), outputHeight: Math.round(sourceHeight) });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Draws the visible area of the video to the canvas at the crop's target
|
|
99
|
+
* output resolution (see {@link VisibleArea.outputWidth}).
|
|
48
100
|
*/
|
|
49
101
|
export function drawVisibleAreaToCanvas(canvas, videoElement, area) {
|
|
50
|
-
const { sourceX, sourceY, sourceWidth, sourceHeight,
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
canvas.
|
|
102
|
+
const { sourceX, sourceY, sourceWidth, sourceHeight, outputWidth, outputHeight } = area;
|
|
103
|
+
// Sized to the crop's intrinsic pixel size rather than the CSS-pixel preview
|
|
104
|
+
// size, so the JPEG keeps the stream's real detail.
|
|
105
|
+
canvas.width = outputWidth;
|
|
106
|
+
canvas.height = outputHeight;
|
|
54
107
|
const ctx = canvas.getContext('2d', { alpha: false });
|
|
55
108
|
if (!ctx) {
|
|
56
109
|
throw new Error('Could not get canvas context');
|
|
57
110
|
}
|
|
58
111
|
// Draw only the visible portion of the video to match what the user sees
|
|
59
|
-
ctx.drawImage(videoElement, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0,
|
|
112
|
+
ctx.drawImage(videoElement, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, outputWidth, outputHeight);
|
|
60
113
|
}
|
|
61
114
|
/**
|
|
62
|
-
* Transforms barcode coordinates from the video source space to display space
|
|
63
|
-
* accounting for
|
|
115
|
+
* Transforms barcode coordinates from the video source space to display space,
|
|
116
|
+
* accounting for how the video element is scaled into its container.
|
|
117
|
+
*
|
|
118
|
+
* Both `object-fit` modes are a single-scale, centered mapping and share one
|
|
119
|
+
* formula, differing only in which axis scale wins: `'cover'` takes the larger
|
|
120
|
+
* (center-cropped), `'fit'` the smaller (letterboxed).
|
|
64
121
|
*
|
|
65
122
|
* @param barcodeBoundingBox The original barcode bounding box from the detector
|
|
66
123
|
* @param videoElement The video element with the camera stream
|
|
124
|
+
* @param scaleMode Whether the preview uses `cover` or `fit` scaling
|
|
67
125
|
* @returns The transformed bounding box coordinates in display space
|
|
68
126
|
*/
|
|
69
|
-
export function transformBarcodeBoundingBox(barcodeBoundingBox, videoElement) {
|
|
127
|
+
export function transformBarcodeBoundingBox(barcodeBoundingBox, videoElement, scaleMode = 'cover') {
|
|
70
128
|
// Get the video element's displayed dimensions
|
|
71
129
|
const videoRect = videoElement.getBoundingClientRect();
|
|
72
130
|
const displayWidth = videoRect.width;
|
|
@@ -74,35 +132,18 @@ export function transformBarcodeBoundingBox(barcodeBoundingBox, videoElement) {
|
|
|
74
132
|
// Get original video dimensions
|
|
75
133
|
const videoWidth = videoElement.videoWidth;
|
|
76
134
|
const videoHeight = videoElement.videoHeight;
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const scaledVideoWidth = videoWidth * scale;
|
|
85
|
-
const cropX = (scaledVideoWidth - displayWidth) / 2;
|
|
86
|
-
scaledWidth = barcodeBoundingBox.width * scale;
|
|
87
|
-
scaledHeight = barcodeBoundingBox.height * scale;
|
|
88
|
-
scaledX = barcodeBoundingBox.x * scale - cropX;
|
|
89
|
-
scaledY = barcodeBoundingBox.y * scale;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
// Video is taller than display area - width matches, height is centered and cropped
|
|
93
|
-
const scale = displayWidth / videoWidth;
|
|
94
|
-
const scaledVideoHeight = videoHeight * scale;
|
|
95
|
-
const cropY = (scaledVideoHeight - displayHeight) / 2;
|
|
96
|
-
scaledWidth = barcodeBoundingBox.width * scale;
|
|
97
|
-
scaledHeight = barcodeBoundingBox.height * scale;
|
|
98
|
-
scaledX = barcodeBoundingBox.x * scale;
|
|
99
|
-
scaledY = barcodeBoundingBox.y * scale - cropY;
|
|
100
|
-
}
|
|
135
|
+
const scaleX = displayWidth / videoWidth;
|
|
136
|
+
const scaleY = displayHeight / videoHeight;
|
|
137
|
+
const scale = scaleMode === 'fit' ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY);
|
|
138
|
+
// Centering offset of the scaled frame within the container: negative on a
|
|
139
|
+
// cropped axis, positive on a letterboxed one.
|
|
140
|
+
const offsetX = (displayWidth - videoWidth * scale) / 2;
|
|
141
|
+
const offsetY = (displayHeight - videoHeight * scale) / 2;
|
|
101
142
|
return {
|
|
102
|
-
x:
|
|
103
|
-
y:
|
|
104
|
-
width:
|
|
105
|
-
height:
|
|
143
|
+
x: barcodeBoundingBox.x * scale + offsetX,
|
|
144
|
+
y: barcodeBoundingBox.y * scale + offsetY,
|
|
145
|
+
width: barcodeBoundingBox.width * scale,
|
|
146
|
+
height: barcodeBoundingBox.height * scale,
|
|
106
147
|
};
|
|
107
148
|
}
|
|
108
149
|
//# sourceMappingURL=utils.js.map
|
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Converts canvas to base64 string\n */\nexport function canvasToBase64(canvas: HTMLCanvasElement, quality: number): string {\n const dataUrl = canvas.toDataURL('image/jpeg', quality);\n return dataUrl.split(',')[1];\n}\n\n/**\n * Calculates the visible area of the video based on object-fit: cover\n */\nexport function calculateVisibleArea(video: HTMLVideoElement): {\n sourceX: number;\n sourceY: number;\n sourceWidth: number;\n sourceHeight: number;\n displayWidth: number;\n displayHeight: number;\n} {\n // Get the displayed dimensions of the video element\n const videoRect = video.getBoundingClientRect();\n const displayWidth = videoRect.width;\n const displayHeight = videoRect.height;\n\n // Get the intrinsic dimensions of the video\n const videoWidth = video.videoWidth;\n const videoHeight = video.videoHeight;\n\n // Calculate which portion of the video is visible (for object-fit: cover)\n const videoAspect = videoWidth / videoHeight;\n const displayAspect = displayWidth / displayHeight;\n\n let sourceX = 0;\n let sourceY = 0;\n let sourceWidth = videoWidth;\n let sourceHeight = videoHeight;\n\n // If video aspect ratio is greater than display aspect ratio,\n // the video is cropped on the sides\n if (videoAspect > displayAspect) {\n sourceWidth = videoHeight * displayAspect;\n sourceX = (videoWidth - sourceWidth) / 2;\n }\n // Otherwise the video is cropped on the top and bottom\n else {\n sourceHeight = videoWidth / displayAspect;\n sourceY = (videoHeight - sourceHeight) / 2;\n }\n\n return {\n sourceX,\n sourceY,\n sourceWidth,\n sourceHeight,\n displayWidth,\n displayHeight,\n };\n}\n\n/**\n * Draws the visible area of the video to the canvas\n */\nexport function drawVisibleAreaToCanvas(\n canvas: HTMLCanvasElement,\n videoElement: HTMLVideoElement,\n area: {\n sourceX: number;\n sourceY: number;\n sourceWidth: number;\n sourceHeight: number;\n displayWidth: number;\n displayHeight: number;\n },\n): void {\n const { sourceX, sourceY, sourceWidth, sourceHeight, displayWidth, displayHeight } = area;\n\n // Set canvas size to match the displayed dimensions\n canvas.width = displayWidth;\n canvas.height = displayHeight;\n\n const ctx = canvas.getContext('2d', { alpha: false });\n if (!ctx) {\n throw new Error('Could not get canvas context');\n }\n\n // Draw only the visible portion of the video to match what the user sees\n ctx.drawImage(videoElement, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, displayWidth, displayHeight);\n}\n\n/**\n * Transforms barcode coordinates from the video source space to display space\n * accounting for object-fit: cover scaling and cropping.\n *\n * @param barcodeBoundingBox The original barcode bounding box from the detector\n * @param videoElement The video element with the camera stream\n * @returns The transformed bounding box coordinates in display space\n */\nexport function transformBarcodeBoundingBox(\n barcodeBoundingBox: {\n x: number;\n y: number;\n width: number;\n height: number;\n },\n videoElement: HTMLVideoElement,\n): {\n x: number;\n y: number;\n width: number;\n height: number;\n} {\n // Get the video element's displayed dimensions\n const videoRect = videoElement.getBoundingClientRect();\n const displayWidth = videoRect.width;\n const displayHeight = videoRect.height;\n\n // Get original video dimensions\n const videoWidth = videoElement.videoWidth;\n const videoHeight = videoElement.videoHeight;\n\n // Calculate scaling and positioning for object-fit: cover\n const videoAspect = videoWidth / videoHeight;\n const displayAspect = displayWidth / displayHeight;\n\n let scaledX, scaledY, scaledWidth, scaledHeight;\n\n if (videoAspect > displayAspect) {\n // Video is wider than display area - height matches, width is centered and cropped\n const scale = displayHeight / videoHeight;\n const scaledVideoWidth = videoWidth * scale;\n const cropX = (scaledVideoWidth - displayWidth) / 2;\n\n scaledWidth = barcodeBoundingBox.width * scale;\n scaledHeight = barcodeBoundingBox.height * scale;\n scaledX = barcodeBoundingBox.x * scale - cropX;\n scaledY = barcodeBoundingBox.y * scale;\n } else {\n // Video is taller than display area - width matches, height is centered and cropped\n const scale = displayWidth / videoWidth;\n const scaledVideoHeight = videoHeight * scale;\n const cropY = (scaledVideoHeight - displayHeight) / 2;\n\n scaledWidth = barcodeBoundingBox.width * scale;\n scaledHeight = barcodeBoundingBox.height * scale;\n scaledX = barcodeBoundingBox.x * scale;\n scaledY = barcodeBoundingBox.y * scale - cropY;\n }\n\n return {\n x: scaledX,\n y: scaledY,\n width: scaledWidth,\n height: scaledHeight,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAyB,EAAE,OAAe;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC;AA8BD;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAuB;IAC1D,oDAAoD;IACpD,MAAM,SAAS,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAC;IAChD,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC;IACrC,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC;IAEvC,4CAA4C;IAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACpC,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IAEtC,0EAA0E;IAC1E,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,CAAC;IAC7C,MAAM,aAAa,GAAG,YAAY,GAAG,aAAa,CAAC;IAEnD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,WAAW,GAAG,UAAU,CAAC;IAC7B,IAAI,YAAY,GAAG,WAAW,CAAC;IAE/B,8DAA8D;IAC9D,oCAAoC;IACpC,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;QAChC,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC;QAC1C,OAAO,GAAG,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,uDAAuD;SAClD,CAAC;QACJ,YAAY,GAAG,UAAU,GAAG,aAAa,CAAC;QAC1C,OAAO,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAE9C,OAAO;QACL,OAAO;QACP,OAAO;QACP,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,WAAW;QACX,YAAY;KACb,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAuB;IAC5D,MAAM,SAAS,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAC;IAEhD,OAAO;QACL,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,KAAK,CAAC,UAAU;QAC7B,YAAY,EAAE,KAAK,CAAC,WAAW;QAC/B,YAAY,EAAE,SAAS,CAAC,KAAK;QAC7B,aAAa,EAAE,SAAS,CAAC,MAAM;QAC/B,WAAW,EAAE,KAAK,CAAC,UAAU;QAC7B,YAAY,EAAE,KAAK,CAAC,WAAW;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAiB,EAAE,KAAa;IAC/D,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;IAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAE/C,uCACK,IAAI,KACP,OAAO,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,EAC5D,OAAO,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,EAC9D,WAAW;QACX,YAAY,EACZ,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,EACpC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IACtC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAyB,EACzB,YAA8B,EAC9B,IAAiB;IAEjB,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAExF,6EAA6E;IAC7E,oDAAoD;IACpD,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC;IAC3B,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC;IAE7B,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,yEAAyE;IACzE,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;AAC5G,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,2BAA2B,CACzC,kBAKC,EACD,YAA8B,EAC9B,YAA6B,OAAO;IAOpC,+CAA+C;IAC/C,MAAM,SAAS,GAAG,YAAY,CAAC,qBAAqB,EAAE,CAAC;IACvD,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC;IACrC,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC;IAEvC,gCAAgC;IAChC,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;IAC3C,MAAM,WAAW,GAAG,YAAY,CAAC,WAAW,CAAC;IAE7C,MAAM,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;IACzC,MAAM,MAAM,GAAG,aAAa,GAAG,WAAW,CAAC;IAC3C,MAAM,KAAK,GAAG,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExF,2EAA2E;IAC3E,+CAA+C;IAC/C,MAAM,OAAO,GAAG,CAAC,YAAY,GAAG,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,CAAC,aAAa,GAAG,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAE1D,OAAO;QACL,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAG,KAAK,GAAG,OAAO;QACzC,CAAC,EAAE,kBAAkB,CAAC,CAAC,GAAG,KAAK,GAAG,OAAO;QACzC,KAAK,EAAE,kBAAkB,CAAC,KAAK,GAAG,KAAK;QACvC,MAAM,EAAE,kBAAkB,CAAC,MAAM,GAAG,KAAK;KAC1C,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Converts canvas to base64 string\n */\nexport function canvasToBase64(canvas: HTMLCanvasElement, quality: number): string {\n const dataUrl = canvas.toDataURL('image/jpeg', quality);\n return dataUrl.split(',')[1];\n}\n\n/**\n * The visible region of a video element rendered with `object-fit: cover`,\n * plus the target dimensions a capture of that region should be rasterized at.\n *\n * `source*` describe the crop in the video's intrinsic (source) pixel space.\n * `display*` are the CSS-pixel dimensions of the on-screen preview and define\n * the aspect ratio of the crop. `output*` are the pixel dimensions the capture\n * canvas should use.\n */\nexport interface VisibleArea {\n /** Left edge of the visible crop, in intrinsic video pixels. */\n sourceX: number;\n /** Top edge of the visible crop, in intrinsic video pixels. */\n sourceY: number;\n /** Width of the visible crop, in intrinsic video pixels. */\n sourceWidth: number;\n /** Height of the visible crop, in intrinsic video pixels. */\n sourceHeight: number;\n /** CSS-pixel width of the on-screen preview. */\n displayWidth: number;\n /** CSS-pixel height of the on-screen preview. */\n displayHeight: number;\n /** Target canvas width for a capture of the visible crop, in device pixels. */\n outputWidth: number;\n /** Target canvas height for a capture of the visible crop, in device pixels. */\n outputHeight: number;\n}\n\n/**\n * Calculates the visible area of the video based on object-fit: cover.\n *\n * The `output*` dimensions default to the visible crop's intrinsic (source)\n * pixel size, so a capture keeps the full detail the stream delivers for that\n * region instead of being downscaled to CSS pixels.\n */\nexport function calculateVisibleArea(video: HTMLVideoElement): VisibleArea {\n // Get the displayed dimensions of the video element\n const videoRect = video.getBoundingClientRect();\n const displayWidth = videoRect.width;\n const displayHeight = videoRect.height;\n\n // Get the intrinsic dimensions of the video\n const videoWidth = video.videoWidth;\n const videoHeight = video.videoHeight;\n\n // Calculate which portion of the video is visible (for object-fit: cover)\n const videoAspect = videoWidth / videoHeight;\n const displayAspect = displayWidth / displayHeight;\n\n let sourceX = 0;\n let sourceY = 0;\n let sourceWidth = videoWidth;\n let sourceHeight = videoHeight;\n\n // If video aspect ratio is greater than display aspect ratio,\n // the video is cropped on the sides\n if (videoAspect > displayAspect) {\n sourceWidth = videoHeight * displayAspect;\n sourceX = (videoWidth - sourceWidth) / 2;\n }\n // Otherwise the video is cropped on the top and bottom\n else {\n sourceHeight = videoWidth / displayAspect;\n sourceY = (videoHeight - sourceHeight) / 2;\n }\n\n // Rasterize the capture at the visible crop's native resolution. Rounding\n // keeps the canvas dimensions integral without distorting the aspect ratio.\n const outputWidth = Math.round(sourceWidth);\n const outputHeight = Math.round(sourceHeight);\n\n return {\n sourceX,\n sourceY,\n sourceWidth,\n sourceHeight,\n displayWidth,\n displayHeight,\n outputWidth,\n outputHeight,\n };\n}\n\n/**\n * The full video frame as a {@link VisibleArea}: no cropping, rasterized at\n * the stream's intrinsic resolution.\n *\n * Used when the session was started with an explicit `aspectRatio`, where the\n * cross-platform contract is that `capture()` returns the full sensor-ratio\n * frame rather than the on-screen cover-cropped region.\n */\nexport function calculateFullFrameArea(video: HTMLVideoElement): VisibleArea {\n const videoRect = video.getBoundingClientRect();\n\n return {\n sourceX: 0,\n sourceY: 0,\n sourceWidth: video.videoWidth,\n sourceHeight: video.videoHeight,\n displayWidth: videoRect.width,\n displayHeight: videoRect.height,\n outputWidth: video.videoWidth,\n outputHeight: video.videoHeight,\n };\n}\n\n/**\n * Further crops a {@link VisibleArea} to account for a CSS `transform: scale()`\n * zoom applied to the video element.\n *\n * The CSS-fallback zoom magnifies the preview about its center, but\n * `getBoundingClientRect()` reports the post-transform size and therefore\n * leaves the crop unchanged. Tightening the source rectangle by `scale`, kept\n * centered, makes a capture match what the user actually sees.\n *\n * A `scale <= 1` (no zoom) returns the area unchanged.\n */\nexport function applyCssZoomCrop(area: VisibleArea, scale: number): VisibleArea {\n if (!(scale > 1)) {\n return area;\n }\n\n const sourceWidth = area.sourceWidth / scale;\n const sourceHeight = area.sourceHeight / scale;\n\n return {\n ...area,\n sourceX: area.sourceX + (area.sourceWidth - sourceWidth) / 2,\n sourceY: area.sourceY + (area.sourceHeight - sourceHeight) / 2,\n sourceWidth,\n sourceHeight,\n outputWidth: Math.round(sourceWidth),\n outputHeight: Math.round(sourceHeight),\n };\n}\n\n/**\n * Draws the visible area of the video to the canvas at the crop's target\n * output resolution (see {@link VisibleArea.outputWidth}).\n */\nexport function drawVisibleAreaToCanvas(\n canvas: HTMLCanvasElement,\n videoElement: HTMLVideoElement,\n area: VisibleArea,\n): void {\n const { sourceX, sourceY, sourceWidth, sourceHeight, outputWidth, outputHeight } = area;\n\n // Sized to the crop's intrinsic pixel size rather than the CSS-pixel preview\n // size, so the JPEG keeps the stream's real detail.\n canvas.width = outputWidth;\n canvas.height = outputHeight;\n\n const ctx = canvas.getContext('2d', { alpha: false });\n if (!ctx) {\n throw new Error('Could not get canvas context');\n }\n\n // Draw only the visible portion of the video to match what the user sees\n ctx.drawImage(videoElement, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, outputWidth, outputHeight);\n}\n\n/**\n * Transforms barcode coordinates from the video source space to display space,\n * accounting for how the video element is scaled into its container.\n *\n * Both `object-fit` modes are a single-scale, centered mapping and share one\n * formula, differing only in which axis scale wins: `'cover'` takes the larger\n * (center-cropped), `'fit'` the smaller (letterboxed).\n *\n * @param barcodeBoundingBox The original barcode bounding box from the detector\n * @param videoElement The video element with the camera stream\n * @param scaleMode Whether the preview uses `cover` or `fit` scaling\n * @returns The transformed bounding box coordinates in display space\n */\nexport function transformBarcodeBoundingBox(\n barcodeBoundingBox: {\n x: number;\n y: number;\n width: number;\n height: number;\n },\n videoElement: HTMLVideoElement,\n scaleMode: 'cover' | 'fit' = 'cover',\n): {\n x: number;\n y: number;\n width: number;\n height: number;\n} {\n // Get the video element's displayed dimensions\n const videoRect = videoElement.getBoundingClientRect();\n const displayWidth = videoRect.width;\n const displayHeight = videoRect.height;\n\n // Get original video dimensions\n const videoWidth = videoElement.videoWidth;\n const videoHeight = videoElement.videoHeight;\n\n const scaleX = displayWidth / videoWidth;\n const scaleY = displayHeight / videoHeight;\n const scale = scaleMode === 'fit' ? Math.min(scaleX, scaleY) : Math.max(scaleX, scaleY);\n\n // Centering offset of the scaled frame within the container: negative on a\n // cropped axis, positive on a letterboxed one.\n const offsetX = (displayWidth - videoWidth * scale) / 2;\n const offsetY = (displayHeight - videoHeight * scale) / 2;\n\n return {\n x: barcodeBoundingBox.x * scale + offsetX,\n y: barcodeBoundingBox.y * scale + offsetY,\n width: barcodeBoundingBox.width * scale,\n height: barcodeBoundingBox.height * scale,\n };\n}\n"]}
|