【问题标题】:Intermittent crashes with audio in react-nativereact-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>
        )



    }
}

【问题讨论】:

标签: javascript ios react-native audio crash


【解决方案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 我认为资源可能还没有释放。我试图提供帮助,希望您的问题能够得到解决。谢谢:-)
          猜你喜欢
          • 2013-06-14
          • 2012-12-31
          • 2015-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-05-21
          相关资源
          最近更新 更多