Add selectable FPS

This commit is contained in:
Marc Rousavy 2021-02-20 17:39:04 +01:00
parent eaebda7954
commit b7274eb2e9
4 changed files with 72 additions and 16 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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,
},
}); });

View File

@ -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;
}, },