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

`mixWithOthers` does not function correctly on iOS.

Open dsf3449 opened this issue 2 years ago • 7 comments

:beetle: Description

mixWithOthers permanently stops audio streams on iOS from other apps such as Spotify, even though mixWithOthers is declared as true.

:beetle: What is the observed behavior?

Audio streams are stopped.

:beetle: What is the expected behavior?

The sound being played from another app is played along side the sound from your app.

:beetle: Please post your code:

In a brand new template app:

  1. npx react-native init TestClean
  2. npm i react-native-sounds
  3. pod install
  4. Add loop.mp3 to the xcode project.
  5. Check off background modes: Audio, AirPlay, and Picture in Picture, External accessory communication
  6. In App.js:
import Sound from "react-native-sound";

Sound.setCategory(`Playback`, true);
Sound.setMode(`Default`);

let sound = new Sound(`loop.mp3`, Sound.MAIN_BUNDLE, error => {
  sound.play();
  if (error) {
    console.error(error);
  } else {
    console.log('success');
  }
});

:bulb: Possible solution

According to the developer docs for AVAudioSessionCategoryOptionAllowBluetooth:

You can set this option only if the audio session category is AVAudioSessionCategoryPlayAndRecord or AVAudioSessionCategoryRecord.

However, in https://github.com/zmxv/react-native-sound/blob/1aa45f25c8c03ea5c17fac18564c3928bd023113/RNSound/RNSound.m#L175-L180

It is setting this option regardless of the category.

:bulb: Is there a workaround?

Removing this declaration AVAudioSessionCategoryOptionAllowBluetooth no longer causes the sound to interrupt other apps. I also checked with bluetooth headphones, and there was no difference in behavior with or without this option being present.

Note: I tested this workaround only on playback category, iOS 15.4.

Using patch-package:

