react-native-sound
react-native-sound copied to clipboard
Audio plays as soon as an instance is created
This packages has an annoying bug which makes it totally useless and that is playing without even calling the .play()
method.
This only happens on Android. The issue is the fact that the method is being called inside of the callback of Sound
:
var whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('failed to load the sound', error);
return;
}
// loaded successfully
console.log('duration in seconds: ' + whoosh.getDuration() + 'number of channels: ' + whoosh.getNumberOfChannels());
// Play the sound with an onEnd callback
whoosh.play((success) => {
if (success) {
console.log('successfully finished playing');
} else {
console.log('playback failed due to audio decoding errors');
}
});
});
And if the method of .play()
isn't called inside of it, it won't work at all.
Hey, @n-ii-ma, does this example fix the issue?
App.tsx
import React from 'react';
import { View, Button, StyleSheet } from 'react-native';
import Sound from 'react-native-sound'
Sound.setCategory('Playback');
var whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('failed to load the sound', error);
return;
}
});
function App(): JSX.Element {
function playSound() {
whoosh.play((success) => {
if (!success) { console.log('Playback failed due to decoding errors') }
});
}
return (
<View style={styles.container}>
<Button title='Play Sound' onPress={playSound} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent:'center',
alignItems:'center'
}
})
export default App;
Environment
package | version |
---|---|
react-native | 0.71.3 |
react-native-sound | 0.11.2 |
Hey, @n-ii-ma, does this example fix the issue?
App.tsx
import React from 'react'; import { View, Button, StyleSheet } from 'react-native'; import Sound from 'react-native-sound' Sound.setCategory('Playback'); var whoosh = new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => { if (error) { console.log('failed to load the sound', error); return; } }); function App(): JSX.Element { function playSound() { whoosh.play((success) => { if (!success) { console.log('Playback failed due to decoding errors') } }); } return ( <View style={styles.container}> <Button title='Play Sound' onPress={playSound} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent:'center', alignItems:'center' } }) export default App;
Environment
package version react-native 0.71.3 react-native-sound 0.11.2
On iOS it does work in this way but on Android I have to call the .play()
method inside the callback of creating the Sound
instance for it to work and that results in it playing as soon as it's created.
Hmm that is weird, it works on my Android device. What can you see in the Android Studio logcat when pressing the button to play the sound? (without having the .play()
inside the callback)
I get those 2 lines everytime the audio is played:
At first it logs the same lines as you but then logs these:
Might be because I'm calling the .play
method inside a useEffect
since by doing a button press it does actually play.
I just tried calling the .play()
method with a button and still nothing. It seems that the asset won't get played at first but then it will.
Getting a lot of headaches with this library, TBH.
You can solve it using the useState()
hook, this way, you can display a loading screen until the asset has been loaded properly & then dismiss it. Also, wrap the function with useMemo()
so that it will only be executed on the first render, like this:
const [loading, setLoading] = useState(true)
const whoosh = useMemo(() => {
return (
new Sound('whoosh.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('failed to load the sound', error);
return;
}
setLoading(false)
})
)
},[]);
I did as you mentioned and still nothing but I just realized that I'm releasing the audio in the cleanup function of useEffect
and that seems to have been the issue here since the cleanup function runs when the conditions set in useEffect
are not met and this causes the audio not to play. This is how my useEffect
looks at the moment:
const [loading, setLoading] = useState(true); // Sound loading state
const orderMusicAudio = useMemo(() => {
return new Sound('order_music.mp3', Sound.MAIN_BUNDLE, error => {
// Return if there's an error
if (error) {
return;
}
// Set indefinite loop
orderMusicAudio.setNumberOfLoops(-1);
// Set loading to false
setLoading(false);
});
}, []);
// Play sound if an order has been selected
useEffect(() => {
// Play sound if sound has loaded, the courier has incoming orders and hasn't yet accepted any
if (!loading && hasOrders && isObjEmpty(acceptedOrder)) {
// Start vibration
Vibration.vibrate([1000, 1000], true);
// Play the audio file
// After it's completed, stop the vibration and release the audio player resource
orderMusicAudio.play(() => {
Vibration.cancel();
orderMusicAudio.release();
});
}
// Stop playing the audio file if otherwise
else {
// After the audio is stopped, stop the vibration and release the audio player resource
orderMusicAudio.stop(() => {
Vibration.cancel();
orderMusicAudio.release();
});
}
// Release the audio player resource on unmount
return () => {
console.log('RELEASE');
orderMusicAudio.release();
};
}, [loading, hasOrders, acceptedOrder]);
This is for an app like Uber for the drivers which will play a sound after an order arrives via a Socket Connection. (The reason I'm releasing the audio after each successful play and stop is for performance purposes although to be honest, I'm beginning to doubt its usefulness)
I myself am a bit confused here for even if the cleanup function gets called, after the conditions are met, the .play()
method is invoked and the sound should play again but it doesn't.
Also, as I mentioned before, this problem only occurs on Android.
You might be over-complicating things with .release()
, try this instead:
useEffect(() => {
if(loading) return
if (hasOrders && isObjEmpty(acceptedOrder)) {
Vibration.vibrate([1000, 1000], true);
orderMusicAudio.play((success) => {
if (!success) { console.log('Playback failed due to decoding errors') }
});
} else {
orderMusicAudio.stop()
}
},[loading, hasOrders, acceptedOrder])
Let me know if it works now!
Yeah this does work indeed but since no cleanup is done in the useEffect
, closing the app while the audio is still playing doesn't release the resource and stop the audio.
One workaround I found was to just stop playing the sound inside the cleanup function yet I'm wondering if not releasing the audio might lead to memory leak issues.
This will help https://www.youtube.com/watch?v=vVI7ZAZq5e0