zmxv / react-native-sound

React Native module for playing sound clips
MIT License
2.78k stars 747 forks source link

Sounds in iOS 15.5 do not mixWithOthers but do in iOS 16 #803

Open kyrstencarlson opened 1 year ago

kyrstencarlson commented 1 year ago

:beetle: Description

I have a timer app that I want to play a beep on count down and on finish. Most users are usually listening to some kind of music when they are using the app. I would like the sounds to be able to play over the music.

:beetle: What have you tried?

Changing the .setCategory() to .setCategory(Ambient, true)

:beetle: Please post your code:

Sound.setCategory('Ambient', true);
const finalBeep = new Sound('beep-single.mp3', Sound.MAIN_BUNDLE, error => {
    if (error) {
        return;
    }
});

//play function takes a boolean if the user marked sound being ON to return the sound being played.
play(finalBeep, context.isSoundOn);

:bulb: Possible solution

Is your issue with...

Are you using...

Which versions are you using?

Does the problem occur on...

If your problem is happening on a device, which device?

olkunmustafa commented 1 year ago

Did you find any solution for this issue?

kyrstencarlson commented 1 year ago

Not yet

j-d-salinger commented 1 year ago

This is part 1 of something that i think could be helpful, but i haven't totally figured it out, because now it won't get re-activated from setActive(true) afterwards

The apple documentation on this is a bit of a rabbit hole... But you can find what you're looking for in these links

https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions/1616534-interruptspokenaudioandmixwithot

https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions/1616618-duckothers

https://developer.apple.com/documentation/avfaudio/avaudiosession/setactiveoptions/1616603-notifyothersondeactivation

One link says: If your app provides occasional spoken audio, such as in a turn-by-turn navigation app or an **exercise app**, you should also set the [interruptSpokenAudioAndMixWithOthers](https://developer.apple.com/documentation/avfaudio/avaudiosession/categoryoptions/1616534-interruptspokenaudioandmixwithot) option.

Another link says: When you configure your audio session category using this option, notify other apps on the system when you deactivate your session so that they can resume audio playback. To do so, deactivate your session using the [notifyOthersOnDeactivation](https://developer.apple.com/documentation/avfaudio/avaudiosession/setactiveoptions/1616603-notifyothersondeactivation) option.

However the react-native-sound library does not mention these extra settings. There are three options to add in the RNSound.m code of the library: AVAudioSessionCategoryOptionDuckOthers, AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers, and a "setActive" option called AvAudioSessionSetActiveOptionNotifyOthersOnDeactivation

In line 178 of ./node_modules/react-native-sound/RNSound/RNSound.m, add: | AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers The "|" symbol means "and" or "&&", meaning you're adding options to this config.

Then, on line 119 of RNSound.m, add "withOptions: AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation". This should be in the RCT_ExportMethod(setActive function

Then in the RCT_ExportMethod(play function, disable the "setActive" function "//[[AVAudioSession sharedInstance] setActive:YES error:nil];"

Now, write a playSound function for your app like:

function playSound(obj){
     Sound.setActive(true);
     playSound(obj);
     // You may need to add a 'setTimeout' here to prevent sound from being clipped: 
     Sound.setActive(false);
 }

When your app plays now, it will quiet the other apps to say your exercise thing, then the other apps will be notified by setActive(false) that they can play again, and they will play

For completeness, my sound setup at the start of the app is:

Sound.setCategory("playback",true);
Sound.setActive(true)

But it doesn't totally work!!!! Perhaps this answer will work? https://stackoverflow.com/questions/20421698/start-playing-audio-from-a-background-task-via-avaudioplayer-in-xcode

j-d-salinger commented 1 year ago

I got it to work. 1) I did everything in the above comment 2) I added react-native-background-geolocation to the project to keep it processing in the background. Not sure if this was necessary, but I looked at my similar exercise app and it also needlessly uses location, so it probably was. I used the one by transistorsoft but I didn't need a license because I was just building a debug version of the app. 3) I created a new function called "setActiveFalse" in react-native-sound which has the 'notifyOthersOnDeactivation'. Yes, I named my new function this idiotic name to remind myself to ONLy call it in places where I would usually call setACtive(false). The normal "setActive" does not do the notifyOthers, but the "setActiveFalse" function does do it. Maximally lazy but it's okay. 4) When sound is done playing, I call Sound.setActiveFalse(false) in the react-native code, which allows music apps to resume bc they are notified that i deactivated. BUT, I also had to set it to Sound.setCategory("Playback",false) right after de-activation.. Then, right AFTER calling setActive(true), I Immediately called Sound.setCategory("Playback") true.

Do you like sick twisted riddles? Code a background audio exercise app for IOS!!!!!!