expo-audio-session
v1.0.1
Published
Expo module to resume the user's background music (Spotify, Apple Music…) after your app plays audio — deactivates the iOS AVAudioSession that expo-video leaves active
Maintainers
Readme
🎧 Expo Audio Session
Resume the user's background music after your app plays audio
An Expo module that deactivates the shared iOS audio session (AVAudioSession) once your app is done playing audio, notifying other audio apps (Spotify, Apple Music…) that they can resume playback. Built to fix the classic expo-video issue where the user's background music stays paused forever after a video played with sound.
🐛 The Problem
expo-video activates the shared AVAudioSession whenever one of its players outputs audio — but it never deactivates it. The consequences for your users:
- 🎵 They're listening to Spotify / Apple Music
- 📹 They open a video in your app, with sound → their music pauses (expected)
- ❌ They close the video → their music never resumes
On iOS, the only way to tell other apps they can resume is to deactivate your audio session with the .notifyOthersOnDeactivation option. That's exactly what this module does.
✨ Features
- Deactivate the iOS audio session with
.notifyOthersOnDeactivationso background music resumes deactivateKeepingPlaybackhelper for players that keep playing muted (feeds, loops)- No config plugin needed — just install and rebuild
- Safe to call from shared code — no-op on Android
🚀 Quick Start
Installation
# Using npm
npm install expo-audio-session
# Using yarn
yarn add expo-audio-session
# Using pnpm
pnpm add expo-audio-session
# Using Expo CLI
npx expo install expo-audio-sessionℹ️ Note: This module contains native code — rebuild your dev client (
npx expo prebuild+ build, oreas build) after installing. No config plugin required, autolinking does everything.
📖 Usage
🎯 Basic Example
import ExpoAudioSession from 'expo-audio-session';
// When your app is done outputting audio (video closed, player muted…):
// the user's background music resumes.
await ExpoAudioSession.deactivateAsync();🎬 With expo-video
import ExpoAudioSession from 'expo-audio-session';
import { useVideoPlayer, VideoView } from 'expo-video';
import { useEffect } from 'react';
export function FullscreenVideo({ uri }: { uri: string }) {
const player = useVideoPlayer(uri, (player) => {
player.play();
});
useEffect(() => {
return () => {
// The screen closes → tell iOS we are done with audio so the user's
// music resumes. The player is released on unmount, so audio I/O stops
// and deactivation succeeds.
ExpoAudioSession.deactivateAsync().catch(() => {
// Another player still outputs sound or the session is already
// inactive: the music simply does not resume, never fatal.
});
};
}, []);
return <VideoView player={player} style={{ flex: 1 }} />;
}🔇 Video that keeps playing muted
iOS refuses to deactivate the session while audio I/O is running. If your player keeps playing (e.g. a feed video that goes back to muted after fullscreen), use the helper — it briefly pauses the player around the deactivation, invisible in practice on a muted looping video:
import { deactivateKeepingPlayback } from 'expo-audio-session';
player.muted = true;
await deactivateKeepingPlayback(player);📚 API Reference
🔧 Methods
deactivateAsync(options?: DeactivateOptions): Promise<void>
Deactivate the shared audio session, notifying other audio apps they can resume. Call it once your app is done outputting audio. Rejects if the session could not be deactivated (typically: a player is still outputting sound).
No-op on Android.
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| notifyOthersOnDeactivation | boolean | true | Tell other audio apps they can resume — the whole point of the module |
deactivateKeepingPlayback(player: PausablePlayer, options?: DeactivateOptions): Promise<void>
Deactivate the session under a player that keeps playing muted: briefly pauses the player around the deactivateAsync call and resumes it afterwards. PausablePlayer is the minimal { playing, pause(), play() } shape — VideoPlayer from expo-video matches it out of the box.
📱 Platform Support
🍎 iOS
- Full
AVAudioSessionsupport .notifyOthersOnDeactivation
🤖 Android
- No-op for compatibility
- Audio focus is per-request: whoever plays the audio (ExoPlayer) requests and abandons its own focus — nothing an external module can safely release
- See What about Android? for the
expo-videopatch that fixes it
🌐 Web
- Not supported
- Will throw errors if used
🤖 What about Android?
Android has the same symptom (music never resumes after a video played with sound), but the cause — and the fix — live inside expo-video itself, which is why this module is a no-op there.
On Android, audio focus is per-request: ExoPlayer requests focus when playback starts and abandons it when playback stops. The problem is the focus type expo-video requests in AudioMixingMode.AUTO (the default): AUDIOFOCUS_GAIN signals a permanent takeover, so music apps (Spotify, YouTube Music…) treat it as "this app owns audio now" and never resume when the focus is abandoned. Requesting AUDIOFOCUS_GAIN_TRANSIENT instead signals a temporary interruption — music apps pause, then resume by themselves once your video stops outputting audio.
Until this is fixed upstream, patch expo-video with yarn patch (or patch-package for npm):
yarn patch expo-video
# edit the file below inside the printed folder, then:
yarn patch-commit -s <printed-folder>Apply this change in android/src/main/java/expo/modules/video/managers/AudioFocusManager.kt:
val audioFocusType = when (audioMixingMode) {
AudioMixingMode.DUCK_OTHERS -> AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
- AudioMixingMode.AUTO -> AudioManager.AUDIOFOCUS_GAIN
+ // Transient focus so music apps (Spotify…) resume by themselves once
+ // our video stops outputting audio and the focus is abandoned.
+ // A permanent AUDIOFOCUS_GAIN never resumes them.
+ AudioMixingMode.AUTO -> AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
AudioMixingMode.DO_NOT_MIX -> AudioManager.AUDIOFOCUS_GAIN
else -> AudioManager.AUDIOFOCUS_GAIN
}Then rebuild your dev client. No call to this module is needed on Android — the patch alone makes the music resume.
🛠️ Troubleshooting
🚨 Common Issues
Problem: deactivateAsync() rejects, or resolves but the music stays paused.
Solution: iOS refuses to deactivate the session while audio I/O is running. Make sure every player is paused, released, or muted before calling deactivateAsync(). For a player that must keep playing muted, use deactivateKeepingPlayback(player).
Problem: The native module is not found at runtime.
Solution:
- This module contains native code and does not work in Expo Go — use a dev client
- Rebuild your dev client after installing (
npx expo prebuild+ build, oreas build) - Clean and rebuild your project
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🤝 Contributing
We welcome contributions! Here's how you can help:
- 🍴 Fork the repository
- 🌿 Create a feature branch (
git checkout -b feature/amazing-feature) - 💾 Commit your changes (
git commit -m 'Add some amazing feature') - 📤 Push to the branch (
git push origin feature/amazing-feature) - 🔄 Open a Pull Request
🐛 Found a Bug?
Please open an issue with:
- 📱 Device information
- 📋 Steps to reproduce
- 📝 Expected vs actual behavior
💡 Have an Idea?
We'd love to hear your suggestions! Open an issue with the enhancement label.
⭐ Star this repo if you found it helpful!
