简体   繁体   English

react-native 中的音频间歇性崩溃

[英]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.我们正在使用 Testflight 在 iPhone/iPad 上测试该应用程序,并且在播放音频剪辑时出现间歇性崩溃。 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.我猜这可能是与new Sound有关的 memory 泄漏,但我不确定如何对此进行测试。 I also thought that I covered this by making sure that I released the sound component after it played and when the screen unmounts.我还认为我通过确保在声音组件播放后和屏幕卸载时released声音组件来解决这个问题。

Is there something that I am missing in making sure that I correctly clean up the this.sound component?在确保正确清理this.sound组件时,我是否遗漏了什么?

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:检查声音是否已经是 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?这可能是this.sound.release()被调用两次的竞争条件吗? So for the second time it is called upon null and it chrashes.所以第二次调用 null 并且它崩溃了。 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..应该可能会被异常块捕获,但我对 js 没有足够的经验知道这一点..

I think crashes may happen when audio files are loading and blur event happens.我认为在加载音频文件并且发生blur事件时可能会发生崩溃。 And you didn't check for this state.而且你没有检查这个 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. this.sound变为有效并且音频文件开始加载。 But immediately blur event happens and you release this.sound .但是会立即发生blur事件,然后您释放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.解决方案:您必须在模糊事件上同时检查this.soundthis.sound.isLoaded() ( ref )。

Just a quick guess here, but are you sure that setting null to the object of sound deltes it from memory?这里只是一个快速的猜测,但你确定将 null 设置为声音的 object 会使其与 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? this.sound = null 尝试删除 this.sound 或类似的东西?

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 React Native:如何加载和播放音频

https://rossbulat.medium.com/react-native-how-to-load-and-play-audio-241808f97f61 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 'react-native-audio 已经超过 2 年没有更新了.. 应该避免...改用 expo-av...' - 从文章中提取

Expo AV Expo AV

https://docs.expo.io/versions/latest/sdk/audio/ 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();
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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