简体   繁体   中英

Intermittent crashes with audio in react-native

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>
        )



    }
}

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
                    }
                })
            }
        });
}

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..

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.

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.

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() {
    /* .... */
  }
}

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();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM