import { FinishMode, IWaveformRef, PermissionStatus, PlaybackSpeedType, PlayerState, RecorderState, UpdateFrequency, Waveform, useAudioPermission, useAudioPlayer, } from '@simform_solutions/react-native-audio-waveform'; import React, { Dispatch, SetStateAction, useEffect, useRef, useState, } from 'react'; import { ActivityIndicator, Alert, Image, Linking, Pressable, ScrollView, StatusBar, Text, View, } from 'react-native'; import fs from 'react-native-fs'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider, useSafeAreaInsets, } from 'react-native-safe-area-context'; import { Icons } from './assets'; import { generateAudioList, getRecordedAudios, playbackSpeedSequence, type ListItem, } from './constants'; import stylesheet from './styles'; import { Colors } from './theme'; let currentPlayingRef: React.RefObject | undefined; const RenderListItem = React.memo( ({ item, onPanStateChange, currentPlaybackSpeed, changeSpeed, }: { item: ListItem; onPanStateChange: (value: boolean) => void; currentPlaybackSpeed: PlaybackSpeedType; changeSpeed: () => void; }) => { const ref = useRef(null); const [playerState, setPlayerState] = useState(PlayerState.stopped); const styles = stylesheet({ currentUser: item.fromCurrentUser }); const [isLoading, setIsLoading] = useState(true); const handlePlayPauseAction = async () => { // If we are recording do nothing if ( currentPlayingRef?.current?.currentState === RecorderState.recording ) { return; } const startNewPlayer = async () => { currentPlayingRef = ref; if (ref.current?.currentState === PlayerState.paused) { await ref.current?.resumePlayer(); } else { await ref.current?.startPlayer({ finishMode: FinishMode.stop, }); // If the player took too much time to initialize and another player started instead we pause the former one! if ( currentPlayingRef?.current?.playerKey !== ref?.current?.playerKey ) { await ref?.current?.pausePlayer(); } } }; // If no player or if current player is stopped just start the new player! if ( currentPlayingRef == null || [PlayerState.stopped, PlayerState.paused].includes( currentPlayingRef?.current?.currentState as PlayerState ) ) { await startNewPlayer(); } else { // Pause current player if it was playing if (currentPlayingRef?.current?.currentState === PlayerState.playing) { await currentPlayingRef?.current?.pausePlayer(); } // Start player when it is a different one! if (currentPlayingRef?.current?.playerKey !== ref?.current?.playerKey) { await startNewPlayer(); } } }; const handleStopAction = async () => { ref.current?.stopPlayer(); }; return ( {isLoading ? ( ) : ( )} { console.log('Error in static player:', error); }} onCurrentProgressChange={(_currentProgress, _songDuration) => { // console.log( // `currentProgress ${currentProgress}, songDuration ${songDuration}` // ); }} onChangeWaveformLoadState={state => { setIsLoading(state); }} /> {playerState === PlayerState.playing ? ( {`${currentPlaybackSpeed}x`} ) : ( )} ); } ); const LivePlayerComponent = ({ setList, }: { setList: Dispatch>; }) => { const ref = useRef(null); const [recorderState, setRecorderState] = useState(RecorderState.stopped); const styles = stylesheet(); const { checkHasAudioRecorderPermission, getAudioRecorderPermission } = useAudioPermission(); const startRecording = () => { ref.current ?.startRecord({ updateFrequency: UpdateFrequency.high, }) .then(() => {}) .catch(() => {}); }; const handleRecorderAction = async () => { if (recorderState === RecorderState.stopped) { // Stopping other player before starting recording if (currentPlayingRef?.current?.currentState === PlayerState.playing) { currentPlayingRef?.current?.stopPlayer(); } const hasPermission = await checkHasAudioRecorderPermission(); if (hasPermission === PermissionStatus.granted) { currentPlayingRef = ref; startRecording(); } else if (hasPermission === PermissionStatus.undetermined) { const permissionStatus = await getAudioRecorderPermission(); if (permissionStatus === PermissionStatus.granted) { currentPlayingRef = ref; startRecording(); } } else { Linking.openSettings(); } } else { ref.current?.stopRecord().then(path => { setList(prev => [...prev, { fromCurrentUser: true, path }]); }); currentPlayingRef = undefined; } }; return ( ); }; const AppContainer = () => { const [shouldScroll, setShouldScroll] = useState(true); const [list, setList] = useState([]); const [nbOfRecording, setNumberOfRecording] = useState(0); const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(1.0); const { top, bottom } = useSafeAreaInsets(); const styles = stylesheet({ top, bottom }); useEffect(() => { generateAudioList().then(audioListArray => { if (audioListArray?.length > 0) { setList(audioListArray); } }); }, []); useEffect(() => { getRecordedAudios().then(recordedAudios => setNumberOfRecording(recordedAudios.length) ); }, [list]); const changeSpeed = () => { setCurrentPlaybackSpeed( prev => playbackSpeedSequence[ (playbackSpeedSequence.indexOf(prev) + 1) % playbackSpeedSequence.length ] ?? 1.0 ); }; const handleDeleteRecordings = async () => { const recordings = await getRecordedAudios(); const deleteRecordings = async () => { await Promise.all(recordings.map(async recording => fs.unlink(recording))) .then(() => { generateAudioList().then(audioListArray => { setList(audioListArray); }); }) .catch(error => { Alert.alert( 'Error deleting recordings', 'Below error happened while deleting recordings:\n' + error, [{ text: 'Dismiss' }] ); }); }; Alert.alert( 'Delete all recording', `Continue to delete all ${recordings.length} recordings.`, [ { text: 'Cancel', style: 'cancel' }, { text: 'OK', onPress: deleteRecordings }, ] ); }; const handleStopPlayersAndExtractors = async () => { await currentPlayingRef?.current?.stopPlayer(); const { stopPlayersAndExtractors } = useAudioPlayer(); const hasStoppedAll: boolean[] = await stopPlayersAndExtractors(); if (hasStoppedAll.every(Boolean)) { Alert.alert( 'Everything stopped', 'All players and extractors have been stopped!', [{ text: 'OK' }] ); } else { Alert.alert( 'Error stopping everything', 'An error occurred when trying to stop players or extractors', [{ text: 'OK' }] ); } }; return ( {'Delete recorded audio files'} {'Stop all players and extractors'} {list.map(item => ( setShouldScroll(!value)} {...{ currentPlaybackSpeed, changeSpeed }} /> ))} ); }; export default function App() { return ( ); }