From f30b845f2870a41201c60a668c6891da36c3daa5 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Thu, 25 Jun 2026 16:50:50 +0200 Subject: [PATCH 1/2] chore: Test 4k video + preview constraint bias --- .../visioncamera.constraints.harness.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts b/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts index ddb6a82684..c24eea22a6 100644 --- a/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts @@ -109,6 +109,122 @@ describe('VisionCamera - Constraints', () => { await session.stop() }) + it('keeps 4k video at 60 fps when a preview output is attached', async (context) => { + const targetResolution = CommonResolutions.UHD_16_9 + const targetShortEdge = Math.min( + targetResolution.width, + targetResolution.height, + ) + const targetLongEdge = Math.max( + targetResolution.width, + targetResolution.height, + ) + const supportsUhdVideo = backDevice + .getSupportedResolutions('video') + .some((resolution) => { + const shortEdge = Math.min(resolution.width, resolution.height) + const longEdge = Math.max(resolution.width, resolution.height) + return shortEdge === targetShortEdge && longEdge === targetLongEdge + }) + if (!supportsUhdVideo) { + return context.skip('4k video resolution not supported on this device') + } + if (!backDevice.supportsFPS(60)) { + return context.skip('fps: 60 not supported on this device') + } + + async function resolveVideoSession(includePreview: boolean) { + const session = await VisionCamera.createCameraSession(false) + const videoOutput = VisionCamera.createVideoOutput({ + targetResolution, + enableAudio: false, + enableHigherResolutionCodecs: true, + }) + const previewOutput = includePreview + ? VisionCamera.createPreviewOutput() + : undefined + let selectedConfig: CameraSessionConfig | undefined + + await session.configure([ + { + input: backDevice, + outputs: + previewOutput == null + ? [{ output: videoOutput, mirrorMode: 'auto' }] + : [ + { output: previewOutput, mirrorMode: 'auto' }, + { output: videoOutput, mirrorMode: 'auto' }, + ], + constraints: + previewOutput == null + ? [{ fps: 60 }, { resolutionBias: videoOutput }] + : [ + { fps: 60 }, + { resolutionBias: previewOutput }, + { resolutionBias: videoOutput }, + ], + onSessionConfigSelected: (config) => { + selectedConfig = config + }, + }, + ]) + await waitUntil(() => selectedConfig != null, { timeout: 5_000 }) + + await session.start() + try { + await waitUntil(() => videoOutput.currentResolution != null, { + timeout: 10_000, + }) + const currentResolution = videoOutput.currentResolution + if (selectedConfig == null) throw new Error('no selected config') + if (currentResolution == null) throw new Error('no video resolution') + return { + selectedFPS: selectedConfig.selectedFPS, + resolution: currentResolution, + } + } finally { + await session.stop() + } + } + + const videoOnly = await resolveVideoSession(false) + const videoOnlyShortEdge = Math.min( + videoOnly.resolution.width, + videoOnly.resolution.height, + ) + const videoOnlyLongEdge = Math.max( + videoOnly.resolution.width, + videoOnly.resolution.height, + ) + if ( + videoOnly.selectedFPS !== 60 || + videoOnlyShortEdge !== targetShortEdge || + videoOnlyLongEdge !== targetLongEdge + ) { + return context.skip( + `device resolves video-only 4k@60 to ${videoOnly.resolution.width}x${videoOnly.resolution.height}@${videoOnly.selectedFPS ?? 'default'}fps`, + ) + } + + const withPreview = await resolveVideoSession(true) + const previewShortEdge = Math.min( + withPreview.resolution.width, + withPreview.resolution.height, + ) + const previewLongEdge = Math.max( + withPreview.resolution.width, + withPreview.resolution.height, + ) + console.log( + `4k@60 video-only=${videoOnly.resolution.width}x${videoOnly.resolution.height}@${videoOnly.selectedFPS ?? 'default'}fps ` + + `with-preview=${withPreview.resolution.width}x${withPreview.resolution.height}@${withPreview.selectedFPS ?? 'default'}fps`, + ) + + expect(withPreview.selectedFPS).toBe(60) + expect(previewShortEdge).toBe(targetShortEdge) + expect(previewLongEdge).toBe(targetLongEdge) + }) + it('resolves photoHDR: true when the device supports photo HDR', async (context) => { if (!backDevice.supportsPhotoHDR) { return context.skip('photoHDR: not supported on this device') From 90603a1152e35b7254139e54f192928fb0c7fd2a Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Thu, 25 Jun 2026 21:12:04 +0200 Subject: [PATCH 2/2] test: Cover preview bias against low video target --- .../visioncamera.constraints.harness.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts b/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts index c24eea22a6..eb3d3a6da8 100644 --- a/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.constraints.harness.ts @@ -225,6 +225,105 @@ describe('VisionCamera - Constraints', () => { expect(previewLongEdge).toBe(targetLongEdge) }) + it('keeps a low video target when preview resolution bias is attached first', async (context) => { + const targetResolution = CommonResolutions.HD_16_9 + const maxAllowedResolution = CommonResolutions.FHD_16_9 + const uhdResolution = CommonResolutions.UHD_16_9 + + const getEdges = (resolution: { width: number; height: number }) => ({ + short: Math.min(resolution.width, resolution.height), + long: Math.max(resolution.width, resolution.height), + }) + + const targetEdges = getEdges(targetResolution) + const maxAllowedEdges = getEdges(maxAllowedResolution) + const uhdEdges = getEdges(uhdResolution) + const supportedVideoResolutions = backDevice.getSupportedResolutions('video') + const supportsTargetResolution = supportedVideoResolutions.some( + (resolution) => { + const edges = getEdges(resolution) + return edges.short === targetEdges.short && edges.long === targetEdges.long + }, + ) + const supportsUhdResolution = supportedVideoResolutions.some( + (resolution) => { + const edges = getEdges(resolution) + return edges.short === uhdEdges.short && edges.long === uhdEdges.long + }, + ) + if (!supportsTargetResolution) { + return context.skip('720p video resolution not supported on this device') + } + if (!supportsUhdResolution) { + return context.skip('4k video resolution not supported on this device') + } + + async function resolveLowTargetSession(includePreview: boolean) { + const session = await VisionCamera.createCameraSession(false) + const videoOutput = VisionCamera.createVideoOutput({ + targetResolution, + enableAudio: false, + enableHigherResolutionCodecs: true, + }) + const previewOutput = includePreview + ? VisionCamera.createPreviewOutput() + : undefined + + await session.configure([ + { + input: backDevice, + outputs: + previewOutput == null + ? [{ output: videoOutput, mirrorMode: 'auto' }] + : [ + { output: previewOutput, mirrorMode: 'auto' }, + { output: videoOutput, mirrorMode: 'auto' }, + ], + constraints: + previewOutput == null + ? [{ resolutionBias: videoOutput }] + : [ + { resolutionBias: previewOutput }, + { resolutionBias: videoOutput }, + ], + }, + ]) + + await session.start() + try { + await waitUntil(() => videoOutput.currentResolution != null, { + timeout: 10_000, + }) + const currentResolution = videoOutput.currentResolution + if (currentResolution == null) throw new Error('no video resolution') + return currentResolution + } finally { + await session.stop() + } + } + + const videoOnly = await resolveLowTargetSession(false) + const videoOnlyEdges = getEdges(videoOnly) + if ( + videoOnlyEdges.short > maxAllowedEdges.short || + videoOnlyEdges.long > maxAllowedEdges.long + ) { + return context.skip( + `device resolves video-only 720p target to ${videoOnly.width}x${videoOnly.height}`, + ) + } + + const withPreview = await resolveLowTargetSession(true) + const previewEdges = getEdges(withPreview) + console.log( + `720p target video-only=${videoOnly.width}x${videoOnly.height} ` + + `with-preview=${withPreview.width}x${withPreview.height}`, + ) + + expect(previewEdges.short).toBeLessThanOrEqual(maxAllowedEdges.short) + expect(previewEdges.long).toBeLessThanOrEqual(maxAllowedEdges.long) + }) + it('resolves photoHDR: true when the device supports photo HDR', async (context) => { if (!backDevice.supportsPhotoHDR) { return context.skip('photoHDR: not supported on this device')