Add selectable FPS
This commit is contained in:
parent
eaebda7954
commit
b7274eb2e9
@ -13,7 +13,7 @@ import type { CameraDevice, CameraDeviceFormat, CameraProps, CameraRuntimeError,
|
|||||||
import { Camera } from 'react-native-vision-camera';
|
import { Camera } from 'react-native-vision-camera';
|
||||||
import { useIsScreenFocused } from './hooks/useIsScreenFocused';
|
import { useIsScreenFocused } from './hooks/useIsScreenFocused';
|
||||||
import { compareFormats, frameRateIncluded, formatWithClosestMatchingFps, compareDevices } from './FormatFilter';
|
import { compareFormats, frameRateIncluded, formatWithClosestMatchingFps, compareDevices } from './FormatFilter';
|
||||||
import { CONTENT_SPACING, HIGH_FPS, MAX_ZOOM_FACTOR, SAFE_AREA_PADDING } from './Constants';
|
import { CONTENT_SPACING, MAX_ZOOM_FACTOR, SAFE_AREA_PADDING } from './Constants';
|
||||||
import Reanimated, { Extrapolate, interpolate, useAnimatedGestureHandler, useAnimatedProps, useSharedValue } from 'react-native-reanimated';
|
import Reanimated, { Extrapolate, interpolate, useAnimatedGestureHandler, useAnimatedProps, useSharedValue } from 'react-native-reanimated';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useIsForeground } from './hooks/useIsForeground';
|
import { useIsForeground } from './hooks/useIsForeground';
|
||||||
@ -22,6 +22,8 @@ import { CaptureButton } from './views/CaptureButton';
|
|||||||
import { PressableOpacity } from './views/PressableOpacity';
|
import { PressableOpacity } from './views/PressableOpacity';
|
||||||
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import IonIcon from 'react-native-vector-icons/Ionicons';
|
import IonIcon from 'react-native-vector-icons/Ionicons';
|
||||||
|
import { useSelector } from 'pipestate';
|
||||||
|
import { FpsSelector } from './state/selectors';
|
||||||
|
|
||||||
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera);
|
||||||
Reanimated.addWhitelistedNativeProps({
|
Reanimated.addWhitelistedNativeProps({
|
||||||
@ -53,26 +55,28 @@ export const App: NavigationFunctionComponent = ({ componentId }) => {
|
|||||||
const formats = useMemo<CameraDeviceFormat[]>(() => device?.formats.sort(compareFormats) ?? [], [device?.formats]);
|
const formats = useMemo<CameraDeviceFormat[]>(() => device?.formats.sort(compareFormats) ?? [], [device?.formats]);
|
||||||
|
|
||||||
//#region Memos
|
//#region Memos
|
||||||
|
const [targetFps] = useSelector(FpsSelector);
|
||||||
|
console.log(`Target FPS: ${targetFps}`);
|
||||||
const fps = useMemo(() => {
|
const fps = useMemo(() => {
|
||||||
if (enableNightMode && !device?.supportsLowLightBoost) {
|
if (enableNightMode && !device?.supportsLowLightBoost) {
|
||||||
// User has enabled Night Mode, but Night Mode is not natively supported, so we simulate it by lowering the frame rate.
|
// User has enabled Night Mode, but Night Mode is not natively supported, so we simulate it by lowering the frame rate.
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportsHdrAtHighFps = formats.some((f) => f.supportsVideoHDR && f.frameRateRanges.some((r) => frameRateIncluded(r, HIGH_FPS)));
|
const supportsHdrAtHighFps = formats.some((f) => f.supportsVideoHDR && f.frameRateRanges.some((r) => frameRateIncluded(r, targetFps)));
|
||||||
if (enableHdr && !supportsHdrAtHighFps) {
|
if (enableHdr && !supportsHdrAtHighFps) {
|
||||||
// User has enabled HDR, but HDR is not supported at HIGH_FPS.
|
// User has enabled HDR, but HDR is not supported at targetFps.
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportsHighFps = formats.some((f) => f.frameRateRanges.some((r) => frameRateIncluded(r, HIGH_FPS)));
|
const supportsHighFps = formats.some((f) => f.frameRateRanges.some((r) => frameRateIncluded(r, targetFps)));
|
||||||
if (!supportsHighFps) {
|
if (!supportsHighFps) {
|
||||||
// HIGH_FPS is not supported by any format.
|
// targetFps is not supported by any format.
|
||||||
return 30;
|
return 30;
|
||||||
}
|
}
|
||||||
// If nothing blocks us from using it, we default to HIGH_FPS.
|
// If nothing blocks us from using it, we default to targetFps.
|
||||||
return HIGH_FPS;
|
return targetFps;
|
||||||
}, [device, enableHdr, enableNightMode, formats]);
|
}, [device?.supportsLowLightBoost, enableHdr, enableNightMode, formats, targetFps]);
|
||||||
|
|
||||||
const supportsCameraFlipping = useMemo(() => devices.some((d) => d.position === 'back') && devices.some((d) => d.position === 'front'), [devices]);
|
const supportsCameraFlipping = useMemo(() => devices.some((d) => d.position === 'back') && devices.some((d) => d.position === 'front'), [devices]);
|
||||||
const supportsFlash = device?.hasFlash ?? false;
|
const supportsFlash = device?.hasFlash ?? false;
|
||||||
|
@ -28,9 +28,6 @@ export const RESOLUTION_LIMIT = Platform.select({
|
|||||||
// whether to use ultra-wide-angle cameras if available, or explicitly disable them. I think ultra-wide-angle cams don't support 60FPS...
|
// whether to use ultra-wide-angle cameras if available, or explicitly disable them. I think ultra-wide-angle cams don't support 60FPS...
|
||||||
export const USE_ULTRAWIDE_IF_AVAILABLE = true;
|
export const USE_ULTRAWIDE_IF_AVAILABLE = true;
|
||||||
|
|
||||||
// the max FPS to use if available
|
|
||||||
export const HIGH_FPS = 50;
|
|
||||||
|
|
||||||
// The maximum zoom _factor_ you should be able to zoom in
|
// The maximum zoom _factor_ you should be able to zoom in
|
||||||
export const MAX_ZOOM_FACTOR = 16;
|
export const MAX_ZOOM_FACTOR = 16;
|
||||||
|
|
||||||
|
@ -1,15 +1,62 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { StyleSheet, View, Text, Linking } from 'react-native';
|
import { StyleSheet, View, Text, Linking } from 'react-native';
|
||||||
import type { NavigationFunctionComponent } from 'react-native-navigation';
|
import { Navigation, NavigationFunctionComponent } from 'react-native-navigation';
|
||||||
|
import Slider from '@react-native-community/slider';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Camera } from 'react-native-vision-camera';
|
||||||
|
import { SAFE_AREA_PADDING } from './Constants';
|
||||||
|
import { useSelector } from 'pipestate';
|
||||||
|
import { FpsSelector } from './state/selectors';
|
||||||
|
|
||||||
|
export const Settings: NavigationFunctionComponent = ({ componentId }) => {
|
||||||
|
const [fpsSelector, setFpsSelector] = useSelector(FpsSelector);
|
||||||
|
const [fps, setFps] = useState(fpsSelector);
|
||||||
|
|
||||||
|
const [minFps, setMinFps] = useState<number>();
|
||||||
|
const [maxFps, setMaxFps] = useState<number>();
|
||||||
|
|
||||||
export const Settings: NavigationFunctionComponent = () => {
|
|
||||||
const onCuventPressed = useCallback(() => {
|
const onCuventPressed = useCallback(() => {
|
||||||
Linking.openURL('https://cuvent.com');
|
Linking.openURL('https://cuvent.com');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadFormats = async (): Promise<void> => {
|
||||||
|
const devices = await Camera.getAvailableCameraDevices();
|
||||||
|
const formats = devices.flatMap((d) => d.formats);
|
||||||
|
let max = 0,
|
||||||
|
min = 0;
|
||||||
|
formats.forEach((format) => {
|
||||||
|
const frameRates = format.frameRateRanges.map((f) => f.maxFrameRate).sort((left, right) => left - right);
|
||||||
|
const highest = frameRates[frameRates.length - 1] as number;
|
||||||
|
const lowest = frameRates[0] as number;
|
||||||
|
if (highest > max) max = highest;
|
||||||
|
if (lowest < min) min = lowest;
|
||||||
|
});
|
||||||
|
setMaxFps(max);
|
||||||
|
setMinFps(min);
|
||||||
|
};
|
||||||
|
loadFormats();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = Navigation.events().registerScreenPoppedListener((event) => {
|
||||||
|
if (event.componentId === componentId) setFpsSelector(fps);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
listener.remove();
|
||||||
|
};
|
||||||
|
}, [componentId, fps, setFpsSelector]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
<View style={styles.vControl}>
|
||||||
|
<Text>Frame Rate (FPS): {fps}</Text>
|
||||||
|
{minFps != null && maxFps != null && <Slider minimumValue={minFps} maximumValue={maxFps} value={fps} onValueChange={setFps} />}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.spacer} />
|
||||||
|
|
||||||
<Text style={styles.aboutText}>
|
<Text style={styles.aboutText}>
|
||||||
Vision Camera is powered by{' '}
|
Vision Camera is powered by{' '}
|
||||||
<Text style={styles.hyperlink} onPress={onCuventPressed}>
|
<Text style={styles.hyperlink} onPress={onCuventPressed}>
|
||||||
@ -37,11 +84,13 @@ Settings.options = {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'stretch',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
|
...SAFE_AREA_PADDING,
|
||||||
},
|
},
|
||||||
aboutText: {
|
aboutText: {
|
||||||
|
alignSelf: 'center',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: '#A9A9A9',
|
color: '#A9A9A9',
|
||||||
@ -52,4 +101,10 @@ const styles = StyleSheet.create({
|
|||||||
color: '#007aff',
|
color: '#007aff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
|
vControl: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
spacer: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { selector } from 'pipestate';
|
import { selector } from 'pipestate';
|
||||||
import { FormatSettingsAtom } from './atoms';
|
import { FormatSettingsAtom } from './atoms';
|
||||||
|
|
||||||
export const FpsSelector = selector({
|
export const FpsSelector = selector<number, []>({
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
return get(FormatSettingsAtom).fps;
|
return get(FormatSettingsAtom).fps;
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user