DEV Community

Cover image for How to Add Background Audio to Expo Apps
Josie Daw
Josie Daw

Posted on

How to Add Background Audio to Expo Apps

One of the most common problems that I've come across with audio with Expo apps is trying to get the audio to play even while the app is no longer on screen/in focus. There are many use cases where you want audio to persist from your app even while using another app or when the screen is locked.

The guide below is a simple tutorial for how to use Expo AV to play audio in the background of your Expo app.


Table of Contents

  1. Get Started
  2. Install Expo AV
  3. Set Up a Basic Audio Button
  4. Add Allow Background Playing

Get Started

First, create an Expo app. You can followthis tutorialif it is your first time using Expo.

Once you have your basic app set up, you can run it on Expo Go (or an emulator/device if you prefer).


Install Expo AV

Expo has a great audio library ready for you to use, you just need to install it. Runnpx expo install expo-av.


Set Up a Basic Audio Button

For this tutorial, we will create a very basic audio player based on a simple play button (in Typescript)

First, create a new file calledAudioButton.tsx.In this case, we will pass a URL from the parent component, so we will add it as a props withAudioButtonProps.

Then create two basic states to hold our playing status and sound status.

Next, we want to create auseEffectto prepare the audio on load. Inside theuseEffect,we will set all of the important options to enable audio to play in the background in an Expo app.

You will notice that we havestaysActiveInBackgroundset totrue.This is a basic requirement, but is not enough on its own for the background audio to work.

The most common problem that people have with Expo AV is that they don't set all of the options:

staysActiveInBackground: true,
playsInSilentModeIOS: true,
interruptionModeIOS: InterruptionModeIOS.DuckOthers,
interruptionModeAndroid: InterruptionModeAndroid.DuckOthers,
shouldDuckAndroid: true,
playThroughEarpieceAndroid: true,
Enter fullscreen mode Exit fullscreen mode

Each of these options needs to be set for audio to play in the background in every case on different devices.

ForinterruptionModeIOSandinterruptionModeAndroid,you can chooseMixWithOthers,DoNotMix,orDuckOthers.We have setDuckOthersso that any other audio nicely fades out, which makes for a more pleasant user experience.

You can read more details about these options on theExpo Audiopage.

// AudioButton.tsx

import { Audio, InterruptionModeAndroid, InterruptionModeIOS } from 'expo-av';
import { Sound } from 'expo-av/build/Audio';

interface AudioButtonProps {
audioUrl: string;
}

const AudioButton = ({ audioUrl }: AudioButtonProps) => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [sound, setSound] = useState<Sound | null>(null);

useEffect(() => {
Audio.setAudioModeAsync({
staysActiveInBackground: true,
playsInSilentModeIOS: true,
interruptionModeIOS: InterruptionModeIOS.DuckOthers,
interruptionModeAndroid: InterruptionModeAndroid.DuckOthers,
shouldDuckAndroid: true,
playThroughEarpieceAndroid: true,
});
return sound
?() => {
sound.unloadAsync();
}
:undefined;
}, [sound]);


};

export default AudioButton;

Enter fullscreen mode Exit fullscreen mode

Next, we will create a function to play the sound and a little button to trigger it with. Expo comes with Vector icons built-in, so you can change theFontAwesomeicon to any you prefer. You can search the iconshere.

const playAudio = async () => {
// Set and play the sound
const { sound: newSound } = await Audio.Sound.createAsync({ uri: audioUrl });
setSound(newSound);

setIsPlaying(true);
await newSound.playAsync();

// After the sound has finished, update the state so that the icon changes
newSound.setOnPlaybackStatusUpdate((status) => {
if ('didJustFinish' in status && status.didJustFinish) {
setIsPlaying(false);
}
});
};

return (
<TouchableOpacity onPress={playAudio}>
<FontAwesome name={isPlaying? 'volume-up': 'play'} size={15} color= "#6b7280" />
</TouchableOpacity>
);
Enter fullscreen mode Exit fullscreen mode

Let's also add a bit of styling so it looks like a nice round play button.

<TouchableOpacity style={styles.button} onPress={playAudio}>
<FontAwesome name={isPlaying? 'volume-up': 'play'} size={15} color= "#6b7280" />
</TouchableOpacity>


// Include this after the AudioButton export
const styles = StyleSheet.create({
button: {
height: 30,
width: 30,
borderRadius: 15, // Half of the height/width
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '#e5e7eb',
marginHorizontal: 5,
},
});
Enter fullscreen mode Exit fullscreen mode

