diff --git a/apps/simple-camera/__tests__/visioncamera.session.harness.ts b/apps/simple-camera/__tests__/visioncamera.session.harness.ts
index 65e32ded8b..5c6612fd96 100644
--- a/apps/simple-camera/__tests__/visioncamera.session.harness.ts
+++ b/apps/simple-camera/__tests__/visioncamera.session.harness.ts
@@ -5,7 +5,10 @@ import {
it,
waitUntil,
} from 'react-native-harness'
-import type { CameraDeviceFactory } from 'react-native-vision-camera'
+import type {
+ CameraDeviceFactory,
+ TargetCameraPosition,
+} from 'react-native-vision-camera'
import { CommonResolutions, VisionCamera } from 'react-native-vision-camera'
import { provider as workletsProvider } from 'react-native-vision-camera-worklets'
import { scheduleOnRN } from 'react-native-worklets'
@@ -71,6 +74,50 @@ describe('VisionCamera - Session', () => {
}
})
+ it('configures a session directly from each target camera position', async () => {
+ const positions: TargetCameraPosition[] = ['back', 'front', 'external']
+ const session = await VisionCamera.createCameraSession(false)
+ const previewOutput = VisionCamera.createPreviewOutput()
+ const photoOutput = VisionCamera.createPhotoOutput({
+ containerFormat: 'native',
+ quality: 1,
+ qualityPrioritization: 'balanced',
+ targetResolution: CommonResolutions.HD_4_3,
+ })
+
+ try {
+ for (const position of positions) {
+ const hasDeviceAtPosition = factory.cameraDevices.some(
+ (device) => device.position === position,
+ )
+ const configurePromise = session.configure([
+ {
+ input: position,
+ outputs: [
+ {
+ output: previewOutput,
+ mirrorMode: position === 'front' ? 'on' : 'auto',
+ },
+ { output: photoOutput, mirrorMode: 'auto' },
+ ],
+ constraints: [],
+ },
+ ])
+
+ if (!hasDeviceAtPosition) {
+ await expect(configurePromise).rejects.toThrow()
+ continue
+ }
+
+ const controllers = await configurePromise
+ expect(controllers).toHaveLength(1)
+ expect(controllers[0]?.device.position).toBe(position)
+ }
+ } finally {
+ await session.stop()
+ }
+ })
+
it('fires onStarted/onStopped exactly once per lifecycle', async () => {
const device = factory.getDefaultCamera('back')
expect(device).toBeDefined()
diff --git a/docs/content/docs/camera-extensions.mdx b/docs/content/docs/camera-extensions.mdx
index b989ee2e59..9bdfc9496f 100644
--- a/docs/content/docs/camera-extensions.mdx
+++ b/docs/content/docs/camera-extensions.mdx
@@ -10,7 +10,7 @@ Some [`CameraDevice`](/api/react-native-vision-camera/hybrid-objects/CameraDevic
### Getting available Extensions
-To get all available [`CameraExtension`](/api/react-native-vision-camera/hybrid-objects/CameraExtension)s, use [`getSupportedExtensions(...)`](/api/react-native-vision-camera/functions/getSupportedExtensions):
+To get all available [`CameraExtension`](/api/react-native-vision-camera/hybrid-objects/CameraExtension)s, use [`useCameraDeviceExtensions(...)`](/api/react-native-vision-camera/functions/useCameraDeviceExtensions) or [`CameraDeviceFactory.getSupportedExtensions(...)`](/api/react-native-vision-camera/hybrid-objects/CameraDeviceFactory#getsupportedextensions):
@@ -22,7 +22,8 @@ const extensions = useCameraDeviceExtensions(device)
```ts
const device = ...
-const extensions = await getSupportedExtensions(device)
+const deviceFactory = await VisionCamera.createDeviceFactory()
+const extensions = await deviceFactory.getSupportedExtensions(device)
```
@@ -71,7 +72,8 @@ function App() {
```tsx
const device = ...
const session = ...
-const extensions = await getSupportedExtensions(device)
+const deviceFactory = await VisionCamera.createDeviceFactory()
+const extensions = await deviceFactory.getSupportedExtensions(device)
const extension = extensions.find((e) => e.type === 'night')
const controllers = await session.configure([
diff --git a/docs/content/docs/camera-outputs.mdx b/docs/content/docs/camera-outputs.mdx
index f35565076a..02c31c6dce 100644
--- a/docs/content/docs/camera-outputs.mdx
+++ b/docs/content/docs/camera-outputs.mdx
@@ -55,13 +55,12 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
const photoOutput = ...
const videoOutput = ...
await session.configure([
{
- input: device,
+ input: 'back',
// [!code ++]
outputs: [
{ output: photoOutput, mirrorMode: 'auto' },
@@ -154,12 +153,11 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
const output = ...
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
{ output: output, mirrorMode: 'auto' }
],
diff --git a/docs/content/docs/constraints.mdx b/docs/content/docs/constraints.mdx
index 2d78532008..8f9629553a 100644
--- a/docs/content/docs/constraints.mdx
+++ b/docs/content/docs/constraints.mdx
@@ -61,11 +61,10 @@ const session = await VisionCamera.createCameraSession(false)
const videoOutput = createVideoOutput({
targetResolution: CommonResolutions.UHD_16_9
})
-const device = await getDefaultCameraDevice('back')
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [videoOutput],
// [!code ++:4]
constraints: [
@@ -136,11 +135,10 @@ const session = await VisionCamera.createCameraSession(false)
const videoOutput = createVideoOutput({
targetResolution: CommonResolutions.UHD_16_9
})
-const device = await getDefaultCameraDevice('back')
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [videoOutput],
// [!code ++:4]
constraints: [
@@ -217,11 +215,10 @@ const session = await VisionCamera.createCameraSession(false)
const videoOutput = createVideoOutput({
targetResolution: CommonResolutions.UHD_16_9
})
-const device = await getDefaultCameraDevice('back')
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [videoOutput],
// [!code ++:3]
constraints: [
diff --git a/docs/content/docs/depth-output.mdx b/docs/content/docs/depth-output.mdx
index a284af6e27..4a6703736d 100644
--- a/docs/content/docs/depth-output.mdx
+++ b/docs/content/docs/depth-output.mdx
@@ -62,7 +62,6 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
// [!code ++:9]
const depthOutput = VisionCamera.createDepthFrameOutput({ /* options */ })
const workletRuntime = createWorkletRuntimeForThread(depthOutput.thread)
@@ -76,7 +75,7 @@ scheduleOnRuntime(workletRuntime, () => {
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
// [!code ++]
{ output: depthOutput, mirrorMode: 'auto' }
diff --git a/docs/content/docs/devices.mdx b/docs/content/docs/devices.mdx
index 6b193bfbae..c02fe505db 100644
--- a/docs/content/docs/devices.mdx
+++ b/docs/content/docs/devices.mdx
@@ -17,7 +17,8 @@ const device = useCameraDevice("back")
```ts
-const device = await getDefaultCameraDevice("back")
+const deviceFactory = await VisionCamera.createDeviceFactory()
+const device = deviceFactory.getDefaultCamera("back")
```
@@ -79,8 +80,8 @@ const device = useCameraDevice("back", {
```ts
-const devices = getAllCameraDevices()
-const device = getCameraDevice(devices, "back", {
+const deviceFactory = await VisionCamera.createDeviceFactory()
+const device = getCameraDevice(deviceFactory, "back", {
physicalDevices: ['ultra-wide-angle', 'wide-angle', 'telephoto']
})
```
@@ -111,8 +112,9 @@ const devices = useCameraDevices()
```ts
-let devices = getAllCameraDevices()
-addOnCameraDevicesChangedListener((d) => {
+const deviceFactory = await VisionCamera.createDeviceFactory()
+let devices = deviceFactory.cameraDevices
+deviceFactory.addOnCameraDevicesChangedListener((d) => {
devices = d
})
```
@@ -134,14 +136,14 @@ As an external Camera is plugged in or plugged out from the device, the [`useCam
```ts
-const devices = getAllCameraDevices()
-let device = getCameraDevice(devices, "external")
-addOnCameraDevicesChangedListener((d) => {
- device = getCameraDevice(d, "external")
+const deviceFactory = await VisionCamera.createDeviceFactory()
+let device = getCameraDevice(deviceFactory, "external")
+deviceFactory.addOnCameraDevicesChangedListener(() => {
+ device = getCameraDevice(deviceFactory, "external")
})
```
-As an external Camera is plugged in or plugged out from the device, the [`addOnCameraDevicesChangedListener(...)`](/api/react-native-vision-camera/functions/addOnCameraDevicesChangedListener) listener fires and you can call [`getCameraDevice(...)`](/api/react-native-vision-camera/functions/getCameraDevice) again to possibly detect new devices, or avoid using removed devices.
+As an external Camera is plugged in or plugged out from the device, the [`CameraDeviceFactory.addOnCameraDevicesChangedListener(...)`](/api/react-native-vision-camera/hybrid-objects/CameraDeviceFactory#addoncameradeviceschangedlistener) listener fires and you can call [`getCameraDevice(...)`](/api/react-native-vision-camera/functions/getCameraDevice) again to possibly detect new devices, or avoid using removed devices.
@@ -182,7 +184,7 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
+const device = ...
await session.configure([
{
// [!code ++]
diff --git a/docs/content/docs/fps.mdx b/docs/content/docs/fps.mdx
index 2077d6d642..a75633da7e 100644
--- a/docs/content/docs/fps.mdx
+++ b/docs/content/docs/fps.mdx
@@ -64,10 +64,9 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
await session.configure([
{
- input: device,
+ input: 'back',
constraints: [
// [!code ++]
{ fps: 60 }
diff --git a/docs/content/docs/frame-output.mdx b/docs/content/docs/frame-output.mdx
index b0a51d1d6f..dde5197e0c 100644
--- a/docs/content/docs/frame-output.mdx
+++ b/docs/content/docs/frame-output.mdx
@@ -65,7 +65,6 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
// [!code ++:9]
const frameOutput = VisionCamera.createFrameOutput({ /* options */ })
const workletRuntime = createWorkletRuntimeForThread(frameOutput.thread)
@@ -79,7 +78,7 @@ scheduleOnRuntime(workletRuntime, () => {
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
// [!code ++]
{ output: frameOutput, mirrorMode: 'auto' }
diff --git a/docs/content/docs/low-light-boost.mdx b/docs/content/docs/low-light-boost.mdx
index 35570b7b4c..ff0cfd5991 100644
--- a/docs/content/docs/low-light-boost.mdx
+++ b/docs/content/docs/low-light-boost.mdx
@@ -117,7 +117,8 @@ function App() {
```tsx
const device = ...
const session = ...
-const extensions = await getSupportedExtensions(device)
+const deviceFactory = await VisionCamera.createDeviceFactory()
+const extensions = await deviceFactory.getSupportedExtensions(device)
const extension = extensions.find((e) => e.type === 'night')
const controllers = await session.configure([
diff --git a/docs/content/docs/object-output.mdx b/docs/content/docs/object-output.mdx
index eebb403412..c7f30a6689 100644
--- a/docs/content/docs/object-output.mdx
+++ b/docs/content/docs/object-output.mdx
@@ -59,7 +59,6 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
// [!code ++:6]
const objectOutput = VisionCamera.createObjectOutput({
enabledObjectTypes: ['qr']
@@ -70,7 +69,7 @@ objectOutput.setOnObjectsScannedCallback((objects) => {
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
// [!code ++]
{ output: objectOutput, mirrorMode: 'auto' }
diff --git a/docs/content/docs/performance.mdx b/docs/content/docs/performance.mdx
index ab2aa8b30d..ec5da6bd6e 100644
--- a/docs/content/docs/performance.mdx
+++ b/docs/content/docs/performance.mdx
@@ -29,11 +29,11 @@ const slowerDevice = useCameraDevice('back', {
```ts
-const devices = getAllCameraDevices()
-const fasterDevice = getCameraDevice(devices, 'back', {
+const deviceFactory = await VisionCamera.createDeviceFactory()
+const fasterDevice = getCameraDevice(deviceFactory, 'back', {
physicalDevices: ['wide-angle-camera'],
})
-const slowerDevice = getCameraDevice(devices, 'back', {
+const slowerDevice = getCameraDevice(deviceFactory, 'back', {
physicalDevices: ['ultra-wide-angle-camera', 'wide-angle-camera', 'telephoto-camera'],
})
```
diff --git a/docs/content/docs/photo-output.mdx b/docs/content/docs/photo-output.mdx
index 76990b0b66..bde7128f86 100644
--- a/docs/content/docs/photo-output.mdx
+++ b/docs/content/docs/photo-output.mdx
@@ -48,13 +48,12 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
// [!code ++]
const photoOutput = VisionCamera.createPhotoOutput({ /* options */ })
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
// [!code ++]
{ output: photoOutput, mirrorMode: 'auto' }
diff --git a/docs/content/docs/preview-output.mdx b/docs/content/docs/preview-output.mdx
index 060ee51535..a8f43b2cb7 100644
--- a/docs/content/docs/preview-output.mdx
+++ b/docs/content/docs/preview-output.mdx
@@ -33,13 +33,12 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
// [!code ++]
const previewOutput = VisionCamera.createPreviewOutput()
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
// [!code ++]
{ output: previewOutput, mirrorMode: 'auto' }
diff --git a/docs/content/docs/video-output.mdx b/docs/content/docs/video-output.mdx
index 74d6e0cf3c..254d558d5d 100644
--- a/docs/content/docs/video-output.mdx
+++ b/docs/content/docs/video-output.mdx
@@ -48,13 +48,12 @@ function App() {
```tsx
const session = await VisionCamera.createCameraSession(false)
-const device = await getDefaultCameraDevice('back')
// [!code ++]
const videoOutput = VisionCamera.createVideoOutput({ /* options */ })
await session.configure([
{
- input: device,
+ input: 'back',
outputs: [
// [!code ++]
{ output: videoOutput, mirrorMode: 'auto' }
diff --git a/packages/react-native-vision-camera-barcode-scanner/ios/HybridBarcodeScannerOutput.swift b/packages/react-native-vision-camera-barcode-scanner/ios/HybridBarcodeScannerOutput.swift
index c817ba031b..bb243ef322 100644
--- a/packages/react-native-vision-camera-barcode-scanner/ios/HybridBarcodeScannerOutput.swift
+++ b/packages/react-native-vision-camera-barcode-scanner/ios/HybridBarcodeScannerOutput.swift
@@ -84,7 +84,7 @@ final class HybridBarcodeScannerOutput: HybridCameraOutputSpec, NativeCameraOutp
}
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
guard let connection = self.output.connection(with: .video) else {
return
}
diff --git a/packages/react-native-vision-camera-location/ios/HybridLocationManager.swift b/packages/react-native-vision-camera-location/ios/HybridLocationManager.swift
index 038f6d0bb8..9a5085cd7f 100644
--- a/packages/react-native-vision-camera-location/ios/HybridLocationManager.swift
+++ b/packages/react-native-vision-camera-location/ios/HybridLocationManager.swift
@@ -11,7 +11,6 @@ import VisionCamera
final class HybridLocationManager: HybridLocationManagerSpec {
private let manager = CLLocationManager()
private let delegate = LocationDelegate()
- private var permissionPromises: [Promise] = []
var lastKnownLocation: (any HybridLocationSpec)? {
guard let location = manager.location else {
return nil
diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraDeviceFactory.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraDeviceFactory.kt
index f398d4c7c6..faa0b52dcb 100644
--- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraDeviceFactory.kt
+++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraDeviceFactory.kt
@@ -15,6 +15,7 @@ import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.edit
import com.facebook.react.bridge.ReactApplicationContext
import com.margelo.nitro.NitroModules
+import com.margelo.nitro.camera.extensions.getDefaultCamera
import com.margelo.nitro.camera.extensions.mapToArray
import com.margelo.nitro.camera.hybrids.inputs.HybridCameraDevice
import com.margelo.nitro.camera.hybrids.inputs.HybridCameraExtension
@@ -112,18 +113,8 @@ class HybridCameraDeviceFactory(
@OptIn(ExperimentalLensFacing::class)
override fun getDefaultCamera(position: TargetCameraPosition): HybridCameraDeviceSpec? {
- val selector =
- when (position) {
- TargetCameraPosition.FRONT -> CameraSelector.DEFAULT_FRONT_CAMERA
- TargetCameraPosition.BACK -> CameraSelector.DEFAULT_BACK_CAMERA
- TargetCameraPosition.EXTERNAL ->
- CameraSelector
- .Builder()
- .requireLensFacing(CameraSelector.LENS_FACING_EXTERNAL)
- .build()
- }
try {
- val defaultCamera = cameraProvider.getCameraInfo(selector)
+ val defaultCamera = cameraProvider.getDefaultCamera(position)
return HybridCameraDevice(defaultCamera)
} catch (e: Throwable) {
Log.e(TAG, "No default ${position.name} Camera found!", e)
diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraFactory.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraFactory.kt
index d707795790..c67339c9f4 100644
--- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraFactory.kt
+++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/HybridCameraFactory.kt
@@ -78,7 +78,7 @@ class VisionCamera : HybridCameraFactorySpec() {
device as? NativeCameraDevice
?: throw Error("The given `device` was not of type `NativeCameraDevice`!")
val config = ConstraintResolver.resolveConstraints(device.cameraInfo, outputConfigurations, constraints)
- return@async HybridCameraSessionConfig(device.cameraInfo, config.sessionConfig, config.resolvedConfig)
+ return@async HybridCameraSessionConfig(config.sessionConfig, config.resolvedConfig)
}
}
diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraSessionConnection+getCameraInfo.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraSessionConnection+getCameraInfo.kt
index 879fd1a0c6..34058b4fd5 100644
--- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraSessionConnection+getCameraInfo.kt
+++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/CameraSessionConnection+getCameraInfo.kt
@@ -1,12 +1,24 @@
package com.margelo.nitro.camera.extensions
import androidx.camera.core.CameraInfo
+import androidx.camera.lifecycle.ProcessCameraProvider
import com.margelo.nitro.camera.CameraSessionConnection
import com.margelo.nitro.camera.public.NativeCameraDevice
-fun CameraSessionConnection.getCameraInfo(): CameraInfo {
- val cameraDevice =
- input as? NativeCameraDevice
- ?: throw Error("CameraDevice $input is not of type `NativeCameraDevice`!")
- return cameraDevice.cameraInfo
+fun CameraSessionConnection.getCameraInfo(provider: ProcessCameraProvider): CameraInfo {
+ val cameraInfo =
+ input.match(
+ { deviceSpec ->
+ // unwrap CameraInfo
+ val device =
+ deviceSpec as? NativeCameraDevice
+ ?: throw Error("CameraDevice $input is not of type `NativeCameraDevice`!")
+ return@match device.cameraInfo
+ },
+ { position ->
+ // "back" | "front" | "external" -> CameraInfo
+ return@match provider.getDefaultCamera(position)
+ },
+ )
+ return cameraInfo
}
diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ProcessCameraProvider+getDefaultCamera.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ProcessCameraProvider+getDefaultCamera.kt
new file mode 100644
index 0000000000..9213755cb4
--- /dev/null
+++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/extensions/ProcessCameraProvider+getDefaultCamera.kt
@@ -0,0 +1,23 @@
+package com.margelo.nitro.camera.extensions
+
+import androidx.annotation.OptIn
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ExperimentalLensFacing
+import androidx.camera.lifecycle.ProcessCameraProvider
+import com.margelo.nitro.camera.TargetCameraPosition
+
+@OptIn(ExperimentalLensFacing::class)
+fun ProcessCameraProvider.getDefaultCamera(position: TargetCameraPosition): CameraInfo {
+ val selector =
+ when (position) {
+ TargetCameraPosition.FRONT -> CameraSelector.DEFAULT_FRONT_CAMERA
+ TargetCameraPosition.BACK -> CameraSelector.DEFAULT_BACK_CAMERA
+ TargetCameraPosition.EXTERNAL ->
+ CameraSelector
+ .Builder()
+ .requireLensFacing(CameraSelector.LENS_FACING_EXTERNAL)
+ .build()
+ }
+ return getCameraInfo(selector)
+}
diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSession.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSession.kt
index 042dfe16bd..ab21e76c67 100644
--- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSession.kt
+++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSession.kt
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.util.Log
import androidx.annotation.UiThread
import androidx.camera.core.Camera
-import androidx.camera.core.CameraState
import androidx.camera.core.ConcurrentCamera
import androidx.camera.core.UseCaseGroup
import androidx.camera.lifecycle.ProcessCameraProvider
@@ -50,7 +49,6 @@ class HybridCameraSession(
private var onErrorListeners = arrayListOf<(Throwable) -> Unit>()
private var onInterruptionStartedListeners = arrayListOf<(InterruptionReason) -> Unit>()
private var onInterruptionEndedListeners = arrayListOf<() -> Unit>()
- private var currentCameraState = CameraState.Type.CLOSED
@SuppressLint("RestrictedApi")
override fun configure(
@@ -78,14 +76,14 @@ class HybridCameraSession(
1 -> {
// Single Camera Session
val connection = connections.single()
- val cameraInfo = connection.getCameraInfo()
+ val cameraInfo = connection.getCameraInfo(cameraProvider)
val outputConfigurations = connection.outputs
val config = ConstraintResolver.resolveConstraints(cameraInfo, outputConfigurations, connection.constraints)
Log.i(TAG, "Binding use-cases: ${config.sessionConfig.useCases}")
if (connection.onSessionConfigSelected != null) {
// Notify JS callback that we resolved the constraints to a specific `config`
- val hybridConfig = HybridCameraSessionConfig(cameraInfo, config.sessionConfig, config.resolvedConfig)
+ val hybridConfig = HybridCameraSessionConfig(config.sessionConfig, config.resolvedConfig)
connection.onSessionConfigSelected(hybridConfig)
}
@@ -105,7 +103,7 @@ class HybridCameraSession(
val allPreparedUseCases = mutableListOf()
val configs =
connections.map { connection ->
- val cameraInfo = connection.getCameraInfo()
+ val cameraInfo = connection.getCameraInfo(cameraProvider)
val outputs =
connection.outputs.map {
it.output as? NativeCameraOutput
diff --git a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSessionConfig.kt b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSessionConfig.kt
index 05233485c4..24a192e6b2 100644
--- a/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSessionConfig.kt
+++ b/packages/react-native-vision-camera/android/src/main/java/com/margelo/nitro/camera/hybrids/HybridCameraSessionConfig.kt
@@ -1,6 +1,5 @@
package com.margelo.nitro.camera.hybrids
-import androidx.camera.core.CameraInfo
import androidx.camera.core.SessionConfig
import com.margelo.nitro.camera.AutoFocusSystem
import com.margelo.nitro.camera.HybridCameraSessionConfigSpec
@@ -11,7 +10,6 @@ import com.margelo.nitro.camera.public.NativeCameraOutput
import com.margelo.nitro.camera.public.NativeCameraSessionConfig
class HybridCameraSessionConfig(
- private val cameraInfo: CameraInfo,
override val sessionConfig: SessionConfig,
private val resolvedConfig: NativeCameraOutput.Config,
) : HybridCameraSessionConfigSpec(),
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+init.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+init.swift
index 6853723623..c3fe69b4b3 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+init.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+init.swift
@@ -9,7 +9,7 @@ import Foundation
import NitroModules
extension AVCaptureConnection {
- convenience init(input: AVCaptureDeviceInput, output: any HybridCameraOutputSpec) throws {
+ convenience init(input: AVCaptureDeviceInput, output: ResolvedCameraSessionConnection.Output) throws {
let mediaType = output.mediaType.toAVMediaType()
let targetPorts = input.ports.filter { $0.mediaType == mediaType }
guard !targetPorts.isEmpty else {
@@ -20,19 +20,13 @@ extension AVCaptureConnection {
}
switch output {
- case let hybridOutput as any NativeCameraOutput:
+ case .output(let output):
// a) It's a normal AVCaptureSessionOutput - connect it to all its desired ports
- self.init(inputPorts: targetPorts, output: hybridOutput.output)
- case let hybridPreview as any NativePreviewViewOutput:
+ self.init(inputPorts: targetPorts, output: output.output)
+ case .preview(let preview):
// b) It's a preview AVCapturePreviewLayer
let port = targetPorts.first!
- self.init(inputPort: port, videoPreviewLayer: hybridPreview.previewLayer)
- default:
- // c) It's a bird? It's a plane? no idea
- throw RuntimeError.error(
- withMessage:
- "Connection output \"\(output)\" is not of type `NativeCameraOutput` or `NativePreviewViewOutput`!"
- )
+ self.init(inputPort: port, videoPreviewLayer: preview.previewLayer)
}
}
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+isConnectedTo.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+isConnectedTo.swift
index c68f1ee2f9..a1a1a0a2c5 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+isConnectedTo.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureConnection+isConnectedTo.swift
@@ -22,4 +22,13 @@ extension AVCaptureConnection {
return false
}
}
+
+ func isConnectedTo(output: ResolvedCameraSessionConnection.Output) -> Bool {
+ switch output {
+ case .output(let output):
+ return self.output == output.output
+ case .preview(let preview):
+ return self.videoPreviewLayer == preview.previewLayer
+ }
+ }
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureDevice+defaultFor.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureDevice+defaultFor.swift
new file mode 100644
index 0000000000..bed41b10df
--- /dev/null
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureDevice+defaultFor.swift
@@ -0,0 +1,28 @@
+///
+/// AVCaptureDevice+defaultFor.swift
+/// VisionCamera
+/// Copyright © 2025 Marc Rousavy @ Margelo
+///
+
+import AVFoundation
+import Foundation
+import NitroModules
+
+extension AVCaptureDevice {
+ static func `default`(for position: TargetCameraPosition) -> AVCaptureDevice? {
+ switch position {
+ case .back:
+ // Get default wide-angle at .back
+ return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
+ case .front:
+ // Get default wide-angle at .front
+ return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
+ case .external:
+ // On iOS, Position "external" is for some reason reflected on the .deviceType, not on .position.
+ guard #available(iOS 17.0, *) else {
+ return nil
+ }
+ return AVCaptureDevice.default(.external, for: .video, position: .unspecified)
+ }
+ }
+}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureDevice+resolve.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureDevice+resolve.swift
new file mode 100644
index 0000000000..d53f39291a
--- /dev/null
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureDevice+resolve.swift
@@ -0,0 +1,26 @@
+///
+/// AVCaptureDevice+defaultFor.swift
+/// VisionCamera
+/// Copyright © 2025 Marc Rousavy @ Margelo
+///
+
+import AVFoundation
+import Foundation
+import NitroModules
+
+extension AVCaptureDevice {
+ static func resolve(value: CameraDeviceOrPosition) throws(RuntimeError) -> AVCaptureDevice {
+ switch value {
+ case .first(let hybridCameraDeviceSpec):
+ guard let device = hybridCameraDeviceSpec as? any NativeCameraDevice else {
+ throw RuntimeError("CameraDevice \"\(hybridCameraDeviceSpec)\" is not of type `NativeCameraDevice`!")
+ }
+ return device.device
+ case .second(let targetCameraPosition):
+ guard let device = AVCaptureDevice.default(for: targetCameraPosition) else {
+ throw RuntimeError("No CameraDevice exists at position \"\(targetCameraPosition)\"!")
+ }
+ return device
+ }
+ }
+}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addConnection.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addConnection.swift
index a58e4c2c31..4ddf5e837e 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addConnection.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addConnection.swift
@@ -14,9 +14,9 @@ import NitroModules
// `NativeCameraDevice` directly, and only unwrap once in the caller?
extension AVCaptureSession {
- func addConnection(input: any HybridCameraDeviceSpec, output: any HybridCameraOutputSpec) throws {
- // 1. Get the `AVCaptureDeviceInput` for our `input`
- let deviceInput = try findDevice(for: input)
+ func addConnection(input device: AVCaptureDevice, output: ResolvedCameraSessionConnection.Output) throws {
+ // 1. Get the `AVCaptureDeviceInput` for our `device`
+ let deviceInput = try findDevice(for: device)
// 2. Create the `AVCaptureConnection` (either output or preview, we have an extension init)
let connection = try AVCaptureConnection(input: deviceInput, output: output)
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addInputWithNoConnections.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addInputWithNoConnections.swift
index b3bf24cdf1..3c3adca4b1 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addInputWithNoConnections.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addInputWithNoConnections.swift
@@ -9,17 +9,13 @@ import Foundation
import NitroModules
extension AVCaptureSession {
- func addInputWithNoConnections(_ deviceSpec: any HybridCameraDeviceSpec) throws
+ func addInputWithNoConnections(_ deviceOrPosition: CameraDeviceOrPosition) throws
-> AVCaptureDeviceInput
{
- guard let input = deviceSpec as? any NativeCameraDevice else {
- throw RuntimeError.error(
- withMessage: "Input \"\(deviceSpec)\" is not of type `NativeCameraDevice`!")
- }
- let deviceInput = try AVCaptureDeviceInput(device: input.device)
+ let device = try AVCaptureDevice.resolve(value: deviceOrPosition)
+ let deviceInput = try AVCaptureDeviceInput(device: device)
guard canAddInput(deviceInput) else {
- throw RuntimeError.error(
- withMessage: "Input \"\(deviceInput)\" cannot be added to Camera Session!")
+ throw RuntimeError("Input \"\(deviceInput)\" cannot be added to Camera Session!")
}
logger.info("Adding input \(deviceInput)...")
addInputWithNoConnections(deviceInput)
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addOutputWithNoConnections.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addOutputWithNoConnections.swift
index 24a5a0d45a..f76d97abd3 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addOutputWithNoConnections.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+addOutputWithNoConnections.swift
@@ -9,11 +9,11 @@ import Foundation
import NitroModules
extension AVCaptureSession {
- func addOutputWithNoConnections(_ outputSpec: any HybridCameraOutputSpec) throws {
- switch outputSpec {
- case let hybridOutput as any NativeCameraOutput:
+ func addOutputWithNoConnections(_ output: ResolvedCameraSessionConnection.Output) throws {
+ switch output {
+ case .output(let output):
// It's a normal AVCaptureOutput
- let output = hybridOutput.output
+ let output = output.output
guard output.connections.isEmpty else {
throw RuntimeError.error(withMessage: "The given Output \"\(output)\" is already connected to a different Camera Session!")
}
@@ -23,18 +23,15 @@ extension AVCaptureSession {
}
logger.info("Adding Output \(output)...")
addOutputWithNoConnections(output)
- case let hybridPreview as any NativePreviewViewOutput:
+
+ case .preview(let preview):
// It's an AVVideoPreviewLayer - we need to set its .session
- guard hybridPreview.previewLayer.session == nil else {
- throw RuntimeError.error(withMessage: "The given Preview Output \"\(hybridPreview)\" is already connected to a different Camera Session!")
+ let previewLayer = preview.previewLayer
+ guard previewLayer.session == nil else {
+ throw RuntimeError.error(withMessage: "The given Preview Output \"\(previewLayer)\" is already connected to a different Camera Session!")
}
- logger.info("Adding Preview \(hybridPreview.previewLayer)...")
- hybridPreview.previewLayer.setSessionWithNoConnection(self)
- default:
- throw RuntimeError.error(
- withMessage:
- "Output \"\(outputSpec)\" is not of type `NativeCameraOutput` or `NativePreviewViewOutput`!"
- )
+ logger.info("Adding Preview \(previewLayer)...")
+ previewLayer.setSessionWithNoConnection(self)
}
}
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsConnection.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsConnection.swift
index fa967e3957..e345b547e3 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsConnection.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsConnection.swift
@@ -9,17 +9,18 @@ import Foundation
import NitroModules
extension AVCaptureSession {
- func containsConnection(input: any HybridCameraDeviceSpec, output: any HybridCameraOutputSpec)
- throws -> Bool
- {
- // 1. Get the `AVCaptureDeviceInput` for our given `input` - this throws if it isn't attached yet.
- let deviceInput = try findDevice(for: input)
- // 2. Find out if we currently have a connection from this input to this output
+ // TODO: Wait I think this is wrong and doesn't cover all outputs..?
+ func contains(connection: ResolvedCameraSessionConnection) -> Bool {
+ return self.connections.contains { c in
+ return connection.contains(connection: c)
+ }
+ }
+ func containsConnection(
+ input: AVCaptureDevice,
+ output: ResolvedCameraSessionConnection.Output
+ ) -> Bool {
return self.connections.contains { connection in
- let isConnectedToInput = connection.inputPorts.contains { port in
- return port.input == deviceInput
- }
- return isConnectedToInput && connection.isConnectedTo(output: output)
+ return connection.deviceInput == input && connection.isConnectedTo(output: output)
}
}
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsInput.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsInput.swift
index 20834abdaa..713ffe06c0 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsInput.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsInput.swift
@@ -9,19 +9,13 @@ import Foundation
import NitroModules
extension AVCaptureSession {
- func containsInput(_ deviceSpec: any HybridCameraDeviceSpec) throws -> Bool {
- guard let nativeInput = deviceSpec as? any NativeCameraDevice else {
- throw RuntimeError.error(
- withMessage: "Input \"\(deviceSpec)\" is not of type `NativeCameraDevice`!")
- }
- for input in self.inputs {
+ func containsInput(_ deviceOrPosition: CameraDeviceOrPosition) throws -> Bool {
+ let device = try AVCaptureDevice.resolve(value: deviceOrPosition)
+ return self.inputs.contains { input in
guard let deviceInput = input as? AVCaptureDeviceInput else {
- continue
- }
- if deviceInput.device == nativeInput.device {
- return true
+ return false
}
+ return deviceInput.device == device
}
- return false
}
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsOutput.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsOutput.swift
index c5761d7d52..85c2250b04 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsOutput.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+containsOutput.swift
@@ -9,20 +9,12 @@ import Foundation
import NitroModules
extension AVCaptureSession {
- func containsOutput(_ outputSpec: any HybridCameraOutputSpec) throws -> Bool {
- switch outputSpec {
- case let hybridOutput as any NativeCameraOutput:
- // It's a normal AVCaptureOutput
- return outputs.contains(hybridOutput.output)
- case let hybridPreview as any NativePreviewViewOutput:
- // It's an AVVideoPreviewLayer - this is a bit different than normal outputs:
- // We "add" it by setting its .session property.
- return hybridPreview.previewLayer.session == self
- default:
- throw RuntimeError.error(
- withMessage:
- "Output \"\(outputSpec)\" is not of type `NativeCameraOutput` or `NativePreviewViewOutput`!"
- )
+ func containsOutput(_ output: ResolvedCameraSessionConnection.Output) -> Bool {
+ switch output {
+ case .output(let output):
+ return outputs.contains(output.output)
+ case .preview(let previewLayer):
+ return previewLayer.previewLayer.session == self
}
}
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+findDevice.swift b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+findDevice.swift
index 431e93a82e..dd48dc154e 100644
--- a/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+findDevice.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/AVFoundation/AVCaptureSession+findDevice.swift
@@ -14,26 +14,23 @@ import NitroModules
// `NativeCameraDevice` directly, and only unwrap once in the caller?
extension AVCaptureSession {
- func findDevice(for input: any HybridCameraDeviceSpec) throws -> AVCaptureDeviceInput {
- // 1. Downcast input
- guard let input = input as? any NativeCameraDevice else {
- throw RuntimeError.error(
- withMessage: "Input \"\(input)\" is not of type `NativeCameraDevice`!")
- }
- // 2. Get a device-input
+ func findDevice(for input: CameraDeviceOrPosition) throws -> AVCaptureDeviceInput {
+ let device = try AVCaptureDevice.resolve(value: input)
+ return try findDevice(for: device)
+ }
+
+ func findDevice(for device: AVCaptureDevice) throws -> AVCaptureDeviceInput {
for attachedInput in self.inputs {
guard let attachedInput = attachedInput as? AVCaptureDeviceInput else {
continue
}
- if attachedInput.device == input.device {
+ if attachedInput.device == device {
// 3. We found it! Return
return attachedInput
}
}
- // 4. We didn't find the device because it is not attached to the `CameraSession` yet. Throw.
- throw RuntimeError.error(
- withMessage:
- "The given input \"\(input)\" is not yet attached to the `CameraSession` - cannot form a connection yet!"
- )
+
+ // We didn't find it!
+ throw RuntimeError("The given device \"\(device)\" is not yet attached to the `CameraSession` - cannot form a connection yet!")
}
}
diff --git a/packages/react-native-vision-camera/ios/Extensions/CameraSessionConnections+contains.swift b/packages/react-native-vision-camera/ios/Extensions/CameraSessionConnections+contains.swift
index 7bffdb441d..ec3b337838 100644
--- a/packages/react-native-vision-camera/ios/Extensions/CameraSessionConnections+contains.swift
+++ b/packages/react-native-vision-camera/ios/Extensions/CameraSessionConnections+contains.swift
@@ -9,18 +9,6 @@ import Foundation
import NitroModules
extension Array where Element == CameraSessionConnection {
- var containsOutputThatRequiresAudioInput: Bool {
- return self.contains { connection in
- return connection.outputs.contains { outputConfig in
- if let output = outputConfig.output as? any NativeCameraOutput {
- return output.requiresAudioInput
- } else {
- return false
- }
- }
- }
- }
-
func contains(input targetInput: AVCaptureInput) -> Bool {
guard let targetInput = targetInput as? AVCaptureDeviceInput else {
// We currently only support AVCaptureDeviceInput. I don't even know if there are other subclasses.
@@ -28,10 +16,10 @@ extension Array where Element == CameraSessionConnection {
}
return self.contains { connection in
- guard let input = connection.input as? any NativeCameraDevice else {
+ guard let device = try? AVCaptureDevice.resolve(value: connection.input) else {
return false
}
- return input.device == targetInput.device
+ return device == targetInput.device
}
}
@@ -62,7 +50,7 @@ extension Array where Element == CameraSessionConnection {
// It's a connection to a camera device - compare `input` and `output`:
return self.contains { connection in
- guard let input = connection.input as? any NativeCameraDevice else {
+ guard let device = try? AVCaptureDevice.resolve(value: connection.input) else {
return false
}
@@ -71,7 +59,7 @@ extension Array where Element == CameraSessionConnection {
guard let deviceInput = port.input as? AVCaptureDeviceInput else {
return false
}
- return input.device == deviceInput.device
+ return device == deviceInput.device
}
if !containsInput {
return false
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ConstraintResolver.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ConstraintResolver.swift
index 92f2bec4e3..22a5793079 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ConstraintResolver.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ConstraintResolver.swift
@@ -13,6 +13,20 @@ import NitroModules
// .yuv4208BiVideo formats. When it's `'native'`, we shouldn't care. Like .any
enum ConstraintResolver {
+ static func resolveConstraints(
+ for device: CameraDeviceOrPosition,
+ constraints: [Constraint],
+ outputs: [any HybridCameraOutputSpec],
+ isMultiCam: Bool
+ ) throws -> ResolvedConstraints {
+ let device = try AVCaptureDevice.resolve(value: device)
+ return try resolveConstraints(
+ for: device,
+ constraints: constraints,
+ outputs: outputs,
+ isMultiCam: isMultiCam)
+ }
+
static func resolveConstraints(
for device: any HybridCameraDeviceSpec,
constraints: [Constraint],
@@ -20,9 +34,21 @@ enum ConstraintResolver {
isMultiCam: Bool
) throws -> ResolvedConstraints {
guard let device = device as? any NativeCameraDevice else {
- throw RuntimeError.error(
- withMessage: "The given `device` is not of type `NativeCameraDevice`!")
+ throw RuntimeError("The given `device` is not of type `NativeCameraDevice`!")
}
+ return try resolveConstraints(
+ for: device.device,
+ constraints: constraints,
+ outputs: outputs,
+ isMultiCam: isMultiCam)
+ }
+
+ static func resolveConstraints(
+ for device: AVCaptureDevice,
+ constraints: [Constraint],
+ outputs: [any HybridCameraOutputSpec],
+ isMultiCam: Bool
+ ) throws -> ResolvedConstraints {
let outputs = try outputs.compactMap { output in
switch output {
case let output as any NativeCameraOutput:
@@ -37,7 +63,7 @@ enum ConstraintResolver {
}
}
return try resolveConstraints(
- for: device.device,
+ for: device,
constraints: constraints,
outputs: outputs,
isMultiCam: isMultiCam)
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ResolvableConstraint/ResolvableConstraint+AVAutoFocusSystem.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ResolvableConstraint/ResolvableConstraint+AVAutoFocusSystem.swift
index 4e68e5cb08..1cf973cddc 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ResolvableConstraint/ResolvableConstraint+AVAutoFocusSystem.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Constraints/ResolvableConstraint/ResolvableConstraint+AVAutoFocusSystem.swift
@@ -9,19 +9,6 @@ import AVFoundation
extension AVCaptureDevice.Format.AutoFocusSystem: ResolvableConstraint {
typealias ResolvedValue = Void
- private var rank: Int {
- switch self {
- case .none:
- return 0
- case .contrastDetection:
- return 1
- case .phaseDetection:
- return 2
- @unknown default:
- return 3
- }
- }
-
func resolve(for format: AVCaptureDevice.Format) -> ConstraintResolution {
let targetAutoFocusSystem = self
let actualAutoFocusSystem = format.autoFocusSystem
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraController.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraController.swift
index 6b8ac9b850..ff48fd9f4f 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraController.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraController.swift
@@ -14,13 +14,10 @@ final class HybridCameraController: HybridCameraControllerSpec, NativeCameraCont
let captureDevice: AVCaptureDevice
private var activeMeteringTask: MeteringTask? = nil
- init(device: any HybridCameraDeviceSpec, queue: DispatchQueue) throws {
- guard let hybridDevice = device as? HybridCameraDevice else {
- throw RuntimeError.error(
- withMessage: "Device \"\(device)\" is not of type `HybridCameraDevice`!")
- }
- self.captureDevice = hybridDevice.device
+ init(device: any HybridCameraDeviceSpec & NativeCameraDevice, queue: DispatchQueue) throws {
self.device = device
+ self.captureDevice = device.device
+
// TODO: Is it best practice to use the same `queue` as our HybridCameraSession?
self.queue = queue
}
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraDeviceFactory.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraDeviceFactory.swift
index 5cdc026b20..013833ca5f 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraDeviceFactory.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraDeviceFactory.swift
@@ -74,29 +74,12 @@ final class HybridCameraDeviceFactory: HybridCameraDeviceFactorySpec {
}
func getDefaultCamera(position: TargetCameraPosition) throws -> (any HybridCameraDeviceSpec)? {
- guard let device = getDefaultAVCaptureDevice(at: position) else {
+ guard let device = AVCaptureDevice.default(for: position) else {
return nil
}
return HybridCameraDevice(device: device)
}
- private func getDefaultAVCaptureDevice(at position: TargetCameraPosition) -> AVCaptureDevice? {
- switch position {
- case .back:
- // Get default wide-angle at .back
- return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
- case .front:
- // Get default wide-angle at .front
- return AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)
- case .external:
- // On iOS, Position "external" is for some reason reflected on the .deviceType, not on .position.
- guard #available(iOS 17.0, *) else {
- return nil
- }
- return AVCaptureDevice.default(.external, for: .video, position: .unspecified)
- }
- }
-
func getSupportedExtensions(camera: any HybridCameraDeviceSpec) throws -> Promise<
[any HybridCameraExtensionSpec]
> {
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraSession.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraSession.swift
index b7b0c3cf3c..01112938b7 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraSession.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraSession.swift
@@ -46,16 +46,17 @@ final class HybridCameraSession: HybridCameraSessionSpec {
let isMultiCam = connections.count > 1
if isMultiCam {
guard self.session is AVCaptureMultiCamSession else {
- throw RuntimeError.error(
- withMessage:
- "Cannot add multiple inputs (\(connections)) to a single-cam CameraSession! "
- + "Create your CameraSession as a multi-cam session (`enableMultiCamSupport = true`) to add multiple camera inputs."
- )
+ throw RuntimeError(
+ "Cannot add multiple inputs (\(connections)) to a single-cam CameraSession! "
+ + "Create your CameraSession as a multi-cam session (`enableMultiCamSupport = true`) to add multiple camera inputs.")
}
}
logger.info("Reconfiguring CameraSession with \(connections.count) connection(s)...")
+ // Resolve flat Connections upfront to AVCaptureDevice, AVOutput, etc etc
+ let connections = try connections.map { try ResolvedCameraSessionConnection(connection: $0) }
+
// Wrap the configuration in a batch
self.session.beginConfiguration()
defer {
@@ -76,24 +77,11 @@ final class HybridCameraSession: HybridCameraSessionSpec {
for connection in connections {
// connection:
try self.configureConnection(connection, isMultiCam: isMultiCam)
-
// outputs:
- for outputConfiguration in connection.outputs {
- switch outputConfiguration.output {
- case let output as any NativeCameraOutput:
- // Configure AVCaptureOutput
- output.configure(config: outputConfiguration)
- case let previewOutput as any NativePreviewViewOutput:
- // Configure AVCaptureVideoPreviewLayer
- previewOutput.configure(config: outputConfiguration)
- default:
- // wrong type!
- break
- }
- }
+ self.configureOutputs(connection)
}
- // Apply any changes to the session connections
+ // If we have any Dynamic Range Constraints, we disable automatic color space selection on the AVCaptureSession.
let hasCustomDynamicRangeConstraint = connections.contains { connection in
return connection.constraints.contains { constraint in
// If the user specified a custom DynamicRange, we cannot automatically adjust the ColorSpace
@@ -103,8 +91,8 @@ final class HybridCameraSession: HybridCameraSessionSpec {
self.session.automaticallyConfiguresCaptureDeviceForWideColor = !hasCustomDynamicRangeConstraint
// Background Audio Playback
- if let allowBackgroundAudioPlayback = config?.allowBackgroundAudioPlayback {
- if #available(iOS 18.0, *) {
+ if #available(iOS 18.0, *) {
+ if let allowBackgroundAudioPlayback = config?.allowBackgroundAudioPlayback {
self.session.configuresApplicationAudioSessionToMixWithOthers = allowBackgroundAudioPlayback
}
}
@@ -117,9 +105,9 @@ final class HybridCameraSession: HybridCameraSessionSpec {
}
// pragma MARK: SessionConfiguration
- private func configureConnection(_ connection: CameraSessionConnection, isMultiCam: Bool) throws {
+ private func configureConnection(_ connection: ResolvedCameraSessionConnection, isMultiCam: Bool) throws {
// Get a handle to the input
- let outputs = connection.outputs.map { $0.output }
+ let outputs = connection.outputs.map { $0.output.hybrid }
let resolved = try ConstraintResolver.resolveConstraints(
for: connection.input,
constraints: connection.constraints,
@@ -201,6 +189,18 @@ final class HybridCameraSession: HybridCameraSessionSpec {
}
}
+ private func configureOutputs(_ connection: ResolvedCameraSessionConnection) {
+ for outputConfiguration in connection.outputs {
+ let outputConfig = OutputConfiguration(mirrorMode: outputConfiguration.mirrorMode)
+ switch outputConfiguration.output {
+ case .output(let output):
+ output.configure(config: outputConfig)
+ case .preview(let preview):
+ preview.configure(config: outputConfig)
+ }
+ }
+ }
+
// pragma MARK: Start/Stop
func start() -> Promise {
return Promise.parallel(Self.queue) {
@@ -225,19 +225,24 @@ final class HybridCameraSession: HybridCameraSessionSpec {
* Adds all inputs on the given [targetConnections] if they haven't been added yet,
* and removes all current inputs that aren't listed in the [connections] array.
*/
- private func updateInputs(_ targetConnections: [CameraSessionConnection]) throws {
+ private func updateInputs(_ targetConnections: [ResolvedCameraSessionConnection]) throws {
+ let requiresAudioInput = targetConnections.contains { $0.requiresAudioInput }
+
// 1. Loop through all existing inputs in the session - if it's not in our `connections` array, we remove it
for currentlyAttachedInput in self.session.inputs {
if currentlyAttachedInput.isMicrophone {
// It's a microphone/audio input - let's check if we want audio in any connection.
- if !targetConnections.containsOutputThatRequiresAudioInput {
+ if !requiresAudioInput {
// 1.1.a. We don't want any audio input - remove it!
logger.info("Removing audio input \(currentlyAttachedInput)...")
self.session.removeInput(currentlyAttachedInput)
}
} else {
// It's a normal camera device - let's check if we want it in connections.
- if !targetConnections.contains(input: currentlyAttachedInput) {
+ let containsCurrentlyAttachedInput = targetConnections.contains { connection in
+ return connection.isConnectedTo(input: currentlyAttachedInput)
+ }
+ if !containsCurrentlyAttachedInput {
// 1.1.b. We don't want this input - remove it!
logger.info("Removing input \(currentlyAttachedInput)...")
self.session.removeInput(currentlyAttachedInput)
@@ -246,21 +251,28 @@ final class HybridCameraSession: HybridCameraSessionSpec {
}
// 2. Loop through all connection inputs - if it's not yet attached to the session, add it
for connection in targetConnections {
- let containsInput = try self.session.containsInput(connection.input)
+ let device = connection.input.device
+ let containsInput = self.session.inputs.contains { input in
+ guard let deviceInput = input as? AVCaptureDeviceInput else {
+ return false
+ }
+ return deviceInput.device == device
+ }
if !containsInput {
// 2.1. It doesn't exist yet - add it to the session..
- let input = try self.session.addInputWithNoConnections(connection.input)
+ let deviceInput = try AVCaptureDeviceInput(device: device)
+ self.session.addInputWithNoConnections(deviceInput)
// 2.2. Configure the input with initial settings
if connection.initialZoom != nil || connection.initialExposureBias != nil {
try applyInitialConfig(
- device: input.device,
+ device: device,
initialZoom: connection.initialZoom,
initialExposureBias: connection.initialExposureBias)
}
}
}
// 3. If we need an audio input, add it now
- if targetConnections.containsOutputThatRequiresAudioInput {
+ if requiresAudioInput {
let containsAudioInput = self.session.inputs.contains { $0.isMicrophone }
if !containsAudioInput {
guard AVCaptureDevice.authorizationStatus(for: .audio) == .authorized else {
@@ -275,10 +287,13 @@ final class HybridCameraSession: HybridCameraSessionSpec {
* and removes all current outputs that aren't listed in the [targetConnections] array.
* This includes special handling for `NativePreviewViewOutput`.
*/
- private func updateOutputs(_ targetConnections: [CameraSessionConnection]) throws {
+ private func updateOutputs(_ targetConnections: [ResolvedCameraSessionConnection]) throws {
// 1. Loop through all current CameraSession outputs - if it's not in our array, we remove it
for currentlyAttachedOutput in self.session.outputs {
- if !targetConnections.contains(output: currentlyAttachedOutput) {
+ let containsAttachedOutput = targetConnections.contains { connection in
+ return connection.isConnectedTo(output: currentlyAttachedOutput)
+ }
+ if !containsAttachedOutput {
// 1.1. We don't want this output - remove it!
logger.info("Removing output \(currentlyAttachedOutput)...")
self.session.removeOutput(currentlyAttachedOutput)
@@ -286,19 +301,18 @@ final class HybridCameraSession: HybridCameraSessionSpec {
}
// 2. Loop through all connections
for connection in targetConnections {
- // 3. Loop through all outputs
- for outputConfiguration in connection.outputs {
- let output = outputConfiguration.output
- let containsOutput = try self.session.containsOutput(output)
+ // 2.1. Loop through each output
+ for output in connection.outputs {
+ let containsOutput = self.session.containsOutput(output.output)
if !containsOutput {
- // 3.1. It doesn't exist yet - add it to the session..
- try session.addOutputWithNoConnections(output)
+ // 2.2. It doesn't exist yet - add it to the session..
+ try session.addOutputWithNoConnections(output.output)
}
}
}
}
- private func updateConnections(_ targetConnections: [CameraSessionConnection]) throws {
+ private func updateConnections(_ targetConnections: [ResolvedCameraSessionConnection]) throws {
// By this time, `updateInputs(...)` and `updateOutputs(...)` has already been called.
// This ensures that all unwanted inputs or outputs have been removed - and if an input
// or output is removed from the session, the connection will also automatically be removed.
@@ -306,7 +320,8 @@ final class HybridCameraSession: HybridCameraSessionSpec {
// 1. Loop through all current CameraSession connections - if it's not in our array, we remove it
for currentConnection in self.session.connections {
- if !targetConnections.contains(connection: currentConnection) {
+ let containsConnection = targetConnections.contains { $0.contains(connection: currentConnection) }
+ if !containsConnection {
// 1.1. We don't want this connection - remove it!
logger.info("Removing connection \(currentConnection)...")
self.session.removeConnection(currentConnection)
@@ -315,19 +330,20 @@ final class HybridCameraSession: HybridCameraSessionSpec {
// 2. Add all new connections that we don't have yet:
for connection in targetConnections {
+ // 2.1. Iterate through all outputs
for outputConfiguration in connection.outputs {
- // 2.1. Add the camera -> output connection
- let containsConnection = try self.session.containsConnection(
- input: connection.input,
+ // 2.2. Add the camera -> output connection
+ let containsConnection = self.session.containsConnection(
+ input: connection.input.device,
output: outputConfiguration.output)
if !containsConnection {
try self.session.addConnection(
- input: connection.input,
+ input: connection.input.device,
output: outputConfiguration.output)
}
// 2.2. If this `output` requires audio, add this connection too
- if let output = outputConfiguration.output as? any NativeCameraOutput {
+ if case .output(let output) = outputConfiguration.output {
if output.requiresAudioInput {
let hasAudioConnection = self.session.containsAudioConnection(to: output.output)
if !hasAudioConnection {
@@ -340,10 +356,10 @@ final class HybridCameraSession: HybridCameraSessionSpec {
}
private func applyInitialConfig(
- device: AVCaptureDevice, initialZoom: Double?, initialExposureBias: Double?
- )
- throws
- {
+ device: AVCaptureDevice,
+ initialZoom: Double?,
+ initialExposureBias: Double?
+ ) throws {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
if let initialZoom = initialZoom {
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift
index e600a6eac6..0be7d9d315 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraDepthFrameOutput.swift
@@ -66,7 +66,7 @@ final class HybridCameraDepthFrameOutput: HybridCameraDepthFrameOutputSpec, Nati
}
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
// TODO: Somehow attach this to `connection`, so we dont have race conditions
// where we read `self.mirrorMode` for an old Frame in `getMediaSampleMetadata`
self.mirrorMode = config.mirrorMode
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift
index afba918669..17c7a5f13c 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraFrameOutput.swift
@@ -80,7 +80,7 @@ final class HybridCameraFrameOutput: HybridCameraFrameOutputSpec, NativeCameraOu
}
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
// TODO: Somehow attach this to `connection`, so we dont have race conditions
// where we read `self.mirrorMode` for an old Frame in `getMediaSampleMetadata`
self.mirrorMode = config.mirrorMode
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraObjectOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraObjectOutput.swift
index ebde0a6d94..9e9270ae99 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraObjectOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraObjectOutput.swift
@@ -39,7 +39,7 @@ final class HybridCameraObjectOutput: HybridCameraObjectOutputSpec, NativeCamera
output.setMetadataObjectsDelegate(delegate, queue: queue)
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
guard let connection = output.connection(with: .metadataObject) else {
return
}
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift
index 1f5a54cf18..5b82535074 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPhotoOutput.swift
@@ -75,7 +75,7 @@ final class HybridCameraPhotoOutput: HybridCameraPhotoOutputSpec, NativeCameraOu
try? prepareDefaultPhotoSettings()
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
guard let connection = output.connection(with: .video) else {
return
}
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPreviewOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPreviewOutput.swift
index bc95c1f6d3..559829f4e6 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPreviewOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraPreviewOutput.swift
@@ -52,7 +52,7 @@ final class HybridCameraPreviewOutput: HybridCameraPreviewOutputSpec, NativePrev
orientationManager.stopOrientationUpdates()
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
guard let connection = previewLayer.connection else {
return
}
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift
index 4abe05cbea..ddf5895cd3 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoFrameOutput.swift
@@ -78,7 +78,7 @@ final class HybridCameraVideoFrameOutput: HybridCameraVideoOutputSpec, NativeCam
}
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
// TODO: Set orientation via video metadata flags to avoid physically rotating buffers.
// The problem with that is that we need to support switching Camera devices on the fly,
// which rotates buffer later on somehow.
diff --git a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoOutput.swift b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoOutput.swift
index 6a9ebafd5d..a7c8f74c7b 100644
--- a/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoOutput.swift
+++ b/packages/react-native-vision-camera/ios/Hybrid Objects/Outputs/HybridCameraVideoOutput.swift
@@ -58,7 +58,7 @@ final class HybridCameraVideoOutput: HybridCameraVideoOutputSpec, NativeCameraOu
self.output.metadata = metadata
}
- func configure(config: CameraOutputConfiguration) {
+ func configure(config: OutputConfiguration) {
guard let connection = output.connection(with: .video) else {
return
}
diff --git a/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift b/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift
index b1a1db6b75..d15d3eb9c2 100644
--- a/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift
+++ b/packages/react-native-vision-camera/ios/Public/NativeCameraOutput.swift
@@ -55,5 +55,5 @@ public protocol NativeCameraOutput: AnyObject, ResolutionNegotiationParticipant
* The `NativeCameraOutput` is expected to apply all configs
* such as orientation or mirroring settings in here.
*/
- func configure(config: CameraOutputConfiguration)
+ func configure(config: OutputConfiguration)
}
diff --git a/packages/react-native-vision-camera/ios/Public/NativePreviewViewOutput.swift b/packages/react-native-vision-camera/ios/Public/NativePreviewViewOutput.swift
index 8834e2a99d..865189ee69 100644
--- a/packages/react-native-vision-camera/ios/Public/NativePreviewViewOutput.swift
+++ b/packages/react-native-vision-camera/ios/Public/NativePreviewViewOutput.swift
@@ -11,11 +11,11 @@ public protocol NativePreviewViewOutput: AnyObject, ResolutionNegotiationPartici
var previewLayer: AVCaptureVideoPreviewLayer { get }
/**
- * Called whenever the `CameraOutputConfiguration` might
+ * Called whenever the `OutputConfiguration` might
* change, in a `beginConfiguration()`/`commitConfiguration()`
* batch.
* The `NativePreviewViewOutput` is expected to apply all configs
* such as orientation or mirroring settings in here.
*/
- func configure(config: CameraOutputConfiguration)
+ func configure(config: OutputConfiguration)
}
diff --git a/packages/react-native-vision-camera/ios/Public/SessionConfig.swift b/packages/react-native-vision-camera/ios/Public/OutputConfig.swift
similarity index 54%
rename from packages/react-native-vision-camera/ios/Public/SessionConfig.swift
rename to packages/react-native-vision-camera/ios/Public/OutputConfig.swift
index efaaa31e64..b8b71b8d6f 100644
--- a/packages/react-native-vision-camera/ios/Public/SessionConfig.swift
+++ b/packages/react-native-vision-camera/ios/Public/OutputConfig.swift
@@ -1,5 +1,5 @@
///
-/// SessionConfig.swift
+/// OutputConfiguration.swift
/// VisionCamera
/// Copyright © 2025 Marc Rousavy @ Margelo
///
@@ -7,6 +7,6 @@
import AVFoundation
import Foundation
-public protocol SessionConfig {
- var enablePhotoHDR: Bool { get }
+public struct OutputConfiguration {
+ let mirrorMode: MirrorMode
}
diff --git a/packages/react-native-vision-camera/ios/Utils/ResolvedCameraSessionConnection.swift b/packages/react-native-vision-camera/ios/Utils/ResolvedCameraSessionConnection.swift
new file mode 100644
index 0000000000..9c162d76c0
--- /dev/null
+++ b/packages/react-native-vision-camera/ios/Utils/ResolvedCameraSessionConnection.swift
@@ -0,0 +1,137 @@
+//
+// ResolvedCameraSessionConnection.swift
+// VisionCamera
+//
+// Created by Marc Rousavy on 07.01.26.
+//
+
+import AVFoundation
+import NitroModules
+
+/// Represents a resolved `CameraSessionConnection`.
+struct ResolvedCameraSessionConnection {
+ let input: any HybridCameraDeviceSpec & NativeCameraDevice
+ let outputs: [OutputConfiguration]
+ let initialExposureBias: Double?
+ let initialZoom: Double?
+ let onSessionConfigSelected: ((_ config: (any HybridCameraSessionConfigSpec)) -> Void)?
+ let constraints: [Constraint]
+
+ init(connection: CameraSessionConnection) throws {
+ self.input = try Self.resolveInput(connection.input)
+ self.outputs = try connection.outputs.map { try Self.resolveOutput($0) }
+ self.initialExposureBias = connection.initialExposureBias
+ self.initialZoom = connection.initialZoom
+ self.onSessionConfigSelected = connection.onSessionConfigSelected
+ self.constraints = connection.constraints
+ }
+
+ var requiresAudioInput: Bool {
+ return outputs.contains { $0.output.requiresAudioInput }
+ }
+
+ func isConnectedTo(input: AVCaptureInput) -> Bool {
+ guard let deviceInput = input as? AVCaptureDeviceInput else {
+ return false
+ }
+ return deviceInput.device == self.input.device
+ }
+ func isConnectedTo(output avOutput: AVCaptureOutput) -> Bool {
+ return outputs.contains { outputConfiguration in
+ switch outputConfiguration.output {
+ case .output(let output):
+ return output.output == avOutput
+ case .preview:
+ return false
+ }
+ }
+ }
+ func isConnectedTo(preview previewLayer: AVCaptureVideoPreviewLayer) -> Bool {
+ return outputs.contains { outputConfiguration in
+ switch outputConfiguration.output {
+ case .output:
+ return false
+ case .preview(let preview):
+ return preview.previewLayer == previewLayer
+ }
+ }
+ }
+ func contains(connection: AVCaptureConnection) -> Bool {
+ guard let input = connection.deviceInput else {
+ return false
+ }
+ if let output = connection.output {
+ return isConnectedTo(input: input) && isConnectedTo(output: output)
+ } else if let preview = connection.videoPreviewLayer {
+ return isConnectedTo(input: input) && isConnectedTo(preview: preview)
+ } else {
+ fatalError("AVCaptureConnection does not have .output or .videoPreviewLayer!")
+ }
+ }
+
+ struct OutputConfiguration {
+ let output: Output
+ let mirrorMode: MirrorMode
+ }
+ enum Output {
+ case output(any HybridCameraOutputSpec & NativeCameraOutput)
+ case preview(any HybridCameraOutputSpec & NativePreviewViewOutput)
+
+ var requiresAudioInput: Bool {
+ switch self {
+ case .output(let output):
+ return output.requiresAudioInput
+ case .preview:
+ return false
+ }
+ }
+
+ var mediaType: MediaType {
+ switch self {
+ case .output(let output):
+ return output.mediaType
+ case .preview(let preview):
+ return preview.mediaType
+ }
+ }
+
+ var hybrid: any HybridCameraOutputSpec {
+ switch self {
+ case .output(let output):
+ return output
+ case .preview(let preview):
+ return preview
+ }
+ }
+ }
+
+ private static func resolveInput(_ input: CameraDeviceOrPosition) throws -> any HybridCameraDeviceSpec & NativeCameraDevice {
+ switch input {
+ case .first(let hybridCameraDeviceSpec):
+ guard let input = hybridCameraDeviceSpec as? any HybridCameraDeviceSpec & NativeCameraDevice else {
+ throw RuntimeError("The given CameraDevice \"\(hybridCameraDeviceSpec)\" does not conform to `NativeCameraDevice`!")
+ }
+ return input
+ case .second(let targetCameraPosition):
+ guard let device = AVCaptureDevice.default(for: targetCameraPosition) else {
+ throw RuntimeError("No CameraDevice exists at position \"\(targetCameraPosition)\"!")
+ }
+ return HybridCameraDevice(device: device)
+ }
+ }
+ private static func resolveOutput(_ outputConfiguration: CameraOutputConfiguration) throws -> OutputConfiguration {
+ switch outputConfiguration.output {
+ case let hybridOutput as any HybridCameraOutputSpec & NativeCameraOutput:
+ // It's a normal AVCaptureOutput
+ return OutputConfiguration(
+ output: .output(hybridOutput),
+ mirrorMode: outputConfiguration.mirrorMode)
+ case let hybridPreview as any HybridCameraOutputSpec & NativePreviewViewOutput:
+ return OutputConfiguration(
+ output: .preview(hybridPreview),
+ mirrorMode: outputConfiguration.mirrorMode)
+ default:
+ throw RuntimeError("Output \"\(outputConfiguration.output)\" is not of type `NativeCameraOutput` or `NativePreviewViewOutput`!")
+ }
+ }
+}
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/VisionCamera+autolinking.cmake b/packages/react-native-vision-camera/nitrogen/generated/android/VisionCamera+autolinking.cmake
index adfdf1fef3..4dcb7a1d87 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/android/VisionCamera+autolinking.cmake
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/VisionCamera+autolinking.cmake
@@ -107,6 +107,7 @@ target_sources(
../nitrogen/generated/android/c++/JHybridCameraVideoOutputSpec.cpp
../nitrogen/generated/android/c++/JHybridRecorderSpec.cpp
../nitrogen/generated/android/c++/JHybridCameraSessionSpec.cpp
+ ../nitrogen/generated/android/c++/JCameraDeviceOrPosition.cpp
../nitrogen/generated/android/c++/JHybridCameraSessionConfigSpec.cpp
../nitrogen/generated/android/c++/JHybridFrameConverterSpec.cpp
../nitrogen/generated/android/c++/JHybridFrameRendererViewSpec.cpp
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraDeviceOrPosition.cpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraDeviceOrPosition.cpp
new file mode 100644
index 0000000000..c165917c32
--- /dev/null
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraDeviceOrPosition.cpp
@@ -0,0 +1,26 @@
+///
+/// JCameraDeviceOrPosition.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "JCameraDeviceOrPosition.hpp"
+
+namespace margelo::nitro::camera {
+ /**
+ * Converts JCameraDeviceOrPosition to std::variant, TargetCameraPosition>
+ */
+ std::variant, TargetCameraPosition> JCameraDeviceOrPosition::toCpp() const {
+ if (isInstanceOf(JCameraDeviceOrPosition_impl::First::javaClassStatic())) {
+ // It's a `std::shared_ptr`
+ auto jniValue = static_cast(this)->getValue();
+ return jniValue->getJHybridCameraDeviceSpec();
+ } else if (isInstanceOf(JCameraDeviceOrPosition_impl::Second::javaClassStatic())) {
+ // It's a `TargetCameraPosition`
+ auto jniValue = static_cast(this)->getValue();
+ return jniValue->toCpp();
+ }
+ throw std::invalid_argument("Variant is unknown Kotlin instance!");
+ }
+} // namespace margelo::nitro::camera
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraDeviceOrPosition.hpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraDeviceOrPosition.hpp
new file mode 100644
index 0000000000..5152dddf75
--- /dev/null
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraDeviceOrPosition.hpp
@@ -0,0 +1,72 @@
+///
+/// JCameraDeviceOrPosition.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include
+#include
+
+#include
+#include "HybridCameraDeviceSpec.hpp"
+#include "TargetCameraPosition.hpp"
+#include
+#include "JHybridCameraDeviceSpec.hpp"
+#include "JTargetCameraPosition.hpp"
+
+namespace margelo::nitro::camera {
+
+ using namespace facebook;
+
+ /**
+ * The C++ JNI bridge between the C++ std::variant and the Java class "CameraDeviceOrPosition".
+ */
+ class JCameraDeviceOrPosition: public jni::JavaClass {
+ public:
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/camera/CameraDeviceOrPosition;";
+
+ static jni::local_ref create_0(jni::alias_ref value) {
+ static const auto method = javaClassStatic()->getStaticMethod)>("create");
+ return method(javaClassStatic(), value);
+ }
+ static jni::local_ref create_1(jni::alias_ref value) {
+ static const auto method = javaClassStatic()->getStaticMethod)>("create");
+ return method(javaClassStatic(), value);
+ }
+
+ static jni::local_ref fromCpp(const std::variant, TargetCameraPosition>& variant) {
+ switch (variant.index()) {
+ case 0: return create_0(std::dynamic_pointer_cast(std::get<0>(variant))->getJavaPart());
+ case 1: return create_1(JTargetCameraPosition::fromCpp(std::get<1>(variant)));
+ default: throw std::invalid_argument("Variant holds unknown index! (" + std::to_string(variant.index()) + ")");
+ }
+ }
+
+ [[nodiscard]] std::variant, TargetCameraPosition> toCpp() const;
+ };
+
+ namespace JCameraDeviceOrPosition_impl {
+ class First final: public jni::JavaClass {
+ public:
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/camera/CameraDeviceOrPosition$First;";
+
+ [[nodiscard]] jni::local_ref getValue() const {
+ static const auto field = javaClassStatic()->getField("value");
+ return getFieldValue(field);
+ }
+ };
+
+ class Second final: public jni::JavaClass {
+ public:
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/camera/CameraDeviceOrPosition$Second;";
+
+ [[nodiscard]] jni::local_ref getValue() const {
+ static const auto field = javaClassStatic()->getField("value");
+ return getFieldValue(field);
+ }
+ };
+ } // namespace JCameraDeviceOrPosition_impl
+} // namespace margelo::nitro::camera
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraSessionConnection.hpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraSessionConnection.hpp
index bb3533c1bd..680de8cadd 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraSessionConnection.hpp
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JCameraSessionConnection.hpp
@@ -17,6 +17,7 @@
#include "HybridCameraOutputSpec.hpp"
#include "HybridCameraSessionConfigSpec.hpp"
#include "JBinnedConstraint.hpp"
+#include "JCameraDeviceOrPosition.hpp"
#include "JCameraOutputConfiguration.hpp"
#include "JConstraint.hpp"
#include "JFPSConstraint.hpp"
@@ -30,6 +31,7 @@
#include "JPixelFormatConstraint.hpp"
#include "JPreviewStabilizationModeConstraint.hpp"
#include "JResolutionBiasConstraint.hpp"
+#include "JTargetCameraPosition.hpp"
#include "JTargetColorRange.hpp"
#include "JTargetColorSpace.hpp"
#include "JTargetDynamicRange.hpp"
@@ -43,6 +45,7 @@
#include "PixelFormatConstraint.hpp"
#include "PreviewStabilizationModeConstraint.hpp"
#include "ResolutionBiasConstraint.hpp"
+#include "TargetCameraPosition.hpp"
#include "TargetColorRange.hpp"
#include "TargetColorSpace.hpp"
#include "TargetDynamicRange.hpp"
@@ -76,8 +79,8 @@ namespace margelo::nitro::camera {
[[nodiscard]]
CameraSessionConnection toCpp() const {
static const auto clazz = javaClassStatic();
- static const auto fieldInput = clazz->getField("input");
- jni::local_ref input = this->getFieldValue(fieldInput);
+ static const auto fieldInput = clazz->getField("input");
+ jni::local_ref input = this->getFieldValue(fieldInput);
static const auto fieldOutputs = clazz->getField>("outputs");
jni::local_ref> outputs = this->getFieldValue(fieldOutputs);
static const auto fieldConstraints = clazz->getField>("constraints");
@@ -89,7 +92,7 @@ namespace margelo::nitro::camera {
static const auto fieldOnSessionConfigSelected = clazz->getField("onSessionConfigSelected");
jni::local_ref onSessionConfigSelected = this->getFieldValue(fieldOnSessionConfigSelected);
return CameraSessionConnection(
- input->getJHybridCameraDeviceSpec(),
+ input->toCpp(),
[&](auto&& __input) {
size_t __size = __input->size();
std::vector __vector;
@@ -130,12 +133,12 @@ namespace margelo::nitro::camera {
*/
[[maybe_unused]]
static jni::local_ref fromCpp(const CameraSessionConnection& value) {
- using JSignature = JCameraSessionConnection(jni::alias_ref, jni::alias_ref>, jni::alias_ref>, jni::alias_ref, jni::alias_ref, jni::alias_ref);
+ using JSignature = JCameraSessionConnection(jni::alias_ref, jni::alias_ref>, jni::alias_ref>, jni::alias_ref, jni::alias_ref, jni::alias_ref);
static const auto clazz = javaClassStatic();
static const auto create = clazz->getStaticMethod("fromCpp");
return create(
clazz,
- std::dynamic_pointer_cast(value.input)->getJavaPart(),
+ JCameraDeviceOrPosition::fromCpp(value.input),
[&](auto&& __input) {
size_t __size = __input.size();
jni::local_ref> __array = jni::JArrayClass::newArray(__size);
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraSessionSpec.cpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraSessionSpec.cpp
index 15851995f2..28013c89eb 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraSessionSpec.cpp
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraSessionSpec.cpp
@@ -15,6 +15,8 @@ namespace margelo::nitro::camera { struct ListenerSubscription; }
namespace margelo::nitro::camera { struct CameraSessionConnection; }
// Forward declaration of `HybridCameraDeviceSpec` to properly resolve imports.
namespace margelo::nitro::camera { class HybridCameraDeviceSpec; }
+// Forward declaration of `TargetCameraPosition` to properly resolve imports.
+namespace margelo::nitro::camera { enum class TargetCameraPosition; }
// Forward declaration of `CameraOutputConfiguration` to properly resolve imports.
namespace margelo::nitro::camera { struct CameraOutputConfiguration; }
// Forward declaration of `MirrorMode` to properly resolve imports.
@@ -71,7 +73,11 @@ namespace margelo::nitro::camera { enum class InterruptionReason; }
#include "CameraSessionConnection.hpp"
#include "JCameraSessionConnection.hpp"
#include "HybridCameraDeviceSpec.hpp"
+#include "TargetCameraPosition.hpp"
+#include
+#include "JCameraDeviceOrPosition.hpp"
#include "JHybridCameraDeviceSpec.hpp"
+#include "JTargetCameraPosition.hpp"
#include "CameraOutputConfiguration.hpp"
#include "JCameraOutputConfiguration.hpp"
#include "MirrorMode.hpp"
@@ -86,7 +92,6 @@ namespace margelo::nitro::camera { enum class InterruptionReason; }
#include "PhotoHDRConstraint.hpp"
#include "PixelFormatConstraint.hpp"
#include "BinnedConstraint.hpp"
-#include
#include "JConstraint.hpp"
#include "JFPSConstraint.hpp"
#include "JVideoStabilizationModeConstraint.hpp"
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraDeviceOrPosition.kt b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraDeviceOrPosition.kt
new file mode 100644
index 0000000000..6189d88d5d
--- /dev/null
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraDeviceOrPosition.kt
@@ -0,0 +1,62 @@
+///
+/// CameraDeviceOrPosition.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.camera
+
+import com.facebook.proguard.annotations.DoNotStrip
+
+
+/**
+ * Represents the TypeScript variant "HybridCameraDeviceSpec | TargetCameraPosition".
+ */
+@Suppress("ClassName")
+@DoNotStrip
+sealed class CameraDeviceOrPosition {
+ @DoNotStrip
+ data class First(@DoNotStrip val value: HybridCameraDeviceSpec): CameraDeviceOrPosition()
+ @DoNotStrip
+ data class Second(@DoNotStrip val value: TargetCameraPosition): CameraDeviceOrPosition()
+
+ inline fun asType(): T? {
+ return when (this) {
+ is First -> (value) as? T
+ is Second -> (value) as? T
+ }
+ }
+ inline fun isType(): Boolean {
+ return asType() != null
+ }
+ inline fun match(first: (HybridCameraDeviceSpec) -> R, second: (TargetCameraPosition) -> R): R {
+ return when (this) {
+ is First -> first(value)
+ is Second -> second(value)
+ }
+ }
+
+ val isFirst: Boolean
+ get() = this is First
+ val isSecond: Boolean
+ get() = this is Second
+
+ fun asFirstOrNull(): HybridCameraDeviceSpec? {
+ val value = (this as? First)?.value ?: return null
+ return value
+ }
+ fun asSecondOrNull(): TargetCameraPosition? {
+ val value = (this as? Second)?.value ?: return null
+ return value
+ }
+
+ companion object {
+ @JvmStatic
+ @DoNotStrip
+ fun create(value: HybridCameraDeviceSpec): CameraDeviceOrPosition = First(value)
+ @JvmStatic
+ @DoNotStrip
+ fun create(value: TargetCameraPosition): CameraDeviceOrPosition = Second(value)
+ }
+}
diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraSessionConnection.kt b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraSessionConnection.kt
index 238fa5b981..6bb1c1d16f 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraSessionConnection.kt
+++ b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/CameraSessionConnection.kt
@@ -20,7 +20,7 @@ import java.util.Objects
data class CameraSessionConnection(
@DoNotStrip
@Keep
- val input: HybridCameraDeviceSpec,
+ val input: CameraDeviceOrPosition,
@DoNotStrip
@Keep
val outputs: Array,
@@ -40,7 +40,7 @@ data class CameraSessionConnection(
/**
* Create a new instance of CameraSessionConnection from Kotlin
*/
- constructor(input: HybridCameraDeviceSpec, outputs: Array, constraints: Array, initialZoom: Double?, initialExposureBias: Double?, onSessionConfigSelected: ((config: HybridCameraSessionConfigSpec) -> Unit)?):
+ constructor(input: CameraDeviceOrPosition, outputs: Array, constraints: Array, initialZoom: Double?, initialExposureBias: Double?, onSessionConfigSelected: ((config: HybridCameraSessionConfigSpec) -> Unit)?):
this(input, outputs, constraints, initialZoom, initialExposureBias, onSessionConfigSelected?.let { Func_void_std__shared_ptr_HybridCameraSessionConfigSpec__java(it) })
override fun equals(other: Any?): Boolean {
@@ -73,7 +73,7 @@ data class CameraSessionConnection(
@Keep
@Suppress("unused")
@JvmStatic
- private fun fromCpp(input: HybridCameraDeviceSpec, outputs: Array, constraints: Array, initialZoom: Double?, initialExposureBias: Double?, onSessionConfigSelected: Func_void_std__shared_ptr_HybridCameraSessionConfigSpec_?): CameraSessionConnection {
+ private fun fromCpp(input: CameraDeviceOrPosition, outputs: Array, constraints: Array, initialZoom: Double?, initialExposureBias: Double?, onSessionConfigSelected: Func_void_std__shared_ptr_HybridCameraSessionConfigSpec_?): CameraSessionConnection {
return CameraSessionConnection(input, outputs, constraints, initialZoom, initialExposureBias, onSessionConfigSelected)
}
}
diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp b/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp
index 91a1028a3c..9cbee63205 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp
+++ b/packages/react-native-vision-camera/nitrogen/generated/ios/VisionCamera-Swift-Cxx-Bridge.hpp
@@ -156,6 +156,8 @@ namespace margelo::nitro::camera { enum class ScannedObjectType; }
namespace margelo::nitro::camera { enum class SceneAdaptiveness; }
// Forward declaration of `Size` to properly resolve imports.
namespace margelo::nitro::camera { struct Size; }
+// Forward declaration of `TargetCameraPosition` to properly resolve imports.
+namespace margelo::nitro::camera { enum class TargetCameraPosition; }
// Forward declaration of `TargetColorRange` to properly resolve imports.
namespace margelo::nitro::camera { enum class TargetColorRange; }
// Forward declaration of `TargetColorSpace` to properly resolve imports.
@@ -324,6 +326,7 @@ namespace VisionCamera { class HybridZoomGestureControllerSpec_cxx; }
#include "ScannedObjectType.hpp"
#include "SceneAdaptiveness.hpp"
#include "Size.hpp"
+#include "TargetCameraPosition.hpp"
#include "TargetColorRange.hpp"
#include "TargetColorSpace.hpp"
#include "TargetDynamicRange.hpp"
@@ -2649,6 +2652,35 @@ namespace margelo::nitro::camera::bridge::swift {
return Func_void_std__vector_std__shared_ptr_HybridCameraControllerSpec___Wrapper(std::move(value));
}
+ // pragma MARK: std::variant, TargetCameraPosition>
+ /**
+ * Wrapper struct for `std::variant, TargetCameraPosition>`.
+ * std::variant cannot be used in Swift because of a Swift bug.
+ * Not even specializing it works. So we create a wrapper struct.
+ */
+ struct std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_ final {
+ std::variant, TargetCameraPosition> variant;
+ std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(std::variant, TargetCameraPosition> variant): variant(variant) { }
+ operator std::variant, TargetCameraPosition>() const noexcept {
+ return variant;
+ }
+ inline size_t index() const noexcept {
+ return variant.index();
+ }
+ inline std::shared_ptr get_0() const noexcept {
+ return std::get<0>(variant);
+ }
+ inline TargetCameraPosition get_1() const noexcept {
+ return std::get<1>(variant);
+ }
+ };
+ inline std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_ create_std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(const std::shared_ptr& value) noexcept {
+ return std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(value);
+ }
+ inline std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_ create_std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(TargetCameraPosition value) noexcept {
+ return std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(value);
+ }
+
// pragma MARK: std::optional& /* config */)>>
/**
* Specialized version of `std::optional& / * config * /)>>`.
diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraSessionSpecSwift.hpp b/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraSessionSpecSwift.hpp
index 9204993f94..74b2ed6d66 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraSessionSpecSwift.hpp
+++ b/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraSessionSpecSwift.hpp
@@ -18,6 +18,8 @@ namespace margelo::nitro::camera { class HybridCameraControllerSpec; }
namespace margelo::nitro::camera { struct CameraSessionConnection; }
// Forward declaration of `HybridCameraDeviceSpec` to properly resolve imports.
namespace margelo::nitro::camera { class HybridCameraDeviceSpec; }
+// Forward declaration of `TargetCameraPosition` to properly resolve imports.
+namespace margelo::nitro::camera { enum class TargetCameraPosition; }
// Forward declaration of `CameraOutputConfiguration` to properly resolve imports.
namespace margelo::nitro::camera { struct CameraOutputConfiguration; }
// Forward declaration of `MirrorMode` to properly resolve imports.
@@ -67,6 +69,8 @@ namespace margelo::nitro::camera { enum class InterruptionReason; }
#include
#include "CameraSessionConnection.hpp"
#include "HybridCameraDeviceSpec.hpp"
+#include "TargetCameraPosition.hpp"
+#include
#include "CameraOutputConfiguration.hpp"
#include "MirrorMode.hpp"
#include "HybridCameraOutputSpec.hpp"
@@ -78,7 +82,6 @@ namespace margelo::nitro::camera { enum class InterruptionReason; }
#include "PhotoHDRConstraint.hpp"
#include "PixelFormatConstraint.hpp"
#include "BinnedConstraint.hpp"
-#include
#include "TargetStabilizationMode.hpp"
#include "TargetDynamicRange.hpp"
#include "TargetDynamicRangeBitDepth.hpp"
diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraDeviceOrPosition.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraDeviceOrPosition.swift
new file mode 100644
index 0000000000..6f2c1d2c92
--- /dev/null
+++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraDeviceOrPosition.swift
@@ -0,0 +1,30 @@
+///
+/// CameraDeviceOrPosition.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+
+
+/**
+ * An Swift enum with associated values representing a Variant/Union type.
+ * JS type: `hybrid-object | enum`
+ */
+@frozen
+public indirect enum CameraDeviceOrPosition {
+ case first((any HybridCameraDeviceSpec))
+ case second(TargetCameraPosition)
+}
+
+public extension CameraDeviceOrPosition {
+ func asType(_ type: T.Type = T.self) -> T? {
+ switch self {
+ case .first(let value): return value as? T
+ case .second(let value): return value as? T
+ }
+ }
+ func isType(_ type: T.Type = T.self) -> Bool {
+ return self.asType(type) != nil
+ }
+}
diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraSessionConnection.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraSessionConnection.swift
index 696b201b89..f5bcf226e7 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraSessionConnection.swift
+++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/CameraSessionConnection.swift
@@ -18,11 +18,18 @@ public extension CameraSessionConnection {
/**
* Create a new instance of `CameraSessionConnection`.
*/
- init(input: (any HybridCameraDeviceSpec), outputs: [CameraOutputConfiguration], constraints: [Constraint], initialZoom: Double?, initialExposureBias: Double?, onSessionConfigSelected: ((_ config: (any HybridCameraSessionConfigSpec)) -> Void)?) {
- self.init({ () -> bridge.std__shared_ptr_HybridCameraDeviceSpec_ in
- let __cxxWrapped = input.getCxxWrapper()
- return __cxxWrapped.getCxxPart()
- }(), { () -> bridge.std__vector_CameraOutputConfiguration_ in
+ init(input: CameraDeviceOrPosition, outputs: [CameraOutputConfiguration], constraints: [Constraint], initialZoom: Double?, initialExposureBias: Double?, onSessionConfigSelected: ((_ config: (any HybridCameraSessionConfigSpec)) -> Void)?) {
+ self.init({ () -> bridge.std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_ in
+ switch input {
+ case .first(let __value):
+ return bridge.create_std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_({ () -> bridge.std__shared_ptr_HybridCameraDeviceSpec_ in
+ let __cxxWrapped = __value.getCxxWrapper()
+ return __cxxWrapped.getCxxPart()
+ }())
+ case .second(let __value):
+ return bridge.create_std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(__value)
+ }
+ }().variant, { () -> bridge.std__vector_CameraOutputConfiguration_ in
var __vector = bridge.create_std__vector_CameraOutputConfiguration_(outputs.count)
for __item in outputs {
__vector.push_back(__item)
@@ -78,11 +85,23 @@ public extension CameraSessionConnection {
}
@inline(__always)
- var input: (any HybridCameraDeviceSpec) {
- return { () -> any HybridCameraDeviceSpec in
- let __unsafePointer = bridge.get_std__shared_ptr_HybridCameraDeviceSpec_(self.__input)
- let __instance = HybridCameraDeviceSpec_cxx.fromUnsafe(__unsafePointer)
- return __instance.getHybridCameraDeviceSpec()
+ var input: CameraDeviceOrPosition {
+ return { () -> CameraDeviceOrPosition in
+ let __variant = bridge.std__variant_std__shared_ptr_HybridCameraDeviceSpec___TargetCameraPosition_(self.__input)
+ switch __variant.index() {
+ case 0:
+ let __actual = __variant.get_0()
+ return .first({ () -> any HybridCameraDeviceSpec in
+ let __unsafePointer = bridge.get_std__shared_ptr_HybridCameraDeviceSpec_(__actual)
+ let __instance = HybridCameraDeviceSpec_cxx.fromUnsafe(__unsafePointer)
+ return __instance.getHybridCameraDeviceSpec()
+ }())
+ case 1:
+ let __actual = __variant.get_1()
+ return .second(__actual)
+ default:
+ fatalError("Variant can never have index \(__variant.index())!")
+ }
}()
}
diff --git a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/CameraSessionConnection.hpp b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/CameraSessionConnection.hpp
index 82389224a1..49aa074570 100644
--- a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/CameraSessionConnection.hpp
+++ b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/CameraSessionConnection.hpp
@@ -30,6 +30,8 @@
// Forward declaration of `HybridCameraDeviceSpec` to properly resolve imports.
namespace margelo::nitro::camera { class HybridCameraDeviceSpec; }
+// Forward declaration of `TargetCameraPosition` to properly resolve imports.
+namespace margelo::nitro::camera { enum class TargetCameraPosition; }
// Forward declaration of `CameraOutputConfiguration` to properly resolve imports.
namespace margelo::nitro::camera { struct CameraOutputConfiguration; }
// Forward declaration of `FPSConstraint` to properly resolve imports.
@@ -53,6 +55,8 @@ namespace margelo::nitro::camera { class HybridCameraSessionConfigSpec; }
#include
#include "HybridCameraDeviceSpec.hpp"
+#include "TargetCameraPosition.hpp"
+#include
#include "CameraOutputConfiguration.hpp"
#include
#include "FPSConstraint.hpp"
@@ -63,7 +67,6 @@ namespace margelo::nitro::camera { class HybridCameraSessionConfigSpec; }
#include "PhotoHDRConstraint.hpp"
#include "PixelFormatConstraint.hpp"
#include "BinnedConstraint.hpp"
-#include
#include
#include "HybridCameraSessionConfigSpec.hpp"
#include
@@ -75,7 +78,7 @@ namespace margelo::nitro::camera {
*/
struct CameraSessionConnection final {
public:
- std::shared_ptr input SWIFT_PRIVATE;
+ std::variant, TargetCameraPosition> input SWIFT_PRIVATE;
std::vector outputs SWIFT_PRIVATE;
std::vector> constraints SWIFT_PRIVATE;
std::optional initialZoom SWIFT_PRIVATE;
@@ -84,7 +87,7 @@ namespace margelo::nitro::camera {
public:
CameraSessionConnection() = default;
- explicit CameraSessionConnection(std::shared_ptr input, std::vector outputs, std::vector> constraints, std::optional initialZoom, std::optional initialExposureBias, std::optional& /* config */)>> onSessionConfigSelected): input(input), outputs(outputs), constraints(constraints), initialZoom(initialZoom), initialExposureBias(initialExposureBias), onSessionConfigSelected(onSessionConfigSelected) {}
+ explicit CameraSessionConnection(std::variant, TargetCameraPosition> input, std::vector outputs, std::vector> constraints, std::optional initialZoom, std::optional initialExposureBias, std::optional& /* config */)>> onSessionConfigSelected): input(input), outputs(outputs), constraints(constraints), initialZoom(initialZoom), initialExposureBias(initialExposureBias), onSessionConfigSelected(onSessionConfigSelected) {}
public:
// CameraSessionConnection is not equatable because these properties are not equatable: onSessionConfigSelected
@@ -100,7 +103,7 @@ namespace margelo::nitro {
static inline margelo::nitro::camera::CameraSessionConnection fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
jsi::Object obj = arg.asObject(runtime);
return margelo::nitro::camera::CameraSessionConnection(
- JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "input"))),
+ JSIConverter, margelo::nitro::camera::TargetCameraPosition>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "input"))),
JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "outputs"))),
JSIConverter>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "constraints"))),
JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "initialZoom"))),
@@ -110,7 +113,7 @@ namespace margelo::nitro {
}
static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::camera::CameraSessionConnection& arg) {
jsi::Object obj(runtime);
- obj.setProperty(runtime, PropNameIDCache::get(runtime, "input"), JSIConverter>::toJSI(runtime, arg.input));
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "input"), JSIConverter, margelo::nitro::camera::TargetCameraPosition>>::toJSI(runtime, arg.input));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "outputs"), JSIConverter>::toJSI(runtime, arg.outputs));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "constraints"), JSIConverter>>::toJSI(runtime, arg.constraints));
obj.setProperty(runtime, PropNameIDCache::get(runtime, "initialZoom"), JSIConverter>::toJSI(runtime, arg.initialZoom));
@@ -126,7 +129,7 @@ namespace margelo::nitro {
if (!nitro::isPlainObject(runtime, obj)) {
return false;
}
- if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "input")))) return false;
+ if (!JSIConverter, margelo::nitro::camera::TargetCameraPosition>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "input")))) return false;
if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "outputs")))) return false;
if (!JSIConverter>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "constraints")))) return false;
if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "initialZoom")))) return false;
diff --git a/packages/react-native-vision-camera/src/hooks/useCameraDeviceExtensions.ts b/packages/react-native-vision-camera/src/hooks/useCameraDeviceExtensions.ts
index 8a435c9b6a..1b5cb39029 100644
--- a/packages/react-native-vision-camera/src/hooks/useCameraDeviceExtensions.ts
+++ b/packages/react-native-vision-camera/src/hooks/useCameraDeviceExtensions.ts
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
-import { getSupportedExtensions } from '../CameraDevices'
import type { CameraDevice } from '../specs/inputs/CameraDevice.nitro'
import type { CameraExtension } from '../specs/inputs/CameraExtension.nitro'
+import { useCameraDeviceFactory } from './internal/useCameraDeviceFactory'
/**
* Get a list of all available {@linkcode CameraExtension}s for this specific
@@ -16,16 +16,19 @@ import type { CameraExtension } from '../specs/inputs/CameraExtension.nitro'
export function useCameraDeviceExtensions(
device: CameraDevice | undefined,
): CameraExtension[] | undefined {
+ const factory = useCameraDeviceFactory()
const [extensions, setExtensions] = useState([])
useEffect(() => {
if (device == null) return
+ if (factory == null) return
+
const load = async () => {
- const exts = await getSupportedExtensions(device)
+ const exts = await factory.getSupportedExtensions(device)
setExtensions(exts)
}
load()
- }, [device])
+ }, [device, factory])
return extensions
}
diff --git a/packages/react-native-vision-camera/src/specs/session/CameraSession.nitro.ts b/packages/react-native-vision-camera/src/specs/session/CameraSession.nitro.ts
index 91d77addd3..02f4cd26a0 100644
--- a/packages/react-native-vision-camera/src/specs/session/CameraSession.nitro.ts
+++ b/packages/react-native-vision-camera/src/specs/session/CameraSession.nitro.ts
@@ -37,7 +37,17 @@ export type InterruptionReason =
* per output, or multi-cam {@linkcode CameraSession}s.
*
* @example
- * Create a Camera Session using the hooks:
+ * Create a Camera Session with the default 'back' CameraDevice using the hooks API:
+ * ```ts
+ * const photoOutput = ...
+ * const camera = useCamera({
+ * isActive: true,
+ * input: 'back',
+ * outputs: [photoOutput]
+ * })
+ * ```
+ * @example
+ * Create a Camera Session with a custom CameraDevice using the hooks API:
* ```ts
* const device = ...
* const photoOutput = ...
@@ -142,7 +152,22 @@ export interface CameraSession
* be used together - see {@linkcode CameraDeviceFactory.supportedMultiCamDeviceCombinations}
*
* @example
- * Creating a simple Preview + Photo connection:
+ * Creating a simple Preview + Photo connection for the default 'back' CameraDevice:
+ * ```ts
+ * const session = ...
+ * const [controller] = await session.configure([
+ * {
+ * input: 'back',
+ * outputs: [
+ * { output: previewOutput, mirrorMode: 'auto' },
+ * { output: photoOutput, mirrorMode: 'auto' },
+ * ],
+ * constraints: []
+ * }
+ * ])
+ * ```
+ * @example
+ * Creating a simple Preview + Photo connection with a custom CameraDevice:
* ```ts
* const session = ...
* const device = ...
diff --git a/packages/react-native-vision-camera/src/specs/session/CameraSessionConnection.ts b/packages/react-native-vision-camera/src/specs/session/CameraSessionConnection.ts
index 27c028123e..d76f66d4ff 100644
--- a/packages/react-native-vision-camera/src/specs/session/CameraSessionConnection.ts
+++ b/packages/react-native-vision-camera/src/specs/session/CameraSessionConnection.ts
@@ -1,9 +1,12 @@
import type { CameraFactory } from '../CameraFactory.nitro'
+import type { TargetCameraPosition } from '../common-types/CameraPosition'
import type { Constraint } from '../common-types/Constraint'
import type { CameraDevice } from '../inputs/CameraDevice.nitro'
import type { CameraOutputConfiguration } from './CameraOutputConfiguration'
import type { CameraSessionConfig } from './CameraSessionConfig.nitro'
+type CameraDeviceOrPosition = CameraDevice | TargetCameraPosition
+
/**
* Specifies a single Camera input stream connection
* streaming into zero or more outputs.
@@ -13,8 +16,12 @@ export interface CameraSessionConnection {
* The input device of this {@linkcode CameraSessionConnection}.
* This {@linkcode CameraDevice} will stream into all given
* {@linkcode outputs}.
+ *
+ * Pass a {@linkcode TargetCameraPosition} to automatically
+ * select the default {@linkcode CameraDevice} at the given
+ * position.
*/
- input: CameraDevice
+ input: CameraDeviceOrPosition
/**
* All output configurations that the given {@linkcode input}
* device will stream into.