-
-
Notifications
You must be signed in to change notification settings - Fork 25
Description
Description
Bug Report: AudioManager play/pause issue on iOS with react-native-audio-api
Description:
There appears to be an issue with the AudioManager in the react-native-audio-api package on iOS. Specifically:
AudioManager play/pause → App UI play/pause: Works fine.
App UI play/pause → AudioManager play/pause (lock screen/control center): Does not work on iOS. The lock screen and control center UI do not respond to play/pause commands, even though the state parameter is passed as "state_playing" or "state_paused". This functionality works as expected on Android.
Environment:
React Native Version: 0.80.1
Platform: iOS (not working), Android (working)
Library: react-native-audio-api
Code Details:
Here is the relevant code used in the implementation:
// Helper function to create lock screen data
const createLockScreenData = useCallback(
(
title: string,
duration: number,
elapsedTime: number = 0,
state: MediaState = "state_paused", // Even when passing "state_playing" or "state_paused", the iOS lock screen/control center play/pause UI does not work. Android works fine.
) => {
// Validate and sanitize data for iOS
const safeDuration = Math.max(0, Math.floor(duration || 0));
const safeElapsedTime = Math.max(
0,
Math.min(Math.floor(elapsedTime || 0), safeDuration),
);
return {
title: title || "Unknown Track",
artist: appConfig.displayName || appConfig.name,
album: "Meditation",
duration: safeDuration,
elapsedTime: safeElapsedTime,
state,
speed: 1.0,
};
},
[],
);
// Update lock screen info
const updateLockScreenInfo = useCallback(
(state: MediaState) => {
try {
// Only update if we have valid duration and audio data
if (!audioData?.name || playerState.duration <= 0) {
console.log("Skipping lock screen update - insufficient data");
return;
}
const lockScreenData = createLockScreenData(
audioData.name,
playerState.duration,
playerState.currentTime,
state,
);
console.log("📱 Updating lock screen info:", {
title: lockScreenData.title,
state: lockScreenData.state,
currentTime: lockScreenData.elapsedTime,
duration: lockScreenData.duration,
isPlaying: playerState.isPlaying,
});
AudioManager.setLockScreenInfo(lockScreenData);
} catch (error) {
console.error("❌ Error in updateLockScreenInfo:", error);
}
},
[
audioData?.name,
playerState.duration,
playerState.currentTime,
playerState.isPlaying,
createLockScreenData,
],
);
// Setup media controls and audio session
useEffect(() => {
try {
// Configure audio session for iOS with better options
AudioManager.setAudioSessionOptions({
iosCategory: "playback",
iosMode: "default",
iosOptions: [
"allowBluetooth",
"allowAirPlay",
"defaultToSpeaker",
"allowBluetoothA2DP",
],
});
// Enable remote commands - more comprehensive for iOS
AudioManager.enableRemoteCommand("remotePlay", true);
AudioManager.enableRemoteCommand("remotePause", true);
AudioManager.enableRemoteCommand("remoteStop", true);
AudioManager.enableRemoteCommand("remoteSkipForward", true);
AudioManager.enableRemoteCommand("remoteSkipBackward", true);
AudioManager.observeAudioInterruptions(true);
// Setup remote control event listeners
remotePlaySubscriptionRef.current = AudioManager.addSystemEventListener(
"remotePlay",
() => {
console.log("Remote play command received");
// Use current refs to avoid stale closure issues
if (!playerStateRef.current.isPlaying) {
playAudioRef.current();
}
},
);
remotePauseSubscriptionRef.current = AudioManager.addSystemEventListener(
"remotePause",
() => {
console.log("Remote pause command received");
// Use current refs to avoid stale closure issues
if (playerStateRef.current.isPlaying) {
pauseAudioRef.current().catch(console.error);
}
},
);
// Handle audio interruptions (calls, other apps, etc.)
interruptionSubscriptionRef.current = AudioManager.addSystemEventListener(
"interruption",
(event) => {
console.log("Audio interruption event:", event);
if (event.type === "began") {
// Audio was interrupted, pause if playing
if (playerStateRef.current.isPlaying) {
pauseAudioRef.current().catch(console.error);
}
} else if (event.type === "ended" && event.shouldResume) {
// Interruption ended, resume if we should
if (!playerStateRef.current.isPlaying) {
playAudioRef.current().catch(console.error);
}
}
},
);
console.log("Media controls setup completed");
} catch (error) {
console.error("Error setting up media controls:", error);
}
return () => {
try {
// Cleanup event listeners
remotePlaySubscriptionRef.current?.remove();
remotePauseSubscriptionRef.current?.remove();
interruptionSubscriptionRef.current?.remove();
// Disable remote commands
AudioManager.enableRemoteCommand("remotePlay", false);
AudioManager.enableRemoteCommand("remotePause", false);
AudioManager.enableRemoteCommand("remoteStop", false);
AudioManager.enableRemoteCommand("remoteSkipForward", false);
AudioManager.enableRemoteCommand("remoteSkipBackward", false);
AudioManager.observeAudioInterruptions(false);
console.log("Media controls cleanup completed");
} catch (error) {
console.error("Error cleaning up media controls:", error);
}
};
}, []);
// ... other logic for loading and playing/pausing audio
Please investigate this issue with the AudioManager on iOS and provide guidance or a fix for enabling lock screen/control center play/pause functionality.
Steps to reproduce
1: react-native 0.80.1
2: Code function handle audio with react-native-audio-api
3: Run AudioManager and test bug on IOS
Snack or a link to a repository
https://github.com/software-mansion/react-native-audio-api
React Native Audio API version
0.6.4
React Native version
0.80.1
Platforms
iOS
JavaScript runtime
None
Workflow
React Native
Architecture
Fabric (New Architecture)
Build type
Debug app & dev bundle
Device
None
Device model
Iphone
Acknowledgements
Yes