tanguyantoine / react-native-music-control

Display and manage media controls on lock screen and notification center for iOS and Android.
https://www.npmjs.com/package/react-native-music-control
MIT License
697 stars 264 forks source link

Do lockscreen controls of this library (1.3.0) work on iOs with react-native 6.3.0 and expo-av 8.6.0 #378

Open rubenkaiser opened 3 years ago

rubenkaiser commented 3 years ago

After creating a basic player with this library I noticed the lockscreen controls are not showing. On Android everything is working fine, on the simulator in iOs however I can't get it to work.

In an useEffect I set the audio mode

      await Audio.setIsEnabledAsync(true);
      await Audio.setAudioModeAsync({
        allowsRecordingIOS: true,
        playsInSilentLockedModeIOS: true,
        playsInSilentModeIOS: true,
        interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
        shouldDuckAndroid: true,
        interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
        playThroughEarpieceAndroid: false,
        staysActiveInBackground: true,
      });

Also I initialize the controls:

    MusicControl.enableBackgroundMode(true);
    MusicControl.handleAudioInterruptions(true);

    MusicControl.enableControl('closeNotification', true, {when: 'always'});

    // Basic Controls
    MusicControl.enableControl('play', true);
    MusicControl.enableControl('pause', true);
    MusicControl.enableControl('stop', true);

    MusicControl.on('play', togglePlay);
    MusicControl.on('pause', togglePlay);
    MusicControl.on('stop', stopPlay);
    MusicControl.on('closeNotification', stopPlay);

After that I only call the play on the soundObject and update the controls:

await soundObject.playAsync();
MusicControl.setNowPlaying({ ... });

Any help is appreciated

PupoSDC commented 3 years ago

Im also struggling a lot with this: https://github.com/tanguyantoine/react-native-music-control/issues/377

Im still investigating, but so far I've realized the library does not work until you stop the controls once (lol). And since you can only stop the controls after starting. You need something like this:

        // fake start, necessary so we can stop the plugin safely
        MusicControl.enableBackgroundMode(true);
        MusicControl.handleAudioInterruptions(true);
        MusicControl.enableControl(Command.play, true);
        MusicControl.setNowPlaying({});
        MusicControl.updatePlayback(data); // you also need this or it wont work...

        setTimeout(() => {
            // The plugin doesn't work until we stop it at least once...
            // so we do it here.
            MusicControl.stopControl();
        }, 50);

        setTimeout(() => {
            // Real start.
            MusicControl.handleAudioInterruptions(true);
            MusicControl.enableBackgroundMode(true);
            MusicControl.enableControl(Command.play, true);
            MusicControl.enableControl(Command.pause, true);
            MusicControl.on(Command.play, onPlay);
            MusicControl.on(Command.pause, onPause);
            MusicControl.setNowPlaying(data);   
        }, 100);
PupoSDC commented 3 years ago

