diff --git a/docs/content/docs/multi-camera.mdx b/docs/content/docs/multi-camera.mdx index 891037c7ac..e0b2abf273 100644 --- a/docs/content/docs/multi-camera.mdx +++ b/docs/content/docs/multi-camera.mdx @@ -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 @@ -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 @@ -51,6 +72,26 @@ const [frontController, backController] = await session.configure([ await session.start() ``` -Then, ensure you display both `frontPreview` and `backPreview` in separate [``](/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 [``](/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 ( + + + + + ) +} +``` 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 ce693d37ce..5e700b83ac 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 @@ -38,6 +38,13 @@ class HybridCameraDeviceFactory( override val cameraDevices: Array get() = cameraProvider.availableCameraInfos.mapToArray { HybridCameraDevice(it) } + override val supportedMultiCamDeviceCombinations: Array> + get() { + return cameraProvider.availableConcurrentCameraInfos.mapToArray { devices -> + return@mapToArray devices.mapToArray { HybridCameraDevice(it) } + } + } + override var userPreferredCamera: HybridCameraDeviceSpec? get() { val preferredCameraId = 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 37af6deab6..42ef2f8014 100644 --- a/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraDeviceFactory.swift +++ b/packages/react-native-vision-camera/ios/Hybrid Objects/HybridCameraDeviceFactory.swift @@ -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, @@ -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 { @@ -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 diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.cpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.cpp index 3f96d33a4d..f0256d7dd8 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.cpp +++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.cpp @@ -88,6 +88,29 @@ namespace margelo::nitro::camera { static const auto method = _javaPart->javaClassStatic()->getMethod /* userPreferredCamera */)>("setUserPreferredCamera"); method(_javaPart, userPreferredCamera.has_value() ? std::dynamic_pointer_cast(userPreferredCamera.value())->getJavaPart() : nullptr); } + std::vector>> JHybridCameraDeviceFactorySpec::getSupportedMultiCamDeviceCombinations() { + static const auto method = _javaPart->javaClassStatic()->getMethod>>()>("getSupportedMultiCamDeviceCombinations"); + auto __result = method(_javaPart); + return [&]() { + size_t __size = __result->size(); + std::vector>> __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __result->getElement(__i); + __vector.push_back([&]() { + size_t __size = __element->size(); + std::vector> __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __element->getElement(__i); + __vector.push_back(__element->getJHybridCameraDeviceSpec()); + } + return __vector; + }()); + } + return __vector; + }(); + } // Methods ListenerSubscription JHybridCameraDeviceFactorySpec::addOnCameraDevicesChangedListener(const std::function>& /* newDevices */)>& listener) { diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.hpp b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.hpp index df9b7b23c1..19a60bea95 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/android/c++/JHybridCameraDeviceFactorySpec.hpp @@ -53,6 +53,7 @@ namespace margelo::nitro::camera { std::vector> getCameraDevices() override; std::optional> getUserPreferredCamera() override; void setUserPreferredCamera(const std::optional>& userPreferredCamera) override; + std::vector>> getSupportedMultiCamDeviceCombinations() override; public: // Methods diff --git a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridCameraDeviceFactorySpec.kt b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridCameraDeviceFactorySpec.kt index f610aeb748..b41306e76c 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridCameraDeviceFactorySpec.kt +++ b/packages/react-native-vision-camera/nitrogen/generated/android/kotlin/com/margelo/nitro/camera/HybridCameraDeviceFactorySpec.kt @@ -35,6 +35,10 @@ abstract class HybridCameraDeviceFactorySpec: HybridObject() { @set:DoNotStrip @set:Keep abstract var userPreferredCamera: HybridCameraDeviceSpec? + + @get:DoNotStrip + @get:Keep + abstract val supportedMultiCamDeviceCombinations: Array> // Methods abstract fun addOnCameraDevicesChangedListener(listener: (newDevices: Array) -> Unit): ListenerSubscription 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 5bde895e9d..5abcd00279 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 @@ -1426,6 +1426,17 @@ namespace margelo::nitro::camera::bridge::swift { return Result::withError(error); } + // pragma MARK: std::vector>> + /** + * Specialized version of `std::vector>>`. + */ + using std__vector_std__vector_std__shared_ptr_HybridCameraDeviceSpec___ = std::vector>>; + inline std::vector>> create_std__vector_std__vector_std__shared_ptr_HybridCameraDeviceSpec___(size_t size) noexcept { + std::vector>> vector; + vector.reserve(size); + return vector; + } + // pragma MARK: std::function>& /* newDevices */)> /** * Specialized version of `std::function>&)>`. diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraDeviceFactorySpecSwift.hpp b/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraDeviceFactorySpecSwift.hpp index a13436fca5..f166161e87 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraDeviceFactorySpecSwift.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/c++/HybridCameraDeviceFactorySpecSwift.hpp @@ -89,6 +89,10 @@ namespace margelo::nitro::camera { inline void setUserPreferredCamera(const std::optional>& userPreferredCamera) noexcept override { _swiftPart.setUserPreferredCamera(userPreferredCamera); } + inline std::vector>> getSupportedMultiCamDeviceCombinations() noexcept override { + auto __result = _swiftPart.getSupportedMultiCamDeviceCombinations(); + return __result; + } public: // Methods diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec.swift index 62708a1149..ce0ba0d904 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec.swift +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec.swift @@ -12,6 +12,7 @@ public protocol HybridCameraDeviceFactorySpec_protocol: HybridObject { // Properties var cameraDevices: [(any HybridCameraDeviceSpec)] { get } var userPreferredCamera: (any HybridCameraDeviceSpec)? { get set } + var supportedMultiCamDeviceCombinations: [[(any HybridCameraDeviceSpec)]] { get } // Methods func addOnCameraDevicesChangedListener(listener: @escaping (_ newDevices: [(any HybridCameraDeviceSpec)]) -> Void) throws -> ListenerSubscription diff --git a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec_cxx.swift b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec_cxx.swift index 658132f915..ae6fb283ad 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec_cxx.swift +++ b/packages/react-native-vision-camera/nitrogen/generated/ios/swift/HybridCameraDeviceFactorySpec_cxx.swift @@ -167,6 +167,28 @@ open class HybridCameraDeviceFactorySpec_cxx { }() } } + + public final var supportedMultiCamDeviceCombinations: bridge.std__vector_std__vector_std__shared_ptr_HybridCameraDeviceSpec___ { + @inline(__always) + get { + return { () -> bridge.std__vector_std__vector_std__shared_ptr_HybridCameraDeviceSpec___ in + var __vector = bridge.create_std__vector_std__vector_std__shared_ptr_HybridCameraDeviceSpec___(self.__implementation.supportedMultiCamDeviceCombinations.count) + for __item in self.__implementation.supportedMultiCamDeviceCombinations { + __vector.push_back({ () -> bridge.std__vector_std__shared_ptr_HybridCameraDeviceSpec__ in + var __vector = bridge.create_std__vector_std__shared_ptr_HybridCameraDeviceSpec__(__item.count) + for __item in __item { + __vector.push_back({ () -> bridge.std__shared_ptr_HybridCameraDeviceSpec_ in + let __cxxWrapped = __item.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }()) + } + return __vector + }()) + } + return __vector + }() + } + } // Methods @inline(__always) diff --git a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.cpp b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.cpp index d50c1b5a4f..f0435f7356 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.cpp +++ b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.cpp @@ -17,6 +17,7 @@ namespace margelo::nitro::camera { prototype.registerHybridGetter("cameraDevices", &HybridCameraDeviceFactorySpec::getCameraDevices); prototype.registerHybridGetter("userPreferredCamera", &HybridCameraDeviceFactorySpec::getUserPreferredCamera); prototype.registerHybridSetter("userPreferredCamera", &HybridCameraDeviceFactorySpec::setUserPreferredCamera); + prototype.registerHybridGetter("supportedMultiCamDeviceCombinations", &HybridCameraDeviceFactorySpec::getSupportedMultiCamDeviceCombinations); prototype.registerHybridMethod("addOnCameraDevicesChangedListener", &HybridCameraDeviceFactorySpec::addOnCameraDevicesChangedListener); prototype.registerHybridMethod("getCameraForId", &HybridCameraDeviceFactorySpec::getCameraForId); prototype.registerHybridMethod("getSupportedExtensions", &HybridCameraDeviceFactorySpec::getSupportedExtensions); diff --git a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.hpp b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.hpp index 823a998870..cde15be396 100644 --- a/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.hpp +++ b/packages/react-native-vision-camera/nitrogen/generated/shared/c++/HybridCameraDeviceFactorySpec.hpp @@ -63,6 +63,7 @@ namespace margelo::nitro::camera { virtual std::vector> getCameraDevices() = 0; virtual std::optional> getUserPreferredCamera() = 0; virtual void setUserPreferredCamera(const std::optional>& userPreferredCamera) = 0; + virtual std::vector>> getSupportedMultiCamDeviceCombinations() = 0; public: // Methods diff --git a/packages/react-native-vision-camera/src/specs/inputs/CameraDeviceFactory.nitro.ts b/packages/react-native-vision-camera/src/specs/inputs/CameraDeviceFactory.nitro.ts index 0a6f3c0852..6d04537536 100644 --- a/packages/react-native-vision-camera/src/specs/inputs/CameraDeviceFactory.nitro.ts +++ b/packages/react-native-vision-camera/src/specs/inputs/CameraDeviceFactory.nitro.ts @@ -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' @@ -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[] /** @@ -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, 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 6b19b9ab29..8390dac2b9 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 @@ -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 @@ -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)}), @@ -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: @@ -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