Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 50 additions & 9 deletions docs/content/docs/multi-camera.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Using multiple Camera Devices in a single Camera Session

import { Tab, Tabs } from 'fumadocs-ui/components/tabs'

A [`CameraSession`](/api/react-native-vision-camera/hybrid-objects/CameraSession) allows attaching multiple connections to stream from multiple [`CameraDevice`](/api/react-native-vision-camera/hybrid-objects/CameraDevice)s at the same time - if the system supports it.
A [`CameraSession`](/api/react-native-vision-camera/hybrid-objects/CameraSession) allows attaching multiple connections to stream from multiple [`CameraDevice`](/api/react-native-vision-camera/hybrid-objects/CameraDevice)s at the same time (e.g. Picture-in-Picture mode via front + back Camera) - if the system supports it.

### Creating a Multi-Camera Session

Expand All @@ -19,14 +19,35 @@ if (VisionCamera.supportsMultiCamSessions) {

### Using multiple Connections

With a multi-cam [`CameraSession`](/api/react-native-vision-camera/hybrid-objects/CameraSession), you can now attach multiple [`CameraSessionConnection`](/api/react-native-vision-camera/interfaces/CameraSessionConnection)s - for example to stream and capture from the Front- and Back-Camera at the same time, attach both devices:
Due to hardware constraints, not every [`CameraDevice`](/api/react-native-vision-camera/hybrid-objects/CameraDevice) can be paired with every other [`CameraDevice`](/api/react-native-vision-camera/hybrid-objects/CameraDevice) - therefore VisionCamera exposes a fixed array of supported combinations via [`CameraDeviceFactory.supportedMultiCamDeviceCombinations`](/api/react-native-vision-camera/hybrid-objects/CameraDeviceFactory#supportedmulticamdevicecombinations) upfront:

```ts
const session = ...
const frontDevice = useCameraDevice('front')
const backDevice = useCameraDevice('back')
const frontPreview = usePreviewOutput()
const backPreview = usePreviewOutput()
if (!VisionCamera.supportsMultiCamSessions)
return

const deviceFactory = await VisionCamera.createDeviceFactory()
const frontAndBackCombination =
deviceFactory.supportedMultiCamDeviceCombinations.find((devices) => {
return (
devices.some((d) => d.position === 'front') &&
devices.some((d) => d.position === 'back')
)
})
if (frontAndBackCombination == null)
return

const frontDevice = frontAndBackCombination.find((d) => d.position === 'front')
const backDevice = frontAndBackCombination.find((d) => d.position === 'back')
```

Then, knowing `frontDevice` and `backDevice` can be used simultaneously in a Multi-Cam session, create the [`CameraSession`](/api/react-native-vision-camera/hybrid-objects/CameraSession), and attach the [`CameraSessionConnection`](/api/react-native-vision-camera/interfaces/CameraSessionConnection)s:

```ts
const session = await VisionCamera.createCameraSession(true)
const frontPreviewOutput = VisionCamera.createPreviewOutput()
const frontPhotoOutput = VisionCamera.createPhotoOutput({})
const backPreviewOutput = VisionCamera.createPreviewOutput()
const backPhotoOutput = VisionCamera.createPhotoOutput({})

const [frontController, backController] = await session.configure([
// Front Camera
Expand All @@ -51,6 +72,26 @@ const [frontController, backController] = await session.configure([
await session.start()
```

Then, ensure you display both `frontPreview` and `backPreview` in separate [`<NativePreviewView />`](/api/react-native-vision-camera/views/NativePreviewView) views.
Each returned [`CameraController`](/api/react-native-vision-camera/hybrid-objects/CameraController) correlates to the connection at that index - e.g. `frontController` allows zooming/exposure/focus the `frontDevice`, and vice-versa.

Then, ensure you display both `frontPreviewOutput` and `backPreviewOutput` in separate [`<NativePreviewView />`](/api/react-native-vision-camera/views/NativePreviewView) views:

```tsx
function App() {
const frontPreviewOutput = ...
const backPreviewOutput = ...

Each returned [`CameraController`](/api/react-native-vision-camera/hybrid-objects/CameraController) correlates to the connection at that index.
return (
<View style={StyleSheet.absoluteFill}>
<NativePreviewView
style={{ flex: 1 }}
previewOutput={frontPreviewOutput}
/>
<NativePreviewView
style={{ flex: 1 }}
previewOutput={backPreviewOutput}
/>
</View>
)
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ class HybridCameraDeviceFactory(
override val cameraDevices: Array<HybridCameraDeviceSpec>
get() = cameraProvider.availableCameraInfos.mapToArray { HybridCameraDevice(it) }

override val supportedMultiCamDeviceCombinations: Array<Array<HybridCameraDeviceSpec>>
get() {
return cameraProvider.availableConcurrentCameraInfos.mapToArray { devices ->
return@mapToArray devices.mapToArray { HybridCameraDevice(it) }
}
}

override var userPreferredCamera: HybridCameraDeviceSpec?
get() {
val preferredCameraId =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ import Foundation
import NitroModules

class HybridCameraDeviceFactory: HybridCameraDeviceFactorySpec {
let discoverySession: AVCaptureDevice.DiscoverySession
var cameraDevices: [any HybridCameraDeviceSpec] {
return discoverySession.devices.map { HybridCameraDevice(device: $0) }
}

override init() {
self.discoverySession = AVCaptureDevice.DiscoverySession(
deviceTypes: AVCaptureDevice.DeviceType.all,
Expand All @@ -22,6 +17,17 @@ class HybridCameraDeviceFactory: HybridCameraDeviceFactorySpec {
super.init()
}

let discoverySession: AVCaptureDevice.DiscoverySession
var cameraDevices: [any HybridCameraDeviceSpec] {
return discoverySession.devices.map { HybridCameraDevice(device: $0) }
}

var supportedMultiCamDeviceCombinations: [[any HybridCameraDeviceSpec]] {
return discoverySession.supportedMultiCamDeviceSets.map { devices in
return devices.map { HybridCameraDevice(device: $0) }
}
}

var userPreferredCamera: (any HybridCameraDeviceSpec)? {
get {
guard #available(iOS 17.0, *) else {
Expand All @@ -36,7 +42,7 @@ class HybridCameraDeviceFactory: HybridCameraDeviceFactorySpec {
guard #available(iOS 17.0, *) else {
return
}
guard let hybridDevice = newValue as? HybridCameraDevice else {
guard let hybridDevice = newValue as? any NativeCameraDevice else {
return
}
AVCaptureDevice.userPreferredCamera = hybridDevice.device
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { HybridObject } from 'react-native-nitro-modules'
import type { CameraFactory } from '../CameraFactory.nitro'
import type { CameraPosition } from '../common-types/CameraPosition'
import type { ListenerSubscription } from '../common-types/ListenerSubscription'
import type { CameraSession } from '../session/CameraSession.nitro'
Expand All @@ -12,8 +13,12 @@ import type { CameraExtension } from './CameraExtension.nitro'
export interface CameraDeviceFactory
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
/**
* Get a list of all devices.
* This list may change as camera devices get plugged in/out.
* Get a list of all {@linkcode CameraDevice}s on this platform.
*
* This list may change as {@linkcode CameraDevice}s get plugged in/out (e.g.
* {@linkcode CameraPosition | 'external'} Cameras via USB/UVC), devices
* become available/unavailable (e.g. continuity Cameras), or Camera positions
* change (e.g. on foldable phones).
*/
readonly cameraDevices: CameraDevice[]
/**
Expand All @@ -22,6 +27,64 @@ export interface CameraDeviceFactory
*/
userPreferredCamera?: CameraDevice

/**
* A list of all {@linkcode CameraDevice} combinations that are supported
* in Multi-Cam {@linkcode CameraSession}s.
*
* This list always contains a subset of {@linkcode cameraDevices}, often
* less.
*
* @discussion
* For example, on many platforms only a {@linkcode CameraPosition | 'front'}
* and a {@linkcode CameraPosition | 'back'} {@linkcode CameraDevice} are
* supported to be used in a Multi-Cam {@linkcode CameraSession} - in this case,
* the returned 2D Array looks something like this:
* ```
* [
* [{ position: 'back', ... }, { position: 'front', ... }]
* ]
* ```
* Two {@linkcode CameraPosition | 'back'}-, or two {@linkcode CameraPosition | 'front'}
* {@linkcode CameraDevice}s are often not supported together in a Multi-Cam
* {@linkcode CameraSession}.
*
* When creating a Multi-Cam {@linkcode CameraSession}, you must ensure
* that you are using Device combinations that are actually supported
* on the platform, otherwise the session might fail to configure.
*
* @discussion
* If the platform does not support Multi-Cam {@linkcode CameraSession}s,
* an empty array (`[]`) will be returned.
*
*
* @example
* ```ts
* if (VisionCamera.supportsMultiCamSessions) {
* const deviceFactory = await VisionCamera.createDeviceFactory()
* const deviceCombinations = deviceFactory.supportedMultiCamDeviceCombinations[0]
* if (deviceCombinations != null) {
* const connections = deviceCombinations.map((device) => {
* const previewOutput = VisionCamera.createPreviewOutput()
* return {
* input: device,
* outputs: [
* { output: previewOutput, mirrorMode: 'auto' }
* ],
* constraints: []
* } satisfies CameraSessionConnection
* })
*
* const session = await VisionCamera.createCameraSession(true)
* const controllers = await session.configure(connections)
* await session.start()
* }
* }
* ```
*
* @see {@linkcode CameraFactory.supportsMultiCamSessions}
*/
readonly supportedMultiCamDeviceCombinations: CameraDevice[][]

/**
* Add a listener to be called whenever the
* available {@linkcode cameraDevices} change,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export type InterruptionReason =
* and back-Camera using the imperative API:
* ```ts
* if (VisionCamera.supportsMultiCamSessions) {
* const deviceFactory = await VisionCamera.createDeviceFactory()
* const deviceCombinations = deviceFactory.supportedMultiCamDeviceCombinations
* // ... get `frontDevice` and `backDevice` from one of
* // the combinations in `deviceCombinations`.
* const session = await VisionCamera.createCameraSession(true)
* const [frontController, backController] = await session.configure([
* // Front Camera
Expand Down Expand Up @@ -110,6 +114,12 @@ export interface CameraSession
* from one {@linkcode CameraDevice} (the _input_) to multiple
* {@linkcode CameraOutput}s (the _outputs_).
*
* @note In a Multi-Cam Camera Session, the given {@linkcode connections}' input devices
* must be supported to be used together in a Multi-Cam Camera Session, as not every
* {@linkcode CameraDevice} can be used with every other {@linkcode CameraDevice}
* simultaneously. See {@linkcode CameraDeviceFactory.supportedMultiCamDeviceCombinations}
* for a full list of supported combinations.
*
* @param connections The list of connections from one _input_ to
* multiple _outputs_. If this {@linkcode CameraSession} was created
* as a multi-cam session (see {@linkcode CameraFactory.createCameraSession | createCameraSession(enableMultiCam)}),
Expand All @@ -122,6 +132,9 @@ export interface CameraSession
* @throws If Camera Permission has not been granted - see {@linkcode CameraFactory.cameraPermissionStatus}
* @throws If multiple {@linkcode connections} are added, but the
* {@linkcode CameraSession} was not created as a multi-cam session.
* @throws If hardware resources are being exhausted by too large connection graphs.
* @throws If this is a Multi-Cam session, but the connection inputs are not supported to
* be used together - see {@linkcode CameraDeviceFactory.supportedMultiCamDeviceCombinations}
*
* @example
* Creating a simple Preview + Photo connection:
Expand Down Expand Up @@ -162,6 +175,10 @@ export interface CameraSession
* Creating a multi-cam session with front- and back Camera:
* ```ts
* if (VisionCamera.supportsMultiCamSessions) {
* const deviceFactory = await VisionCamera.createDeviceFactory()
* const deviceCombinations = deviceFactory.supportedMultiCamDeviceCombinations
* // ... get `frontDevice` and `backDevice` from one of
* // the combinations in `deviceCombinations`.
* const session = await VisionCamera.createCameraSession(true)
* const [frontController, backController] = await session.configure([
* // Front Camera
Expand Down
Loading