I'm trying to get React Native to whistle 3 times before a timer, so for example, whistle 3 seconds in a row, then let the timer go, then whistle again, but for some reason it is only doing it twice, it's skipping the middle whistle and sometimes the last one.
I've tried mounting the sound before hand, reducing the sound duration to about .3 seconds, and it is still skipping some plays. I know I need to do some refactor on the timers, but I think at least playing the sound should work.
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
Vibration,
} from "react-native";
import React from "react";
import { StatusBar } from "expo-status-bar";
import { Audio } from "expo-av";
const screen = Dimensions.get("window");
let timeout: NodeJS.Timeout | undefined = undefined;
interface TimerComponentProps {
timeInSeconds?: number;
}
export const TimerComponent: React.FC<TimerComponentProps> = ({
timeInSeconds = 5,
}) => {
const [remaningSeconds, setRemainingSeconds] = React.useState(timeInSeconds);
const [isActive, setIsActive] = React.useState(false);
const [sound, setSound] = React.useState<Audio.Sound | undefined>(undefined);
const [shouldCount, setShouldCount] = React.useState(false);
const [counter, setCounter] = React.useState(3);
const { minutes, seconds } = React.useMemo(() => {
const minutes = Math.floor(remaningSeconds / 60);
const seconds = remaningSeconds % 60;
return { minutes, seconds };
}, [remaningSeconds]);
async function mountSound() {
try {
const { sound } = await Audio.Sound.createAsync(
require("../../assets/audio/Whistle.wav")
);
setSound(sound);
} catch (error) {
console.error(error);
}
}
async function playWhistle() {
if (sound) {
try {
await sound.playAsync();
} catch (error) {
console.error(error);
}
}
}
const endTimer = async () => {
try {
await playWhistle();
setIsActive(false);
} catch (error) {
console.error(error);
}
};
const startCounter = async () => {
await mountSound();
setShouldCount(true);
};
const resetTimer = () => {
if (timeout) {
clearTimeout(timeout);
} else {
timeout = setTimeout(() => {
setRemainingSeconds(timeInSeconds);
clearTimeout(timeout);
}, 1000);
}
};
React.useEffect(() => {
let counterInterval: NodeJS.Timer | undefined = undefined;
if (shouldCount) {
counterInterval = setInterval(() => {
try {
if (counter === 1) {
setCounter((counter) => counter - 1);
}
if (counter > 1) {
playWhistle();
Vibration.vibrate();
setCounter((counter) => counter - 1);
} else {
// Plays the whistle sound and vibrates the device
playWhistle();
Vibration.vibrate();
// Restarts the counter
setCounter(3);
setShouldCount(false);
// Starts the timer
setIsActive(true);
// Stops the counter
clearInterval(counterInterval);
}
} catch (error) {
console.error(error);
}
}, 1000);
} else if (!shouldCount && counter !== 0) {
clearInterval(counterInterval);
}
return () => clearInterval(counterInterval);
}, [shouldCount, counter]);
React.useEffect(() => {
let timerInterval: NodeJS.Timer | undefined = undefined;
if (isActive) {
timerInterval = setInterval(() => {
if (remaningSeconds === 1) {
setRemainingSeconds((remaningSeconds) => remaningSeconds - 1);
}
if (remaningSeconds > 1) {
setRemainingSeconds((remaningSeconds) => remaningSeconds - 1);
} else {
Vibration.vibrate();
endTimer();
resetTimer();
}
}, 1000);
} else if (!isActive && remaningSeconds === 0) {
resetTimer();
clearInterval(timerInterval);
}
return () => clearInterval(timerInterval);
}, [isActive, remaningSeconds]);
React.useEffect(() => {
return sound
? () => {
sound.unloadAsync();
setSound(undefined);
}
: undefined;
}, [sound]);
const parseTime = (time: number) => {
return time < 10 ? `0${time}` : time;
};
return (
<View style={styles.container}>
<StatusBar style="light" />
<Text style={styles.timerText}>{`${parseTime(minutes)}:${parseTime(
seconds
)}`}</Text>
<TouchableOpacity onPress={startCounter} style={styles.button}>
<Text style={styles.buttonText}>{isActive ? "Pause" : "Start"}</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#07121B",
alignItems: "center",
justifyContent: "center",
},
button: {
borderWidth: 10,
borderColor: "#B9AAFF",
width: screen.width / 2,
height: screen.width / 2,
borderRadius: screen.width / 2,
alignItems: "center",
justifyContent: "center",
},
buttonText: {
color: "#B9AAFF",
fontSize: 20,
},
timerText: {
color: "#fff",
fontSize: 90,
},
});