0

I am a react native beginner struggling with concurrency.

I would like to enforce within the handlePlay function that no new sound is played while the current sound is playing.

So if sound1.mp3 is playing now and the handlePlay function is called, nothing will happen. Once sound1.mp3 is finished playing and the handlePlay function is called by useEffect being triggered again, sound2.mp3 should be played (should be sound2.mp3 because currentStepInc will have been run to trigger the useEffect).

However quickly the button is pressed there should be no time at which two sounds are being played at once.

import * as React from 'react';
import { View, Button } from 'react-native';
import Sound from 'react-native-sound';

function MusicButton({ currentStep, currentStepInc }) {
  let currSoundName = 'sound' + currentStep + '.mp3';
  console.log('currSoundName is ', currSoundName);

  let soundObject:Sound|null = null;

  const handlePlay = async () => {
       await new Promise((resolve) => {
        if (soundObject !== null && soundObject.isPlaying()) {
            return;
         }
        soundObject = new Sound(currSoundName, null, (error) => {
          if (error) {
            console.log('Failed to load sound', error);
          } else {
            console.log(`Playing sound ${currSoundName}`);
            soundObject.play((success) => {
              if (success) {
                console.log(`Finished playing sound ${currSoundName}`);
              } else {
                console.log(`Failed to play sound ${currSoundName}`);
              }
              resolve(null);
            });
          }
        });
      }); 
  };

  React.useEffect(() => {
    console.log('useEffect being run because currentStep has been incremented');
    handlePlay();
  }, [currentStep]);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Play Sound" onPress={currentStepInc} />
    </View>
  );
}

export default function App() {
  const [currentStep, setCurrentStep] = React.useState(1);

  const currentStepInc = () => {
    setCurrentStep((currentStep) => currentStep + 1);
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <MusicButton currentStep={currentStep} currentStepInc={currentStepInc} />
    </View>
  );
}

context

I have some face recognition code working that triggers a useEffect hook when a face is recognized.

I want to play a sequence of sounds in order, one after the other, with delays in between, but only as long as the face is recognized. When the face is no longer recognized I want the playback sequence to stop at the end of the current step and go back to the beginning of that step. My plan was to implement this by having a step counter passed into a function that is run within a useEffect hook. That useEffect hook is triggered whenever the value of faceVisible or the stepCounter changes. So I can play a given step by incrementing the stepCounter, and if faceVisible is false I simply don't increment the stepCounter within the playback sequence function.

I have not been able to get this working in react-native-sound at all. At first I tried using a single Sound object and passing it around everywhere because one Sound object can only be playing one sound at a time presumably, however that didn't seem to work. I tried to declare the Sound object within a useEffect trigger but I got an error there too (I can explain more about this issue if it seems relevant). If I declare multiple objects outside of the useEffect trigger for different sounds, I can't find a way to prevent multiple sounds from running at once (similar to the issue above with react-native-use-sound).

I am giving this context just to explain why I want my sound playback function to be able to be run from within a useEffect hook, and why I'm using react-native-use-sound. For the purpose of this question I am only asking about the code above to keep things simple. The code above is much simpler because no facial recognition is involved. Instead I'm simulating multiple rapid useEffect triggers (that facial recognition can sometimes generate) using a button that I press repeatedly.

End context

An attempt using react-native-use-sound instead of react-native-sound:

import * as React from 'react';
import { View, Button } from 'react-native';
import useSound from 'react-native-use-sound';

function PlayButton({ currentStep, currentStepInc }) {
  let currSoundName = 'sound' + currentStep + '.mp3';
  console.log('currSoundName is ', currSoundName);
  const [playSound, pauseSound, stopSound] = useSound(currSoundName);
  const currentSound = React.useRef(null);
  const [playbackStatus, setPlaybackStatus] = React.useState('stopped');

  const handlePlay = () => {
    if (playbackStatus === 'playing' || currentSound.current) {
      return;
    }
    currentSound.current = playSound();
    if (currentSound.current) {
      setPlaybackStatus('playing');
      currentSound.current.play();
      currentSound.current.on('ended', () => {
        currentSound.current = null;
        setPlaybackStatus('stopped');
      });
    }
  };

  React.useEffect(() => {
    console.log('useEffect being run because currentStep has been initialized or changed');
    handlePlay();
  }, [currentStep]);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Play Sound" onPress={currentStepInc} />
    </View>
  );
}

export default function App() {
  const [currentStep, setCurrentStep] = React.useState(1);

  const currentStepInc = () => {
    setCurrentStep((currentStep) => currentStep + 1);
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <PlayButton currentStep={currentStep} currentStepInc={currentStepInc} />
    </View>
  );
}


0 Answers0