I finally got my hands on a real ios device, and unfortunately my rather hackish solution above only works on an emulator, not on a real device :(

rubenkaiser commented 3 years ago

I tried your "hack" on both the simulator and a real device and it did not work in my project. Maybe you can share your simplest version of the app that worked on the simulator for me to check. I tried to run it on ios 14.4

PupoSDC commented 3 years ago

This is my current PoC with the basic needs of my project:

https://github.com/PupoSDC/react-native-multimedia-demo

it includes a video and audio player in which both are controlled via the native music controls.

Meanwhile i fixed the problem with ios devices by not setting mixWithOthers on react-native-video. Most of the problems with this library relate to how other libraries interact with the same base APIs, so if you are using different audio/video libraries your mileage may vary.

Im not using expo.

rubenkaiser commented 3 years ago

Replacing Expo-av with react-native-sound-player fixed the issue in my app. I do still need the time out fix you proposed. My guess is that it indeed was an issue with expo-av and it's ios settings.

PupoSDC commented 3 years ago

yeay! I think the root cause of all this silliness, is that most of these audio libraries also fiddle with the native controls in way or the other, and they all use the same base level API.

Thanks for confirming the timeout fix is needed for you as well, and that insanity is not my own only πŸ˜…

PupoSDC commented 3 years ago

After some back and forwarding we discovered the above solution / hack only works in development mode. as soon as the app is built, it stops working :(

If anyone has any ideas, it would be awesome to hear from you

sshah98 commented 3 years ago

Yeah we've tried using expo-av and react-native-music-control to no avail. Thanks for this thread, going to switch to react-native-sound-player as well :)

devpolo commented 3 years ago

Hi everyone πŸ‘‹ Any news on that issue ? I’m stuck for many days now :/

PupoSDC commented 3 years ago

The IOS specific formula that worked for me ended up looking something like:

  MusicControl.handleAudioInterruptions(true);
  MusicControl.setNowPlaying(data);
  MusicControl.enableBackgroundMode(true);
  MusicControl.enableControl(Command.play, true);
  MusicControl.enableControl(Command.pause, true);
  MusicControl.enableControl(Command.closeNotification, true, {
    when: 'always',
  });

  MusicControl.enableControl(Command.skipBackward, !!onSkipBackward, {
    interval: skipInterval,
  });

  MusicControl.enableControl(Command.skipForward, !!onSkipForward, {
    interval: skipInterval,
  });

  MusicControl.on(Command.play, onPlay);
  MusicControl.on(Command.pause, onPause);
  MusicControl.on(Command.skipBackward, () => onSkipBackward?.(skipInterval));
  MusicControl.on(Command.skipForward, () => onSkipForward?.(skipInterval));

In any case it was still very unstable, requiring all stars to align, and some bugs still existed that I was unable to track down last i worked on this...

temahot commented 2 years ago

The IOS specific formula that worked for me ended up looking something like:

  MusicControl.handleAudioInterruptions(true);
  MusicControl.setNowPlaying(data);
  MusicControl.enableBackgroundMode(true);
  MusicControl.enableControl(Command.play, true);
  MusicControl.enableControl(Command.pause, true);
  MusicControl.enableControl(Command.closeNotification, true, {
    when: 'always',
  });

  MusicControl.enableControl(Command.skipBackward, !!onSkipBackward, {
    interval: skipInterval,
  });

  MusicControl.enableControl(Command.skipForward, !!onSkipForward, {
    interval: skipInterval,
  });

  MusicControl.on(Command.play, onPlay);
  MusicControl.on(Command.pause, onPause);
  MusicControl.on(Command.skipBackward, () => onSkipBackward?.(skipInterval));
  MusicControl.on(Command.skipForward, () => onSkipForward?.(skipInterval));

In any case it was still very unstable, requiring all stars to align, and some bugs still existed that I was unable to track down last i worked on this...

Im tried run your music control ios case on my device and this solution doesnt work in background mode πŸ˜”

Could you explain for me one thing?☺️ how you turn on background mode in code ? (i am turned on this in xcode) When I running next code in file like Main.js, music controls doesnt work. But if i delete it, controls will show and my playing audio have paused when I open controls panel

Audio.setAudioModeAsync({
            staysActiveInBackground: true
      });
PupoSDC commented 2 years ago

Hi @vedamet,

Unfortunately I can't be super useful, Im no longer working on this project, so I am working a bit out of memory + my own notes.

I'm not sure what the Audio API you posted is, so I cant give any specific advice. Generically, what I had to do to get the hang of things was study the actual IOS API under the hood, and using the xcode debugger see what calls were being made when. What i realized was that a lot of these libraries, that from the react native perspective handle different things (video, audio, controls...) interact with the same base IOS APIs, which causes conflicts. So my tip is, go deep into the ios code and try to understand what is happening in your code.

I know its super frustrating, at the end of the day, the point of react-native is to abstract away these native APIs, but in this particular case, it must be done.

cjhines commented 2 years ago

I have it working on react-native: 0.65.1 and expo-av: 9.2.3.

Audio.setAudioModeAsync({
      allowsRecordingIOS: false,
      playsInSilentModeIOS: true,
      staysActiveInBackground: true,
      interruptionModeIOS: INTERRUPTION_MODE_IOS_DO_NOT_MIX,
    });
  };
calflegal commented 2 years ago

The above

 interruptionModeIOS: INTERRUPTION_MODE_IOS_DO_NOT_MIX,

is critical.

I'm getting it via

 import {INTERRUPTION_MODE_IOS_DO_NOT_MIX} from 'expo-av/build/Audio';

According to expo-av, the default is INTERRUPTION_MODE_IOS_MIX_WITH_OTHERS https://docs.expo.dev/versions/v45.0.0/sdk/audio/#arguments-1

I think this prevents the lock screen controls from showing up