Error with audio playback on Android

We are playing a custom sound along with certain events in our app. Some of our Android users (on standalone build) are experiencing app crashes every time a sound plays. Here’s the error log:

abi24_0_0.host.exp.exponent.modules.api.av.AVModule$AudioFocusNotAcquiredException: This experience is currently in the background, so audio focus could not be acquired.

The sounds play while the app is in the foreground. Any thoughts on this?

I’m not sure, we haven’t had any other reports of this issue as far as I can tell. What type of Android device and what version of Android are the affected users running?

cc @jesse and @sjchmiela.

Affected users seem to be on Android version 24 for now

The two devices we know of are:
SM-G610M
Moto G (4)

Hi @mikesholiu!

That’s strange… This exception is thrown only by the AVModule.acquireAudioFocus method and it’s always caught in code.

  • AVModule.acquireAudioFocus is called by
    • PlayerData.playPlayerWithRateAndMuteIfNecessary which throws and is called by
      • PlayerData.handleAudioFocusGained which catches the exception and does nothing,
      • PlayerData.onResume which catches the exception and does nothing,
      • PlayerData.applyNewStatus which throws and is called by
        • SimpleExoPlayerData.onPlayerStateChanged which catches the exception and does nothing,
        • PlayerData.setStatusWithListener which catches the exception, abandons audio focus if it’s unused and rejects the promise of AVModule.loadForSound or VideoView.setUri or AVModule.setStatus.
  1. How do you load and setStatus of the sound? Maybe you don’t catch the exception on JS side and unhandled promise rejection crashes the application?
  2. This exception is thrown based on the value of mAppIsPaused property of AVModule which is updated by onHostResume which definitely should be called when app comes to foreground. It would be strange for this code to fail.

Thanks @sjchmiela,

The code is below:

const playSound = async (sound) => {
    try {

        // Load sound

        const soundObject = new (Expo.Audio.Sound)()
        await soundObject.loadAsync(sound.soundFile)

        // Before playing, create a callback that will set `shouldPlay` to `false` after it
        // finishes playing -- without this, sounds played previously will play every time the app is re-foregrounded.

        const onPlaybackStatusUpdate = async (playbackStatus) => {
            if (playbackStatus.didJustFinish) {
                await soundObject.setStatusAsync({ shouldPlay: false})
            }
        }
        soundObject.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate)

        // Play the sound

        await soundObject.setStatusAsync({
            shouldPlay: true,
            positionMillis: 0,
        })

    } catch (error) {
        if (error.code === 'E_AV_SEEKING') {
            // Do nothing; this is expected when multiple sounds are played in a row.
        } else {
            console.error(error)
        }
    }
}

Thanks for the reply! I’ve got two ideas:

  1. It’s just a hunch, but doesn’t console.error cause red box to appear, which in production crashes the application? I may be wrong on this one, the only mention of this behavior I found was is here, and information there may be outdated.
  2. Silly mistake, you don’t try-catch error in onPlaybackStatusUpdate, but that shouldn’t cause this exception to be raised, as you set shouldPlay to false

I’ll try out your code later, maybe I’ll find something more. :slight_smile:

@sjchmiela Ah! You are right (on iOS it doesn’t crash the app but with Android console.error() causes it to restart). Thanks for sending that link. I believe this should be the fix. Really appreciate the timely response.

1 Like

same problem … We are playing a custom sound along with certain events in our app. It is playing when app is foreground but app running in background it is not. Here’s the error log:

[Unhandled promise rejection: Error: abi32_0_0.host.exp.exponent.modules.api.av.AVModule$AudioFocusNotAcquiredException: This experience is currently in the background, so audio focus could not be acquired.]

  • node_modules\react-native\Libraries\BatchedBridge\NativeModules.js:146:41 in createErrorFromErrorData
  • node_modules\react-native\Libraries\BatchedBridge\NativeModules.js:95:55 in
  • node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:397:4 in __invokeCallback
  • node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:127:28 in
  • node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:297:10 in __guard
  • node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:126:17 in invokeCallbackAndReturnFlushedQueue
  • [native code]:null in invokeCallbackAndReturnFlushedQueue

Below is mycode:

   Audio.setAudioModeAsync({
    allowsRecordingIOS: false,
    interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
    playsInSilentModeIOS: true,
    shouldDuckAndroid: true,
    interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
    playThroughEarpieceAndroid: false,
    staysActiveInBackground:true
});
if (playbackInstance != null) {
  await playbackInstance.unloadAsync();
  playbackInstance.setOnPlaybackStatusUpdate(null);
  playbackInstance = null;
}
const source = require('./assets/alarm.mp3');
const initialStatus = {
//        Play by default
    shouldPlay: true,
//        Control the speed
    rate: 1.0,
//        Correct the pitch
    shouldCorrectPitch: true,
//        Control the Volume
    volume: 1.0,
//        mute the Audio
    isMuted: false
};
const { sound, status } = await Audio.Sound.createAsync(
   source,
   initialStatus
);
console.log(status ,'status');

//  Save the response of sound in playbackInstance
playbackInstance = sound;
//  Make the loop of Audio
playbackInstance.setIsLoopingAsync(false);
//   the Music
playbackInstance.playAsync();

Hi Guys, the approach will be in two steps:

First, stop any sound in background experience. Something like:

let youSoundObject = new Audio.Sound();
try {
    await youSoundObject.loadAsync(sound);
    await youSoundObject.playAsync();
} catch (error) {
    await youSoundObject.unloadAsync(); // Unload any sound loaded
    youSoundObject.setOnPlaybackStatusUpdate(null); // Unset all playback status loaded
    youSoundObject = new Audio.Sound();  // Restard you Sound Object
}

Second, make bussines logic when the error is present, like:

  • Retry loading sound. When you do this, is 99% sure to success in second try.
  • Feedback to user about the error

Examples:

playYouSound = async () => {
    let youSoundObject = new Audio.Sound();
    try {
        await youSoundObject.loadAsync(sound);
        await youSoundObject.playAsync();
    } catch (error) {
        await youSoundObject.unloadAsync(); // Unload any sound loaded
        youSoundObject.setOnPlaybackStatusUpdate(null); // Unset all playback status loaded
        retryPlaySound();
    }
}

retryPlaySound = () => this.playYouSound();

Hope this will be helpful.

1 Like