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

Posted

技术标签:

【中文标题】react-native 中的音频间歇性崩溃【英文标题】:Intermittent crashes with audio in react-native 【发布时间】:2021-03-31 13:31:37 【问题描述】:

播放音频时崩溃:

我正在创建一个包含多个不同屏幕的音频剪辑的应用程序。我们正在使用 Testflight 在 iPhone/iPad 上测试该应用程序,并且在播放音频剪辑时出现间歇性崩溃。音频剪辑本身似乎没有任何问题,因为它们大部分时间都在工作,而且问题并不总是同一个音频剪辑。

我猜这可能是与new Sound 相关的内存泄漏,但我不确定如何对此进行测试。我还认为我通过确保在声音组件播放后和屏幕卸载时released 来解决这个问题。

在确保正确清理 this.sound 组件时,我是否遗漏了什么?

我的代码:

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



    

【问题讨论】:

你应该在 yojur 项目中添加 Sentry,它会检测到崩溃并给你提示:docs.sentry.io/platforms/react-native 【参考方案1】:

检查声音是否已经是一个对象:

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

【讨论】:

但是原代码不应该只是覆盖前一个对象,从而不在内存中保留多个声音对象吗? @kojow7 你可以使用 Map 来跟踪 Sound 对象【参考方案2】:

这可能是this.sound.release() 被调用两次的竞争条件吗?因此,它第二次被调用为 null 并且它崩溃了。我的意思是在播放声音后和在“模糊”事物监听器处同步调用它。应该可能会被异常块捕获,但我对 js 没有足够的经验知道这一点..

【讨论】:

【参考方案3】:

我认为在加载音频文件和blur 事件发生时可能会发生崩溃。你没有检查这个状态。所以当你在使用

this.sound = new Sound('audio/' + file, Sound.MAIN_BUNDLE, (error) => ...)

this.sound 生效并开始加载音频文件。但立即blur 事件发生,你释放this.sound。与此同时,加载完成并尝试播放!什么都没有,你会崩溃!

解决方案:您必须同时检查 this.soundthis.sound.isLoaded()(ref) 的模糊事件。

【讨论】:

【参考方案4】:

React Native:如何加载和播放音频

https://rossbulat.medium.com/react-native-how-to-load-and-play-audio-241808f97f61

'react-native-audio 已经超过 2 年没有更新了.. 应该避免...改用 expo-av...' - 摘自文章

世博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();
  

【讨论】:

【参考方案5】:

这里只是一个快速的猜测,但你确定将 null 设置为 sound 的对象会从内存中删除它吗?可能只是有另一个“指针”。

您是否尝试过删除相同的代码只是为了尝试,如果这可能是问题? this.sound = null 而是尝试 删除 this.sound 或类似的东西?

我知道删除算作不好的优化做法,但在这里可能会对您有所帮助。

【讨论】:

【参考方案6】:

这可能有帮助,请查看它

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

【讨论】:

您能否进一步解释一下最初的问题可能是什么? @kojow7 我认为资源可能还没有释放。我试图提供帮助,希望您的问题能够得到解决。谢谢:-)

以上是关于react-native 中的音频间歇性崩溃的主要内容,如果未能解决你的问题,请参考以下文章

AUParameterTree 释放导致死锁

xcode ios间歇性崩溃

在我的 react-native 应用程序中的 Swift 中编译器生成的崩溃

Visual Studio 2015 间歇性崩溃

UITableView willDisplayCell崩溃

P/Invoke 和内存相关的间歇性崩溃