Now we will put all of the code together so you can see where all of the pieces go.

import { FontAwesome } from '@expo/vector-icons';
import { Audio, InterruptionModeAndroid, InterruptionModeIOS } from 'expo-av';
import { Sound } from 'expo-av/build/Audio';
import React, { useEffect, useState } from 'react';
import { StyleSheet, TouchableOpacity } from 'react-native';

interface AudioButtonProps {
audioUrl: string;
}

const AudioButton = ({ audioUrl}: AudioButtonProps) => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [sound, setSound] = useState<Sound | null>(null);

// Prepare the audio
useEffect(() => {
Audio.setAudioModeAsync({
staysActiveInBackground: true,
playsInSilentModeIOS: true,
interruptionModeIOS: InterruptionModeIOS.DuckOthers,
interruptionModeAndroid: InterruptionModeAndroid.DuckOthers,
shouldDuckAndroid: true,
playThroughEarpieceAndroid: true,
});
return sound
?() => {
sound.unloadAsync();
}
:undefined;
}, [sound]);

// Trigger the audio
const playAudio = async () => {
const { sound: newSound } = await Audio.Sound.createAsync({ uri: audioUrl });
setSound(newSound);

setIsPlaying(true);
await newSound.playAsync();

newSound.setOnPlaybackStatusUpdate((status) => {
if ('didJustFinish' in status && status.didJustFinish) {
setIsPlaying(false);
}
});
}
};

return (
<TouchableOpacity style={styles.button} onPress={playAudio}>
<FontAwesome name={isPlaying? 'volume-up': 'play'} size={15} color= "#6b7280" />
</TouchableOpacity>
);
};

export default AudioButton;

const styles = StyleSheet.create({
button: {
height: 30,
width: 30,
borderRadius: 15, // Half of the height/width
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '#e5e7eb',
marginHorizontal: 5,
},
});

Enter fullscreen mode Exit fullscreen mode

Now we have our audio button with some background settings included. You can include this button anywhere in your app with<AudioButton audioUrl= "https://some-audio-url-here.com" />

However, we still have to make some extra changes to ensure that it will work with iOS and Android devices.


Add Allow Background Playing

For iOS and Android to allow background audio, you should update yourapp.json/app.config.jsto includeUIBackgroundModes: ['audio']andpermissions: ['WAKE_LOCK'].

permissions: ['WAKE_LOCK']will keep the Android version of the app active while the screen is locked, but can drain power, so be careful of using this permission if it is not required.

If you do not includeUIBackgroundModes: ['audio']your app can be rejected by the App Store reviewers.

"ios": {
"buildNumber": "1",
"infoPlist": {
"UIBackgroundModes": [ "audio" ]
},
"bundleIdentifier": "com.fakeapp"
},
"android": {
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFF"
},
"package": "com.fakeapp",
"permissions": [ "WAKE_LOCK" ],
"googleServicesFile": "./google-services.json"
}
Enter fullscreen mode Exit fullscreen mode

After adding all of these, you may be wondering during your own testing why it doesn't seem to work. If you are using the Expo Go app or Expo development build, thebackground audio mode will not workcorrectly, because when you close your screen or move to another app, your app will lose connection to Expo metro and stop working.

In order to confirm that the background audio definitely works, you will need to create a real build and try it on TestFlight/Internal Testing. (Just make sure to test your audio button while the app is open before creating a build for it!)


Thanks for following along with this short tutorial for how to add background audio to Expo apps! If you're interested in learning more about me, you can visit my portfoliohere.

If you are facing an error anywhere along the way, feel free to leave a comment below and we can try to debug it together!

Top comments(5)

Collapse
meteorsd profile image
meteorSD

For people having issue on react native 0.73 and android 14, here's the solution:github.com/expo/expo/issues/30371#...

Collapse
josie profile image
Josie Daw

It looks like the latest version of Expo introduced some bugs with background audio, hopefully they will patch it soon!

Collapse
saeid_houti profile image
Saeid

Thank You Josie!

Collapse
pranav1924 profile image
Pranav • Edited

I am working on alarm app in react native, I don't want to use expo-notifications or any other notification api because I want to play a sound even though the app is in background. can you suggest the solution?

Collapse
josie profile image
Josie Daw

I do not know of any other way to schedule a sound/alarm without using expo-notifications, sorry!