diff --git a/node_modules/react-native-sound/RNSound/RNSound.m b/node_modules/react-native-sound/RNSound/RNSound.m
index df3784e..aa97df6 100644
--- a/node_modules/react-native-sound/RNSound/RNSound.m
+++ b/node_modules/react-native-sound/RNSound/RNSound.m
@@ -175,8 +175,7 @@ - (NSDictionary *)constantsToExport {
     if (category) {
         if (mixWithOthers) {
             [session setCategory:category
-                     withOptions:AVAudioSessionCategoryOptionMixWithOthers |
-                                 AVAudioSessionCategoryOptionAllowBluetooth
+                     withOptions:AVAudioSessionCategoryOptionMixWithOthers
                            error:nil];
         } else {
             [session setCategory:category error:nil];

:bulb: If the bug is confirmed, would you be willing to create a pull request?

Sure

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: 0.67.4
  • iOS: iOS 15.4
  • Android: n/a
  • Windows: n/a

Does the problem occur on...

  • [ ] Simulator
  • [x] Device

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

  • Device: iPhone 13 Pro Max

dsf3449 avatar Apr 04 '22 20:04 dsf3449

OH MY GOD thank you so much @dsf3449!!! This is exactly the change I needed to make to get my app sounds to work while spotify is running.

To put the steps succintly.

  1. Set your background audio capability in XCode (or info.plist)

  2. In your instantiation of the Sound module, write

// Make sure to use Playback and set Mixwithothers to true
    Sound.setCategory('Playback', true)
// You only need to call the setActive(true) line *once in your code*, and you never set it to false, 
// EVER unless you no longer want audio to be played in background. 
// You do not need to try calling setActive(false) at any point in the onCompletion methods
    Sound.setActive(true)
  1. Then navigate to node_modules/react-native-sound/RNSound/RNSound.m
  2. Edit the line below as such Change
if (category) {
        if (mixWithOthers) {
            [session setCategory:category
                     withOptions:AVAudioSessionCategoryOptionMixWithOthers | 
                           AVAudioSessionCategoryOptionAllowBluetooth
                           error:nil];
        } else {
            [session setCategory:category error:nil];
        }
    }

TO

if (category) {
        if (mixWithOthers) {
            [session setCategory:category
                     withOptions:AVAudioSessionCategoryOptionMixWithOthers 
                    //  | AVAudioSessionCategoryOptionAllowBluetooth // I moved the '|' to the next line and commented it out
                           error:nil];
        } else {
            [session setCategory:category error:nil];
        }
    }

ucheNkadiCode avatar Apr 13 '22 20:04 ucheNkadiCode

Thanks, guys! I had reported the same here as well, but as a feature request as I didn't know the lib supported this behavior.

https://github.com/zmxv/react-native-sound/issues/781

pedpess avatar Apr 14 '22 10:04 pedpess

I have a peculiar case where I want the sound to play/mix together with Spotify with the device looking and the app in the background. Did you @ucheNkadiCode and @dsf3449 manage to make it work? I have tried iOS 15.4 it didn't

pedpess avatar May 11 '22 12:05 pedpess

We had the same issue. We have sounds playing with Sound.setCategory('Ambient', true); which worked fine in the past and stopped working in the meantime.

I can confirm that the patch of @dsf3449 works and solves the issue. Thank you!

@ucheNkadiCode You should check out https://www.npmjs.com/package/patch-package instead of manually editing the file after each npm/yarn install.

swey avatar Jan 23 '23 13:01 swey

More than a year later; do we still have to use this patch? I am still facing it with latest version :/

pierroo avatar Aug 01 '23 10:08 pierroo

@pierroo I don't believe the repo author has made any changes regarding this specific issue. Many PRs outstanding and no releases for over 1.5 years. I would assume it to be abandoned at this time.

As for alternatives, unfortunately the state of audio packages for react native seems to be quite fragmented. This reddit thread lists at least 6 different options depending on your use case.

dsf3449 avatar Aug 01 '23 19:08 dsf3449

hello !

I modified the RNSound.m ... but the music still stop when my app starts. Here is my code :

init = async () => {

      Sound.setCategory('Playback', true);
      Sound.setActive(true);

  }

  play = async () => {

      try {

              var ding = new Sound("test.mp3", Sound.MAIN_BUNDLE, (error) => {
                  if (error) {
                      console.log('Sound / play / failed to load the sound', error);
                  }
                  else {

                      ding.play(async (success) => {
                          if (success) {
                              ding.release()
                          }
                      });
                  }
              });
          }
          console.log('Sound / play / exit');
      } catch (error) {
          console.log('Sound / play / error', error);
      }
  }

RCT_EXPORT_METHOD(setCategory
                  : (NSString *)categoryName mixWithOthers
                  : (BOOL)mixWithOthers) {
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSString *category = nil;

    if ([categoryName isEqual:@"Ambient"]) {
        category = AVAudioSessionCategoryAmbient;
    } else if ([categoryName isEqual:@"SoloAmbient"]) {
        category = AVAudioSessionCategorySoloAmbient;
    } else if ([categoryName isEqual:@"Playback"]) {
        category = AVAudioSessionCategoryPlayback;
    } else if ([categoryName isEqual:@"Record"]) {
        category = AVAudioSessionCategoryRecord;
    } else if ([categoryName isEqual:@"PlayAndRecord"]) {
        category = AVAudioSessionCategoryPlayAndRecord;
    }
#if TARGET_OS_IOS
    else if ([categoryName isEqual:@"AudioProcessing"]) {
        category = AVAudioSessionCategoryAudioProcessing;
    }
#endif
    else if ([categoryName isEqual:@"MultiRoute"]) {
        category = AVAudioSessionCategoryMultiRoute;
    }

    if (category) {
        if (mixWithOthers) {
            [session setCategory:category
                     withOptions:AVAudioSessionCategoryOptionMixWithOthers
                     // | AVAudioSessionCategoryOptionAllowBluetooth
                           error:nil];
        } else {
            [session setCategory:category error:nil];
        }
    }
}

Koxx3 avatar Aug 28 '23 20:08 Koxx3