feat: New array-based useCameraFormats
API (#1841)
* feat: New array-based `useCameraFormats` API * Use triple-camera in Example app * fix: Remove invalid export * fix: Use constant-time lookup Filter map and only run sort once
This commit is contained in:
parent
3169444697
commit
2d96381b3e
@ -45,14 +45,14 @@ export function CameraPage({ navigation }: Props): React.ReactElement {
|
||||
const [enableNightMode, setEnableNightMode] = useState(false);
|
||||
|
||||
// camera format settings
|
||||
const device = useCameraDevice(cameraPosition);
|
||||
const format = useCameraFormat(device, {
|
||||
fps: {
|
||||
target: 60,
|
||||
priority: 1,
|
||||
},
|
||||
const device = useCameraDevice(cameraPosition, {
|
||||
physicalDevices: ['ultra-wide-angle-camera', 'wide-angle-camera', 'telephoto-camera'],
|
||||
});
|
||||
|
||||
const format = useCameraFormat(device, [
|
||||
{ fps: 60 }, //
|
||||
]);
|
||||
|
||||
//#region Memos
|
||||
const [targetFps, setTargetFps] = useState(30);
|
||||
const fps = Math.min(format?.maxFps ?? 1, targetFps);
|
||||
|
@ -1,43 +0,0 @@
|
||||
export interface Filter<T> {
|
||||
/**
|
||||
* The target value for this specific requirement
|
||||
*/
|
||||
target: T;
|
||||
/**
|
||||
* The priority of this requirement.
|
||||
* Filters with higher priority can take precedence over filters with lower priority.
|
||||
*
|
||||
* For example, if we have two formats:
|
||||
* ```json
|
||||
* [
|
||||
* videoWidth: 3840,
|
||||
* videoHeight: 2160,
|
||||
* maxFps: 30,
|
||||
* ...
|
||||
* ],
|
||||
* [
|
||||
* videoWidth: 1920,
|
||||
* videoHeight: 1080,
|
||||
* maxFps: 60,
|
||||
* ...
|
||||
* ]
|
||||
* ```
|
||||
* And your filter looks like this:
|
||||
* ```json
|
||||
* {
|
||||
* fps: { target: 60, priority: 1 }
|
||||
* videoSize: { target: { width: 4000, height: 2000 }, priority: 3 }
|
||||
* }
|
||||
* ```
|
||||
* The 4k format will be chosen since the `videoSize` filter has a higher priority (2) than the `fps` filter (1).
|
||||
*
|
||||
* To choose the 60 FPS format instead, use a higher priority for the `fps` filter:
|
||||
* ```json
|
||||
* {
|
||||
* fps: { target: 60, priority: 2 }
|
||||
* videoSize: { target: { width: 4000, height: 2000 }, priority: 1 }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
priority: number;
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import type { CameraDevice, CameraDeviceFormat, VideoStabilizationMode } from '../CameraDevice';
|
||||
import { CameraRuntimeError } from '../CameraError';
|
||||
import { PixelFormat } from '../PixelFormat';
|
||||
import { Filter } from './Filter';
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
@ -13,12 +12,12 @@ export interface FormatFilter {
|
||||
* The target resolution of the video (and frame processor) output pipeline.
|
||||
* If no format supports the given resolution, the format closest to this value will be used.
|
||||
*/
|
||||
videoResolution?: Filter<Size>;
|
||||
videoResolution?: Size;
|
||||
/**
|
||||
* The target resolution of the photo output pipeline.
|
||||
* If no format supports the given resolution, the format closest to this value will be used.
|
||||
*/
|
||||
photoResolution?: Filter<Size>;
|
||||
photoResolution?: Size;
|
||||
/**
|
||||
* The target aspect ratio of the video (and preview) output, expressed as a factor: `width / height`.
|
||||
*
|
||||
@ -30,7 +29,7 @@ export interface FormatFilter {
|
||||
* targetVideoAspectRatio: screen.width / screen.height
|
||||
* ```
|
||||
*/
|
||||
videoAspectRatio?: Filter<number>;
|
||||
videoAspectRatio?: number;
|
||||
/**
|
||||
* The target aspect ratio of the photo output, expressed as a factor: `width / height`.
|
||||
*
|
||||
@ -43,31 +42,58 @@ export interface FormatFilter {
|
||||
* targetPhotoAspectRatio: screen.width / screen.height
|
||||
* ```
|
||||
*/
|
||||
photoAspectRatio?: Filter<number>;
|
||||
photoAspectRatio?: number;
|
||||
/**
|
||||
* The target FPS you want to record video at.
|
||||
* If the FPS requirements can not be met, the format closest to this value will be used.
|
||||
*/
|
||||
fps?: Filter<number>;
|
||||
fps?: number;
|
||||
/**
|
||||
* The target video stabilization mode you want to use.
|
||||
* If no format supports the target video stabilization mode, the best other matching format will be used.
|
||||
*/
|
||||
videoStabilizationMode?: Filter<VideoStabilizationMode>;
|
||||
videoStabilizationMode?: VideoStabilizationMode;
|
||||
/**
|
||||
* The target pixel format you want to use.
|
||||
* If no format supports the target pixel format, the best other matching format will be used.
|
||||
*/
|
||||
pixelFormat?: Filter<PixelFormat>;
|
||||
pixelFormat?: PixelFormat;
|
||||
}
|
||||
|
||||
type FilterWithPriority<T> = {
|
||||
target: Exclude<T, null | undefined>;
|
||||
priority: number;
|
||||
};
|
||||
type FilterMap = {
|
||||
[K in keyof FormatFilter]: FilterWithPriority<FormatFilter[K]>;
|
||||
};
|
||||
function filtersToFilterMap(filters: FormatFilter[]): FilterMap {
|
||||
return filters.reduce<FilterMap>((map, curr, index) => {
|
||||
for (const key in curr) {
|
||||
// @ts-expect-error keys are untyped
|
||||
map[key] = {
|
||||
// @ts-expect-error keys are untyped
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
target: curr[key],
|
||||
priority: filters.length - index,
|
||||
};
|
||||
}
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best matching Camera format for the given device that satisfies your requirements using a sorting filter. By default, formats are sorted by highest to lowest resolution.
|
||||
* @param device The Camera Device you're currently using
|
||||
* @param filter The filter you want to use. The format that matches your filter the closest will be returned
|
||||
* @param filters The filter you want to use. The format that matches your filter the closest will be returned. The filter is ranked by priority, descending.
|
||||
* @returns The format that matches your filter the closest.
|
||||
*/
|
||||
export function getCameraFormat(device: CameraDevice, filter: FormatFilter): CameraDeviceFormat {
|
||||
export function getCameraFormat(device: CameraDevice, filters: FormatFilter[]): CameraDeviceFormat {
|
||||
// Combine filters into a single filter map for constant-time lookup
|
||||
const filter = filtersToFilterMap(filters);
|
||||
|
||||
// Sort list because we will pick first element
|
||||
// TODO: Use reduce instead of sort?
|
||||
const copy = [...device.formats];
|
||||
const sortedFormats = copy.sort((left, right) => {
|
||||
let leftPoints = 0;
|
||||
|
@ -4,24 +4,28 @@ import { FormatFilter, getCameraFormat } from '../devices/getCameraFormat';
|
||||
|
||||
/**
|
||||
* Get the best matching Camera format for the given device that satisfies your requirements using a sorting filter. By default, formats are sorted by highest to lowest resolution.
|
||||
*
|
||||
* The {@linkcode filters | filters} are ranked by priority, from highest to lowest.
|
||||
* This means the first item you pass will have a higher priority than the second, and so on.
|
||||
*
|
||||
* @param device The Camera Device you're currently using
|
||||
* @param filter The filter you want to use. The format that matches your filter the closest will be returned
|
||||
* @returns The format that matches your filter the closest.
|
||||
* @example
|
||||
* ```ts
|
||||
* const device = useCameraDevice(...)
|
||||
* const format = useCameraFormat(device, {
|
||||
* videoResolution: { target: { width: 3048, height: 2160 }, priority: 2 },
|
||||
* fps: { target: 60, priority: 1 }
|
||||
* })
|
||||
* const format = useCameraFormat(device, [
|
||||
* { videoResolution: { width: 3048, height: 2160 } },
|
||||
* { fps: 60 }
|
||||
* ])
|
||||
* ```
|
||||
*/
|
||||
export function useCameraFormat(device: CameraDevice | undefined, filter: FormatFilter): CameraDeviceFormat | undefined {
|
||||
export function useCameraFormat(device: CameraDevice | undefined, filters: FormatFilter[]): CameraDeviceFormat | undefined {
|
||||
const format = useMemo(() => {
|
||||
if (device == null) return undefined;
|
||||
return getCameraFormat(device, filter);
|
||||
return getCameraFormat(device, filters);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [device, JSON.stringify(filter)]);
|
||||
}, [device, JSON.stringify(filters)]);
|
||||
|
||||
return format;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ export * from './PixelFormat';
|
||||
export * from './Point';
|
||||
export * from './VideoFile';
|
||||
|
||||
export * from './devices/Filter';
|
||||
export * from './devices/getCameraFormat';
|
||||
export * from './devices/getCameraDevice';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user