diff --git a/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts b/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts index c05f3b67a1..0d2c218971 100644 --- a/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts @@ -489,10 +489,7 @@ describe('VisionCamera - Constraints', () => { expect(config.selectedVideoDynamicRange?.bitDepth).toBe('hdr-10-bit') }) - it('resolves a video stabilization constraint when supported', async (context) => { - // The resolver downgrades stabilization modes (cinematic → standard → off) - // when the requested one isn't supported, so picking the most demanding - // mode the device exposes gives the test the most coverage. + it('configures and starts cinematic video stabilization when supported', async (context) => { const stabDevice = factory.cameraDevices.find((d) => d.supportsVideoStabilizationMode('cinematic'), ) @@ -505,12 +502,47 @@ describe('VisionCamera - Constraints', () => { targetResolution: CommonResolutions.HD_16_9, enableAudio: false, }) - const config = await VisionCamera.resolveConstraints( - stabDevice, - [{ output: videoOutput, mirrorMode: 'auto' }], - [{ videoStabilizationMode: 'cinematic' }], - ) - expect(config.selectedVideoStabilizationMode).toBe('cinematic') + const session = await VisionCamera.createCameraSession(false) + let selectedConfig: CameraSessionConfig | undefined + let didStart = false + let sessionError: Error | undefined + const startSub = session.addOnStartedListener(() => { + didStart = true + }) + const errorSub = session.addOnErrorListener((error) => { + sessionError = error + }) + + try { + await session.configure([ + { + input: stabDevice, + outputs: [{ output: videoOutput, mirrorMode: 'auto' }], + constraints: [{ videoStabilizationMode: 'cinematic' }], + onSessionConfigSelected: (config) => { + selectedConfig = config + }, + }, + ]) + await session.start() + await waitUntil( + () => { + if (sessionError != null) throw sessionError + return ( + selectedConfig != null && + didStart && + videoOutput.currentResolution != null + ) + }, + { timeout: 10_000 }, + ) + + expect(selectedConfig?.selectedVideoStabilizationMode).toBe('cinematic') + } finally { + startSub.remove() + errorSub.remove() + await session.stop() + } }) it('resolves a preview stabilization constraint when supported', async (context) => { @@ -572,23 +604,49 @@ describe('VisionCamera - Constraints', () => { const session = await VisionCamera.createCameraSession(false) let sessionConfig: CameraSessionConfig | undefined - await session.configure([ - { - input: backDevice, - outputs: [outputConfig], - constraints, - onSessionConfigSelected: (config) => { - sessionConfig = config + let sessionError: Error | undefined + const errorSub = session.addOnErrorListener((error) => { + sessionError = error + }) + + try { + await session.configure([ + { + input: backDevice, + outputs: [outputConfig], + constraints, + onSessionConfigSelected: (config) => { + sessionConfig = config + }, }, - }, - ]) - await waitUntil(() => sessionConfig != null, { timeout: 5_000 }) - expect(sessionConfig?.selectedFPS).toBe(standalone.selectedFPS) - expect(sessionConfig?.nativePixelFormat).toBe(standalone.nativePixelFormat) - expect(sessionConfig?.isPhotoHDREnabled).toBe(standalone.isPhotoHDREnabled) - expect(sessionConfig?.isBinned).toBe(standalone.isBinned) - - await session.stop() + ]) + await waitUntil( + () => { + if (sessionError != null) throw sessionError + return sessionConfig != null + }, + { timeout: 5_000 }, + ) + if (sessionConfig == null) throw new Error('no selected config') + + expect(sessionConfig.selectedFPS).toBe(standalone.selectedFPS) + expect(sessionConfig.selectedVideoStabilizationMode).toBe( + standalone.selectedVideoStabilizationMode, + ) + expect(sessionConfig.selectedPreviewStabilizationMode).toBe( + standalone.selectedPreviewStabilizationMode, + ) + expect(sessionConfig.selectedVideoDynamicRange).toEqual( + standalone.selectedVideoDynamicRange, + ) + expect(sessionConfig.isPhotoHDREnabled).toBe(standalone.isPhotoHDREnabled) + expect(sessionConfig.nativePixelFormat).toBe(standalone.nativePixelFormat) + expect(sessionConfig.autoFocusSystem).toBe(standalone.autoFocusSystem) + expect(sessionConfig.isBinned).toBe(standalone.isBinned) + } finally { + errorSub.remove() + await session.stop() + } }) // Verifies the resolver's priority mechanism by running the same pair of @@ -652,7 +710,7 @@ describe('VisionCamera - Constraints', () => { expect(hdrFirst.selectedVideoDynamicRange?.bitDepth).toBe('hdr-10-bit') }) - it('reconfigures the running session with a different constraint set', async (context) => { + it('selects 60 fps while reconfiguring a running session', async (context) => { if (!backDevice.supportsFPS(60)) { return context.skip( 'reconfigure with new constraints: fps: 60 not supported', @@ -665,28 +723,41 @@ describe('VisionCamera - Constraints', () => { enableAudio: false, }) + let didStart = false let sessionError: Error | undefined + const startSub = session.addOnStartedListener(() => { + didStart = true + }) const errorSub = session.addOnErrorListener((error) => { sessionError = error }) - let firstConfig: CameraSessionConfig | undefined - await session.configure([ - { - input: backDevice, - outputs: [{ output: videoOutput, mirrorMode: 'auto' }], - constraints: [{ fps: 30 }], - onSessionConfigSelected: (config) => { - firstConfig = config + try { + let firstConfig: CameraSessionConfig | undefined + await session.configure([ + { + input: backDevice, + outputs: [{ output: videoOutput, mirrorMode: 'auto' }], + constraints: [{ fps: 30 }], + onSessionConfigSelected: (config) => { + firstConfig = config + }, }, - }, - ]) - await waitUntil(() => firstConfig != null, { timeout: 5_000 }) - expect(firstConfig?.selectedFPS).toBe(30) - - await session.start() + ]) + await session.start() + await waitUntil( + () => { + if (sessionError != null) throw sessionError + return ( + firstConfig != null && + didStart && + videoOutput.currentResolution != null + ) + }, + { timeout: 10_000 }, + ) + expect(firstConfig?.selectedFPS).toBe(30) - try { let secondConfig: CameraSessionConfig | undefined await session.configure([ { @@ -698,11 +769,16 @@ describe('VisionCamera - Constraints', () => { }, }, ]) - await waitUntil(() => secondConfig != null, { timeout: 5_000 }) + await waitUntil( + () => { + if (sessionError != null) throw sessionError + return secondConfig != null + }, + { timeout: 5_000 }, + ) expect(secondConfig?.selectedFPS).toBe(60) - - expect(sessionError).toBe(undefined) } finally { + startSub.remove() errorSub.remove() await session.stop() } diff --git a/apps/simple-camera/__tests__/visioncamera.frame-converter.harness.ts b/apps/simple-camera/__tests__/visioncamera.frame-converter.harness.ts index c7d0a0a9e7..efa2f5cc84 100644 --- a/apps/simple-camera/__tests__/visioncamera.frame-converter.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.frame-converter.harness.ts @@ -163,9 +163,6 @@ describe('VisionCamera - Frame Converter', () => { } expectRawPixelsToBeEqual(syncPixels, asyncPixels) - console.log( - `Frame Converter: target=${outputOrientation}, frame=${frame.orientation}, mirrored=${frame.isMirrored}, size=${syncPixels.width}x${syncPixels.height}`, - ) } finally { isWaitingForFrame = false runtime.setOnFrameCallback(frameOutput, undefined) diff --git a/apps/simple-camera/__tests__/visioncamera.multi-output.harness.ts b/apps/simple-camera/__tests__/visioncamera.multi-output.harness.ts index 93912a6431..a47b724ccd 100644 --- a/apps/simple-camera/__tests__/visioncamera.multi-output.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.multi-output.harness.ts @@ -256,18 +256,6 @@ describe('VisionCamera - Multi-Output', () => { sessionError = error }) - await session.configure([ - { - input: backDevice, - outputs: [ - { output: firstPhotoOutput, mirrorMode: 'auto' }, - { output: videoOutput, mirrorMode: 'auto' }, - { output: frameOutput, mirrorMode: 'auto' }, - ], - constraints: [], - }, - ]) - const runtime = workletsProvider.createRuntimeForThread(frameOutput.thread) runtime.setOnFrameCallback(frameOutput, (frame) => { 'worklet' @@ -275,9 +263,20 @@ describe('VisionCamera - Multi-Output', () => { frame.dispose() }) - await session.start() - try { + await session.configure([ + { + input: backDevice, + outputs: [ + { output: firstPhotoOutput, mirrorMode: 'auto' }, + { output: videoOutput, mirrorMode: 'auto' }, + { output: frameOutput, mirrorMode: 'auto' }, + ], + constraints: [], + }, + ]) + await session.start() + const secondPhotoOutput = VisionCamera.createPhotoOutput({ targetResolution: CommonResolutions.FHD_4_3, containerFormat: 'jpeg', @@ -314,15 +313,14 @@ describe('VisionCamera - Multi-Output', () => { await recorder.stopRecording() await withTimeout(finished.promise, 15_000, 'finish') - // The untouched frame output still streams. + // The untouched native frame output still streams. const framesAtCheck = framesReceived await waitUntil( () => framesReceived > framesAtCheck + 2 || sessionError != null, { timeout: 15_000 }, ) - expect(framesReceived).toBeGreaterThan(framesAtCheck) - expect(sessionError).toBe(undefined) + expect(framesReceived).toBeGreaterThan(framesAtCheck) } finally { runtime.setOnFrameCallback(frameOutput, undefined) errorSub.remove() @@ -361,18 +359,6 @@ describe('VisionCamera - Multi-Output', () => { sessionError = error }) - await session.configure([ - { - input: backDevice, - outputs: [ - { output: photoOutput, mirrorMode: 'auto' }, - { output: firstVideoOutput, mirrorMode: 'auto' }, - { output: frameOutput, mirrorMode: 'auto' }, - ], - constraints: [], - }, - ]) - const runtime = workletsProvider.createRuntimeForThread(frameOutput.thread) runtime.setOnFrameCallback(frameOutput, (frame) => { 'worklet' @@ -380,9 +366,20 @@ describe('VisionCamera - Multi-Output', () => { frame.dispose() }) - await session.start() - try { + await session.configure([ + { + input: backDevice, + outputs: [ + { output: photoOutput, mirrorMode: 'auto' }, + { output: firstVideoOutput, mirrorMode: 'auto' }, + { output: frameOutput, mirrorMode: 'auto' }, + ], + constraints: [], + }, + ]) + await session.start() + const secondVideoOutput = VisionCamera.createVideoOutput({ targetResolution: CommonResolutions.FHD_16_9, enableAudio: false, @@ -417,15 +414,14 @@ describe('VisionCamera - Multi-Output', () => { expect(photo.height).toBeGreaterThan(0) photo.dispose() - // The untouched frame output still streams. + // The untouched native frame output still streams. const framesAtCheck = framesReceived await waitUntil( () => framesReceived > framesAtCheck + 2 || sessionError != null, { timeout: 15_000 }, ) - expect(framesReceived).toBeGreaterThan(framesAtCheck) - expect(sessionError).toBe(undefined) + expect(framesReceived).toBeGreaterThan(framesAtCheck) } finally { runtime.setOnFrameCallback(frameOutput, undefined) errorSub.remove() @@ -460,18 +456,6 @@ describe('VisionCamera - Multi-Output', () => { sessionError = error }) - await session.configure([ - { - input: backDevice, - outputs: [ - { output: photoOutput, mirrorMode: 'auto' }, - { output: videoOutput, mirrorMode: 'auto' }, - { output: yuvFrameOutput, mirrorMode: 'auto' }, - ], - constraints: [], - }, - ]) - let yuvIsPlanar: boolean | undefined const reportYuv = (planar: boolean) => { yuvIsPlanar = planar @@ -485,9 +469,20 @@ describe('VisionCamera - Multi-Output', () => { frame.dispose() }) - await session.start() - try { + await session.configure([ + { + input: backDevice, + outputs: [ + { output: photoOutput, mirrorMode: 'auto' }, + { output: videoOutput, mirrorMode: 'auto' }, + { output: yuvFrameOutput, mirrorMode: 'auto' }, + ], + constraints: [], + }, + ]) + await session.start() + await waitUntil(() => yuvIsPlanar != null || sessionError != null, { timeout: 15_000, }) @@ -530,7 +525,6 @@ describe('VisionCamera - Multi-Output', () => { scheduleOnRN(reportRgb, frame.isPlanar) frame.dispose() }) - try { await waitUntil(() => rgbIsPlanar != null || sessionError != null, { timeout: 15_000, @@ -554,10 +548,12 @@ describe('VisionCamera - Multi-Output', () => { await sleep(500) await recorder.stopRecording() await withTimeout(finished.promise, 15_000, 'finish') + expect(sessionError).toBe(undefined) } finally { rgbRuntime.setOnFrameCallback(rgbFrameOutput, undefined) } } finally { + yuvRuntime.setOnFrameCallback(yuvFrameOutput, undefined) errorSub.remove() await session.stop() } diff --git a/apps/simple-camera/__tests__/visioncamera.resizer.harness.ts b/apps/simple-camera/__tests__/visioncamera.resizer.harness.ts index 649453890b..e7856ef3d4 100644 --- a/apps/simple-camera/__tests__/visioncamera.resizer.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.resizer.harness.ts @@ -256,10 +256,6 @@ describe('VisionCamera - Resizer', () => { resized.dispose() } } - - console.log( - `Resizer: target=${outputOrientation}, frame=${frame.orientation}, mirrored=${frame.isMirrored}`, - ) } finally { isWaitingForFrame = false runtime.setOnFrameCallback(frameOutput, undefined) diff --git a/apps/simple-camera/__tests__/visioncamera.session.harness.ts b/apps/simple-camera/__tests__/visioncamera.session.harness.ts index f942cf95d1..e59ed8dd68 100644 --- a/apps/simple-camera/__tests__/visioncamera.session.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.session.harness.ts @@ -348,33 +348,35 @@ describe('VisionCamera - Session', () => { qualityPrioritization: 'balanced', }) - await session.configure([ - { - input: device, - outputs: [{ output: photoOutput, mirrorMode: 'auto' }], - constraints: [], - }, - ]) - await session.start() - - const videoOutput = VisionCamera.createVideoOutput({ - targetResolution: CommonResolutions.HD_16_9, - enableAudio: false, - }) + try { + await session.configure([ + { + input: device, + outputs: [{ output: photoOutput, mirrorMode: 'auto' }], + constraints: [], + }, + ]) + await session.start() - const controllers = await session.configure([ - { - input: device, - outputs: [ - { output: photoOutput, mirrorMode: 'auto' }, - { output: videoOutput, mirrorMode: 'auto' }, - ], - constraints: [], - }, - ]) - expect(controllers).toHaveLength(1) + const videoOutput = VisionCamera.createVideoOutput({ + targetResolution: CommonResolutions.HD_16_9, + enableAudio: false, + }) - await session.stop() + const controllers = await session.configure([ + { + input: device, + outputs: [ + { output: photoOutput, mirrorMode: 'auto' }, + { output: videoOutput, mirrorMode: 'auto' }, + ], + constraints: [], + }, + ]) + expect(controllers).toHaveLength(1) + } finally { + await session.stop() + } }) it('supports a multi-cam session when the platform allows it', async (context) => { diff --git a/apps/simple-camera/__tests__/visioncamera.video.harness.ts b/apps/simple-camera/__tests__/visioncamera.video.harness.ts index c94b0286c1..33aeb1d0cf 100644 --- a/apps/simple-camera/__tests__/visioncamera.video.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.video.harness.ts @@ -650,50 +650,30 @@ describe('VisionCamera - Video', () => { } }) - it('returns supported video codecs on iOS after the output is attached', async (context) => { + it('accepts advertised video output settings on iOS', async (context) => { if (Platform.OS !== 'ios') { - return context.skip('getSupportedVideoCodecs: iOS only') + return context.skip('video output settings: iOS only') } const session = await VisionCamera.createCameraSession(false) const videoOutput = VisionCamera.createVideoOutput({ targetResolution: CommonResolutions.HD_16_9, enableAudio: false, }) - await session.configure([ - { - input: backDevice, - outputs: [{ output: videoOutput, mirrorMode: 'auto' }], - constraints: [], - }, - ]) - const codecs = videoOutput.getSupportedVideoCodecs() - expect(codecs.length).toBeGreaterThan(0) - expect(codecs).not.toContain('unknown') - await session.stop() - }) - - it('applies video output settings on iOS after the output is attached', async (context) => { - if (Platform.OS !== 'ios') { - return context.skip('setOutputSettings: iOS only') - } - const session = await VisionCamera.createCameraSession(false) - const videoOutput = VisionCamera.createVideoOutput({ - targetResolution: CommonResolutions.HD_16_9, - enableAudio: false, - }) - await session.configure([ - { - input: backDevice, - outputs: [{ output: videoOutput, mirrorMode: 'auto' }], - constraints: [], - }, - ]) try { - await videoOutput.setOutputSettings({}) + await session.configure([ + { + input: backDevice, + outputs: [{ output: videoOutput, mirrorMode: 'auto' }], + constraints: [], + }, + ]) const codecs = videoOutput.getSupportedVideoCodecs() expect(codecs.length).toBeGreaterThan(0) + expect(codecs).not.toContain('unknown') + + await videoOutput.setOutputSettings({}) for (const codec of codecs) { await videoOutput.setOutputSettings({ codec }) }