react-native-music-control icon indicating copy to clipboard operation
react-native-music-control copied to clipboard

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

Open rubenkaiser opened this issue 3 years ago β€’ 14 comments

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

rubenkaiser avatar Mar 07 '21 11:03 rubenkaiser

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 avatar Mar 08 '21 12:03 PupoSDC

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

PupoSDC avatar Mar 11 '21 12:03 PupoSDC

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

rubenkaiser avatar Mar 11 '21 12:03 rubenkaiser

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.

PupoSDC avatar Mar 11 '21 15:03 PupoSDC

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.

rubenkaiser avatar Mar 14 '21 12:03 rubenkaiser

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 avatar Mar 14 '21 19:03 PupoSDC

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

PupoSDC avatar Mar 26 '21 15:03 PupoSDC

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

sshah98 avatar Jun 17 '21 01:06 sshah98

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

devpolo avatar Sep 07 '21 21:09 devpolo

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

PupoSDC avatar Sep 08 '21 11:09 PupoSDC

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
      });

temahot avatar Sep 20 '21 18:09 temahot

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.

PupoSDC avatar Sep 21 '21 08:09 PupoSDC

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,
    });
  };

cjhines avatar Sep 29 '21 16:09 cjhines

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

calflegal avatar May 17 '22 17:05 calflegal