问题
Crashing when audio is played:
I am creating an app with several different screens of audio clips. We are testing out the app on iPhone/iPad using Testflight and are getting intermittent crashes when audio clips are played. There seems to be nothing wrong with the audio clips themselves as most of the time they work and it is not always the same audio clip that has the problem.
I am guessing it might be a memory leak to do with new Sound
but I am not sure how to test for this. I also thought that I covered this by making sure that I released
the sound component after it played and when the screen unmounts.
Is there something that I am missing in making sure that I correctly clean up the this.sound
component?
My code:
import React, { Component } from 'react';
var Sound = require('react-native-sound');
Sound.setCategory("Playback"); //Needed for audio to play on IOS devices
import {
Image,
ImageBackground,
SafeAreaView,
ScrollView,
View,
Text,
TouchableOpacity
} from 'react-native';
export default class AudioList extends Component {
constructor(props) {
super(props)
}
playAudio = (file) => {
console.log(file)
if (this.sound) {
console.log("SOUND")
try {
this.sound.release()
} catch (error) {
console.log("A sound release error has occured 111")
} finally {
this.sound = null
}
}
this.sound = new Sound('audio/' + file, Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('error', error);
this.sound = null;
} else {
this.sound.play(() => {
try {
if (this.sound) {
this.sound.release()
}
} catch (error) {
console.log("A sound release error has occured 222")
} finally {
this.sound = null
}
})
}
})
this.willBlurSubscription = this.props.navigation.addListener(
'blur',
() => {
try {
if (this.sound) {
this.sound.release()
}
} catch (error) {
console.log("A sound release error has occured 333")
} finally {
this.sound = null
}
}
)
}
componentWillUnmount() {
try {
this.willBlurSubscription &&
this.willBlurSubscription.remove &&
this.willBlurSubscription.remove()
} catch (error) {} finally {
this.willBlurSubscription = null
}
}
render() {
/* list and new_list removed for brevity */
/* styles removed for brevity */
let audio_clips = new_list.map((item, index) => {
return <View key={index}>
{item.audio ?
<TouchableOpacity onPress={()=>this.playAudio(item.audio)}>
<Image source={require('../assets/images/audio-icon.png')} />
</TouchableOpacity>
:
<View></View>
}
<Text>{item.text}</Text>
</View>
})
return (
<SafeAreaView>
<Text>{list.category_name}</Text>
<ImageBackground source={require('../assets/images/background.jpg')}>
<ScrollView>
<View>
{audio_clips}
</View>
</ScrollView>
</ImageBackground>
</SafeAreaView>
)
}
}
回答1:
Check if sound is already is an object :
if(!this.sound){
this.sound = new Sound('audio/' + file, Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('error', error);
this.sound = null;
} else {
this.sound.play(() => {
try {
if (this.sound) {
this.sound.release()
}
} catch (error) {
console.log("A sound release error has occured 222")
} finally {
this.sound = null
}
})
}
});
}
回答2:
Could it be a race condition where this.sound.release()
is called twice? So for the second time it is called upon null and it chrashes. I mean it is called synchronously after the sound is played and at the "blur" thing listener. Should maybe get caught then by the exception blocks but I'm not experienced enougt with js to know that..
回答3:
This might help, please look into it
import React, { Component } from "react";
var Sound = require("react-native-sound");
Sound.setCategory("PlayAndRecord", true);
Sound.setActive(true); // add this
export default class AudioList extends Component {
playAudio = (file) => {
if (this.sound) {
try {
this.sound.release();
} catch (error) {
console.log("A sound release error has occured 111");
} finally {
this.sound = null;
}
}
const pathType =
Platform.OS === "ios"
? encodeURIComponent(Sound.MAIN_BUNDLE)
: Sound.MAIN_BUNDLE;
this.sound = new Sound("audio/" + file, pathType, (error) => {
/* .... */
});
};
componentWillUnmount() {
try {
if (this.sound) {
this.sound.release();
}
} catch (error) {
console.log("A sound release error has occured 333");
} finally {
this.sound = null;
}
}
render() {
/* .... */
}
}
回答4:
I think crashes may happen when audio files are loading and blur
event happens. And you didn't check for this state. So when you are using
this.sound = new Sound('audio/' + file, Sound.MAIN_BUNDLE, (error) => ...)
this.sound
becomes valid and the audio file starts to load. But immediately blur
event happens and you release this.sound
. In the meanwhile, loading is completed and tries to play! nothing is there and you get a crash!
Solution: you must check both this.sound
and this.sound.isLoaded()
(ref) on bluring event.
回答5:
Just a quick guess here, but are you sure that setting null to the object of sound deltes it from memory? might just have another "pointer" to it.
have you tried the same code with delete just to try and if this might be the issue? this.sound = null instead try delete this.sound or somthing of this sorts?
I know delete counts as bad optimization practice but might just help you here.
回答6:
React Native: How to Load and Play Audio
https://rossbulat.medium.com/react-native-how-to-load-and-play-audio-241808f97f61
'react-native-audio has not been updated in over 2 years.. should be avoided...use expo-av instead...' - pulled from the article
Expo AV
https://docs.expo.io/versions/latest/sdk/audio/
export class Controller {
apiEndpoint = 'https://<your_domain>/audio/load';
audioFemale = new Audio.Sound();
audioMale = new Audio.Sound();
/* resetAudioClips
* stops and unloads any existing audio clips asynchronously,
* without blocking execution
*/
resetAudioClips = async () => {
this.audioFemale.unloadAsync();
this.audioMale.unloadAsync();
}
/* cautiousResetAudioClips
* stops and unloads any existing audio clips asynchronously,
* fetching checking the audio state first.
* Also blocks execution until audio is unloaded.
*/
cautiousResetAudioClips = async () => {
let femaleStatus = await this.audioFemale.getStatusAsync();
let maleStatus = await this.audioMale.getStatusAsync();
if (femaleStatus.isLoaded === true) {
await this.audioFemale.stopAsync()
await this.audioFemale.unloadAsync();
}
if (maleStatus.isLoaded === true) {
await this.audioMale.stopAsync()
await this.audioMale.unloadAsync();
}
}
/* loadClips
* token: some authentication token to your API
* uriFemale: the path to the requested female audio clip
* uriMale: the path to the requested male audio clip
*
* example:
* Controller.loadClips('s!ke9r3qie9au$2kl#d', '/audio/female/a_394.mp3', '/audio/male/a_394.mp3');
*/
loadClips = async (token, uriFemale, uriMale) => {
this.audioFemale.loadAsync({
uri: this.apiEndpoint,
headers: {
token: token,
file: uriFemale,
}
}, {
shouldPlay: false,
volume: 1,
});
this.audioMale.loadAsync({
uri: this.apiEndpoint,
headers: {
token: token,
file: uriMale,
}
}, {
shouldPlay: false,
volume: 1,
});
}
/* playAudioplay
* play audio by gender
*/
playAudio = async (gender) => {
if (gender == 'female') {
this.audioFemale.replayAsync();
} else {
this.audioMale.replayAsync();
}
}
/* stopAudio
* stops all audio
*/
stopAudio = async () => {
await this.audioFemale.stopAsync();
await this.audioMale.stopAsync();
}
}
来源:https://stackoverflow.com/questions/65398645/intermittent-crashes-with-audio-in-react-native