diff --git a/apps/common-app/src/demos/index.ts b/apps/common-app/src/demos/index.ts index 8f1d989b8..c338d6f7d 100644 --- a/apps/common-app/src/demos/index.ts +++ b/apps/common-app/src/demos/index.ts @@ -37,9 +37,8 @@ export const demos: DemoScreen[] = [ { key: 'Crossfade', title: 'Crossfade', - subtitle: - 'Demonstrates crossfading between two audio files.', + subtitle: 'Demonstrates crossfading between two audio files.', icon: icons.ArrowLeftRight, screen: Crossfade, - } + }, ] as const; diff --git a/apps/common-app/src/examples/index.ts b/apps/common-app/src/examples/index.ts index d664f93b1..2572dd90d 100644 --- a/apps/common-app/src/examples/index.ts +++ b/apps/common-app/src/examples/index.ts @@ -31,6 +31,7 @@ type NavigationParamList = { ConvolverIR: undefined; AudioParamPipeline: undefined; TestScreen: undefined; + LatencyMeter: undefined; }; export type ExampleKey = keyof NavigationParamList; diff --git a/apps/common-app/src/other/LatencyMeter/LatencyMeter.tsx b/apps/common-app/src/other/LatencyMeter/LatencyMeter.tsx new file mode 100644 index 000000000..5b438e931 --- /dev/null +++ b/apps/common-app/src/other/LatencyMeter/LatencyMeter.tsx @@ -0,0 +1,125 @@ +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { Platform, StyleSheet, Text, View } from 'react-native'; +import { AudioManager } from 'react-native-audio-api'; + +import { Button, Container } from '../../components'; +import { audioContext, audioRecorder } from '../../singletons'; +import { colors } from '../../styles'; + +interface Latencies { + base: number; + output: number; + input: number; +} + +const fmt = (s: number) => `${(s * 1000).toFixed(2)} ms`; + +const LatencyMeter: FC = () => { + const [isRunning, setIsRunning] = useState(false); + const [latencies, setLatencies] = useState(null); + const intervalRef = useRef | null>(null); + + const readLatencies = useCallback(() => { + setLatencies({ + base: audioContext.baseLatency, + output: audioContext.outputLatency, + input: audioRecorder.inputLatency, + }); + }, []); + + const start = useCallback(async () => { + const status = await AudioManager.requestRecordingPermissions(); + if (status !== 'Granted') { + return; + } + + if (Platform.OS !== 'web') { + AudioManager.setAudioSessionOptions({ + iosCategory: 'playAndRecord', + iosMode: 'measurement', + iosOptions: ['allowBluetooth'], + }); + await AudioManager.setAudioSessionActivity(true); + } + + if (audioContext.state === 'suspended') { + await audioContext.resume(); + } + + await audioRecorder.start(); + setIsRunning(true); + readLatencies(); + intervalRef.current = setInterval(readLatencies, 500); + }, [readLatencies]); + + const stop = useCallback(async () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + await audioRecorder.stop(); + if (Platform.OS !== 'web') { + await AudioManager.setAudioSessionActivity(false); + } + setIsRunning(false); + }, []); + + useEffect(() => { + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; + }, []); + + return ( + +