react-native-sound icon indicating copy to clipboard operation
react-native-sound copied to clipboard

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

Open kyrstencarlson opened this issue 3 years ago • 4 comments

: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...

  • [x] iOS
  • [ ] Android
  • [ ] Windows

Are you using...

  • [x] React Native CLI (e.g. react-native run-android)
  • [ ] Expo
  • [ ] Other: (please specify)

Which versions are you using?

  • React Native Sound: 0.11.2
  • React Native: 7.5.1
  • iOS: 15.5
  • Android:
  • Windows:

Does the problem occur on...

  • [ ] Simulator
  • [x] Device

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

  • Device: iPhone 12

kyrstencarlson avatar Oct 18 '22 14:10 kyrstencarlson

Did you find any solution for this issue?

olkunmustafa avatar Dec 04 '22 21:12 olkunmustafa

Not yet

kyrstencarlson avatar Dec 31 '22 08:12 kyrstencarlson

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 avatar Apr 21 '23 14:04 j-d-salinger

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!!!!!!

j-d-salinger avatar Apr 26 '23 17:04 j-d-salinger