react-native-track-player: [Android] Crash - Not allowed to start service Intent

Configuration

package.json

    "react": "16.3.1",
    "react-native": "0.55.4",
    "react-native-track-player": "^1.1.2",
    "react-native-swift": "^1.2.2",

react-native info

Environment:
  OS: macOS High Sierra 10.13.6
  Node: 8.11.2
  Yarn: 1.9.4
  npm: 5.6.0
  Watchman: 4.9.0
  Xcode: Xcode 10.1 Build version 10B61
  Android Studio: 3.2 AI-181.5540.7.32.5056338

Packages: (wanted => installed)
  react: 16.3.1 => 16.3.1
  react-native: 0.55.4 => 0.55.4

Problem

I am having more and more of the same crash on Android using the version 1.1.2, here is the stack :

Fatal Exception: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.xxx/com.guichaguri.trackplayer.service.MusicService }: app is in background uid UidRecord{39a05c9 u0a454 CEM  bg:+1m5s685ms idle procs:1 seq(0,0,0)}
       at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1538)
       at android.app.ContextImpl.startService(ContextImpl.java:1484)
       at android.content.ContextWrapper.startService(ContextWrapper.java:663)
       at android.content.ContextWrapper.startService(ContextWrapper.java:663)
       at com.guichaguri.trackplayer.module.MusicModule.waitForConnection(MusicModule.java:100)
       at com.guichaguri.trackplayer.module.MusicModule.getPosition(MusicModule.java:405)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
       at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:160)
       at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)
       at android.os.Handler.handleCallback(Handler.java:789)
       at android.os.Handler.dispatchMessage(Handler.java:98)
       at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29)
       at android.os.Looper.loop(Looper.java:164)
       at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:192)
       at java.lang.Thread.run(Thread.java:764)

service.js

module.exports = async function service() {
  TrackPlayer.addEventListener('remote-play', () => {
    MyPlayer.playTrack(() => {});
  });

  TrackPlayer.addEventListener('remote-pause', () => {
    MyPlayer.pauseTrack(() => {});
  });

  TrackPlayer.addEventListener('remote-seek', (time) => {
    TrackPlayer.seekTo(time.position);
  });

  TrackPlayer.addEventListener('remote-jump-backward', (time) => {
    TrackPlayer.getPosition().then((position) => {
      TrackPlayer.seekTo(position - time.interval);
    });
  });

  TrackPlayer.addEventListener('remote-jump-forward', (time) => {
    TrackPlayer.getPosition().then((position) => {
      TrackPlayer.seekTo(position + time.interval);
    });
  });

  TrackPlayer.addEventListener('remote-stop', () => {
    MyPlayer.stopTrack(() => {});
  });

  TrackPlayer.addEventListener('playback-queue-ended', (infos) => {
    console.log('ended', infos);
    if (infos && infos.track) {
      MyPlayer.stopTrack(() => {});
    }
  });
};

Question

Is this solved in the newest version 1.1.3? Cause I saw a bug correction that looks like this on the last version. ==> It’s not solved in 1.1.3

Is there any workaround or maybe some configuration I am missing?

Thanks a lot

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 104 (42 by maintainers)

Commits related to this issue

Most upvoted comments

We’re seeing this crash in crashlytics also. Just had this happen on my phone. Happened right after a long track finished (+10min), app in background, screen off. The crash is not visible to the user, but next track is not started either (playback-queue-ended is probably not sent).

@Guichaguri Reproduced with latest version 1.1.3 with same exception happening on Android 8 and 9 (65% are from Android 8 and the rest for Android 9)

Fatal Exception: java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.xxx/com.guichaguri.trackplayer.service.MusicService }: app is in background uid UidRecord{d4e96d5 u0a217 CEM  bg:+1m3s136ms idle procs:1 seq(36,36,36)}
       at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1538)
       at android.app.ContextImpl.startService(ContextImpl.java:1484)
       at android.content.ContextWrapper.startService(ContextWrapper.java:663)
       at android.content.ContextWrapper.startService(ContextWrapper.java:663)
       at com.guichaguri.trackplayer.module.MusicModule.waitForConnection(MusicModule.java:100)
       at com.guichaguri.trackplayer.module.MusicModule.getPosition(MusicModule.java:405)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
       at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:160)
       at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)
       at android.os.Handler.handleCallback(Handler.java:789)
       at android.os.Handler.dispatchMessage(Handler.java:98)
       at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29)
       at android.os.Looper.loop(Looper.java:164)
       at com.facebook.react.bridge.queue.MessageQueueThreadImpl$3.run(MessageQueueThreadImpl.java:192)
       at java.lang.Thread.run(Thread.java:764)

I am also seeing this in my Crashlytics and Bugsnag reporting.

Android 8 and 9.

I have not seen it happen myself.

This issue is happening with version 1.1.8, any solutions found yet?

java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.xxx/com.guichaguri.trackplayer.service.MusicService }: app is in background uid UidRecord{4d77fb1 u0a225 LAST bg:+1m39s187ms idle change:cached procs:1 seq(159,159,159)} at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1551) at android.app.ContextImpl.startService(ContextImpl.java:1492) at android.content.ContextWrapper.startService(ContextWrapper.java:650) at android.content.ContextWrapper.startService(ContextWrapper.java:650) at com.guichaguri.trackplayer.module.MusicModule.waitForConnection(MusicModule.java:106) at com.guichaguri.trackplayer.module.MusicModule.updateOptions(MusicModule.java:182) at java.lang.reflect.Method.invoke(Method.java) at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372) at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:158) at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java) at android.os.Handler.handleCallback(Handler.java:790) at android.os.Handler.dispatchMessage(Handler.java:99) at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29) at android.os.Looper.loop(Looper.java:192) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:232) at java.lang.Thread.run(Thread.java:764)

Is there any update on when a fix (or the helper function above) might be released? We’ve currently got forked versions of the library for other bugs, so don’t really want to depend on another one!

We’re using version 2.1.1 of react-native-track-player and are also still seeing this issue on Android devices.

@Saehanseul I am able to regenerate the same error using comment I had just put the above comment code in java file and I start getting the same error message in firebase crashlytics.

also, I am working on @biomancer 's solution he had provided here. It looks like service is not started before I was setting up an audio player. I am working on it and looks like we need to add checks for services running status before performing any action on track player. Which will stop crashing it. But still, I am working on it and let you know that like other functionality of the player is working fine or not.

I will update here soon. I may take a week or more because I will come up with the next one-week firebase crash stats after release build with this fix.

@biomancer Have you tested this with users in the wild yet? If @Guichaguri thinks this looks like a good fix, I am happy to do a limited release to a few thousand of my users to see if it brings their ANR rate down.

@bemnet4u @curiousdustin I think I understood why just using startForegroundService won’t work - using it requires us to call startForeground within 5 seconds, or app will crash with Context.startForegroundService() did not then call Service.startForeground() error.

Calling startForeground requires notification to be shown, and currently player is designed to call it only when playback begins, so if you do not start playing music within 5 seconds after service start, app will crash - that happens now if we just change startService to startForegroundService.

So using startForegroundService would require us to show some notification after service is started, and at that time we have no current track, etc, so it may be only some blank / generic notification, that will look ugly.

But I think we can call stopForeground(true) right after notification is created - actually I’ve tried that here https://github.com/perushevandkhmelev-agency/react-native-track-player/commit/b1d50dc20ccc1bf5ce662e7a1673f8c6c074ba6d - and it seems to work, but I have tested it only briefly. I’m also not sure that creating separate channel for this setup notification is ok there, but at a first glance it works, and probably is better than using playback notification channel as it may have lower importance or some other settings that fit better for this specific case. I’ve tried using code for creation blank notification from onStartForeground(), but it was failing with "invalid channel for service notification" on my android 9 device, so I’m not sure if that code is valid or if I was using it incorrectly somehow.

And last thing - actually calling stopForeground(true) may be optional there, as it’s called anyway in updateNotification() as audio session is not active on start, but calling it right after notification is created should remove it a bit faster.

upd @bemnet4u looking through your commits and I see that we’ve actually implemented a very similar fix 😃

upd2 I’ve also refactored a bit creating these blank notifications, inspired by your code https://github.com/perushevandkhmelev-agency/react-native-track-player/commit/13ed1186dd3d3dc6bbf6990704e59a2970277b74

@puckey I see that https://developer.android.com/reference/android/app/ActivityManager#getRunningServices(int) is deprecated, and maybe we can use simpler check for that binder is not null? There also seems to be a problem with that waitForConnection is designed to start service if it was not running and if we check for serviceIsRunning before all TrackPlayer functions that may be called in background, we change behaviour to never start service if it was not already running - would it be always fine while app is in background? Or are there some cases when service should be started?

Good point! Changed it to

    @ReactMethod
    public void isServiceConnected(final Promise promise) {
        promise.resolve(binder != null);
    }

I am adding this to all places where I am calling TrackPlayer functionality in the background. Haven’t added it to things like TrackPlayer.addEventListener('remote-pause', () => TrackPlayer.pause());, because the fact that TrackPlayer can send the event means it is running.

@vasylenko06 it requires using this checks for availability or handling rejections for control/setter methods, here is how we use it in our code https://github.com/react-native-kit/react-native-track-player/issues/473#issuecomment-510438390 and https://github.com/react-native-kit/react-native-track-player/issues/473#issuecomment-527936277. It is really similar to what Guichaguri has suggested, but his suggestion has better naming than my implementation and also as I understand he suggest rejecting for getter methods like getting current track, rate, etc, in my implementation they return some default blank values instead (not sure what approach is better, but I had no issues with it). I guess my implementation with suggested name fixes will do, but I had no time unfortunately lately to do it, check everything and update that https://github.com/react-native-kit/react-native-track-player/pull/725 pr.

@matheus-erthal yes, that happens if you call get / control methods of TrackPlayer in background after service have been stopped. The fastest workaround for now is to fork my repo and use my edge-2 branch, or at least to cherry-pick https://github.com/perushevandkhmelev-agency/react-native-track-player/commit/aa6e1eaa9c322a186c4fe00f6b44374061c4bf1f and to read await TrackPlayer.serviceIsRunning() before making any getter/action call to TrackPlayer, and reacting if service is not running - setting it up or skipping the call, depending on what is desired for application at that moment.

If you use not just this one commit but the whole branch, I must say that it also includes possible fixes for crashes mentioned here https://github.com/react-native-kit/react-native-track-player/issues/607, but I have not yet tested that in production (and cannot reproduce them locally to check), I’ll create a PR after verifying if that helped, and also few other fixes from PRs that are not merged yet, but working good enough to be useful.

Hello,

@Guichaguri I do get this error as well on the version 1.1.3.

It seems to be fixed in the latest beta: https://github.com/DoubleSymmetry/react-native-track-player/issues/1320#issuecomment-1013641860 At least I can no longer reproduce the exception by running our app via Android Studio on a locked device. With the older versions that would consistently throw the exception.

We are using "react-native-track-player": "^2.1.2" and still facing this issue on android devices. Any updates or plans on fixing this issue?

I’m still seeing this issue on lots of Android devices. Can anyone who upgraded to 2.0.x help confirm if this issue got solved in new version? @biomancer 's solution sounds great but I feel it’s hard to check serviceIsRunning everywhere, it’s also hard to catch up with official updates if I migrate to this solution.

Logs below:

java.lang.IllegalStateException: Not allowed to start service Intent { cmp=me.tangke.gamecores/com.guichaguri.trackplayer.service.MusicService }: app is in background uid UidRecord{e2421a1 u0a256 CAC  bg:+4h15m22s310ms idle procs:2 seq(0,0,0)}
    at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1725)
    at android.app.ContextImpl.startService(ContextImpl.java:1680)
    at android.content.ContextWrapper.startService(ContextWrapper.java:731)
    at android.content.ContextWrapper.startService(ContextWrapper.java:731)
    at com.guichaguri.trackplayer.module.MusicModule.waitForConnection(MusicModule.java:106)
    at com.guichaguri.trackplayer.module.MusicModule.getCurrentTrack(MusicModule.java:407)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
    at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:151)
    at com.facebook.react.bridge.queue.NativeRunnable.run(NativeRunnable.java)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
    at android.os.Looper.loop(Looper.java:236)
    at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:226)
    at java.lang.Thread.run(Thread.java:923)

Other infomation: react-native: 0.64.2 react-native-track-player: 1.2.7

Hey fellas, I think I’ve understood in details what’s happening here and it’s explained in my messages above, but I did not create any PRs as I’m a bit lost on how track player API should behave. @Guichaguri suggested that getters would return default values and actions would reject if service is not running, and I actually have implemented this approach in my branch here https://github.com/perushevandkhmelev-agency/react-native-track-player/commits/edge-2 among other fixes cherry-picked from forks or added by me (see my commits since Aug 29, 2019). And while I’m using this in my production build (not yet released, but it seems to be working just fine and will be published soon), I’ve understood that that actually getPosition did reject on errors, so I mimicked that for now there, and also I understood that changing all actions to reject if service running is not really intuitive, as now you will have to write js code always expecting that this call to track player may throw an exception. That actually broke some of my code, even though in most cases I did expect it and was using safely function that I described above https://github.com/react-native-kit/react-native-track-player/issues/473#issuecomment-510438390 , but not in all.

Currently my approach in js code is to:

  1. Check if service is running via method TrackPlayer.serviceIsRunning() before trying to use any getter methods, as knowing if service running at all is actually more valuable, then default getter value.
  2. Keep every action wrapped in that safely function as I have shown before to be sure that app will try to resetup player if service was not running. I also check TrackPlayer.serviceIsRunning() in some places which are more likely to be run with killed service to get rid of extra try -> reject -> throw -> catch -> setup ->try again flow, but that is more an optimization and probably not required.

That works, but doesn’t look like nice API to me and seems really inconsistent and incomplete, so I’m not sure what to do here. I will use it for now in my app as it’s not a problem to keep all these things in mind for me now given how much I’ve spent time to dig into it, but I don’t think that we should release it as final/public. I don’t know - maybe we should at least add TrackPlayer.serviceIsRunning() method as first band aid step, that alone will actually be enough to allow writing a code that won’t crash an app (by checking if service is running before EVERY call to TrackPlayer except setup ones), as now there is no way to accomplish it even with that extra boilerplate.

At the same time I’ve found one fork that tries some different approach and actually rewires all event handling, etc, https://github.com/wakingupllc/react-native-track-player/pull/5, but I didn’t understand if that was working and worth it, and the overall idea behind these changes. I’ve asked if guys could explain it a bit and create a pr, but there is no response for now.

@Guichaguri , any input on the suggestion by @LiWeiQiangAndroid ?

It seems @biomancer has done a decent amount of investigation into using startForegroundService, but in your last reply you kind of dismissed his approach.

Also can anyone clear up how this crash actually affects users? Is the main app actually crashing / quitting when this occurs? Or is it just the player activity running in the background that crashes / quits? If it is just the player, what is the result? Is it restarted when needed?

@ishigamii the error still happens but its much lower rate than before. So the fix is helping.

I think that’s good, I’ll take a better look in the weekend.

Mine is a bit more complicated, and doesn’t directly call the TrackPlayer methods, but maybe it will be useful.

import Analytics from '../../utils/Analytics';
import { store } from '../../containers/Root';
import TrackPlayer from 'react-native-track-player';
import AudioPlayer, { SIZZLE_ID } from '../AudioPlayer';
import {
  audioPlayerStateChange,
  audioPlayerTrackChange,
  audioPlayerPlay,
  audioPlayerPause,
  audioPlayerJumpForward,
  audioPlayerJumpBack,
  audioPlayerStop,
  audioPlayerPlayNext,
  audioPlayerRemoveQueuedTrack,
  audioPlayerRemoveCurrentQueuedTrack,
  audioPlayerSeek,
  setAudioPlayerIntentToPause
} from '../../actions/audioPlayerActions';
import Device from '../../utils/Device';
import { getAudioPlayerState } from '../../utils/selectors';
import getCurrentlyPlayingTrackFactory from '../../utils/selectors/getCurrentlyPlayingTrackFactory';

let didAddEventListeners = false;
let didPauseTemporarily = false;

let getCurrentlyPlayingTrack = getCurrentlyPlayingTrackFactory();

async function logEventWithCurrentTrackInfo(eventName) {
  let params = {};
  try {
    const currentTrack = getCurrentlyPlayingTrack(store.getState());
    if (currentTrack) {
      params.showname = currentTrack.album;
      params.episodename = currentTrack.title;
    }
  } catch (e) {
    //
  }
  Analytics.logEvent(eventName, params);
}

module.exports = async function() {
  if (didAddEventListeners) {
    return;
  } else {
    didAddEventListeners = true;
  }

  TrackPlayer.addEventListener('remote-play', data => {
    store.dispatch(audioPlayerPlay());
    logEventWithCurrentTrackInfo('remote_play');
  });

  TrackPlayer.addEventListener('remote-pause', data => {
    store.dispatch(audioPlayerPause());
    logEventWithCurrentTrackInfo('remote_pause');
  });

  TrackPlayer.addEventListener('remote-stop', data => {
    store.dispatch(audioPlayerStop());
    logEventWithCurrentTrackInfo('remote_stop');
  });

  TrackPlayer.addEventListener('remote-jump-forward', data => {
    if (data) {
      store.dispatch(audioPlayerJumpForward(data.interval));
      logEventWithCurrentTrackInfo('remote_jumpforward');
    }
  });

  TrackPlayer.addEventListener('remote-jump-backward', data => {
    if (data) {
      store.dispatch(audioPlayerJumpBack(data.interval));
      logEventWithCurrentTrackInfo('remote_jumpback');
    }
  });

  TrackPlayer.addEventListener('remote-seek', data => {
    if (data) {
      store.dispatch(audioPlayerSeek(data.position));
      logEventWithCurrentTrackInfo('remote_seek');
    }
  });

  TrackPlayer.addEventListener('remote-next', data => {
    store.dispatch(audioPlayerJumpForward(AudioPlayer.options.jumpInterval));
    logEventWithCurrentTrackInfo('remote_jumpforward');
  });

  TrackPlayer.addEventListener('remote-previous', data => {
    store.dispatch(audioPlayerJumpBack(AudioPlayer.options.jumpInterval));
    logEventWithCurrentTrackInfo('remote_jumpback');
  });

  TrackPlayer.addEventListener('playback-state', data => {
    if (data) {
      const currentPlayerState = getAudioPlayerState(store.getState());

      let playerState = AudioPlayer.getStateForState(data.state);
      store.dispatch(audioPlayerStateChange(playerState));
      AudioPlayer.monitorProgress();
      if (playerState === AudioPlayer.STATE_PLAYING) {
        AudioPlayer.startMonitoring();
      } else {
        AudioPlayer.stopMonitoring();
      }

      if (playerState === AudioPlayer.STATE_PAUSED) {
        if (currentPlayerState === AudioPlayer.STATE_PLAYING) {
          store.dispatch(setAudioPlayerIntentToPause());
        }
      } else {
        didPauseTemporarily = false;
      }
    }
  });

  TrackPlayer.addEventListener('playback-track-changed', data => {
    if (data) {
      let { track, position, nextTrack } = data;
      if (track && track !== SIZZLE_ID) {
        AudioPlayer.storeTrackProgress(track, position);
      }
      if (nextTrack) {
        store.dispatch(audioPlayerTrackChange(track, position, nextTrack));
      }
    }
  });

  TrackPlayer.addEventListener('playback-queue-ended', data => {
    if (data) {
      let { track, position } = data;
      if (track) {
        if (track === SIZZLE_ID) {
          store.dispatch(audioPlayerStop());
        } else {
          AudioPlayer.storeTrackComplete(track);
          store.dispatch(audioPlayerRemoveQueuedTrack(track));
          store.dispatch(audioPlayerPlayNext(true));
        }
      }
    } else {
      store.dispatch(audioPlayerRemoveCurrentQueuedTrack()).then(() => {
        store.dispatch(audioPlayerPlayNext(true));
      });
    }
  });

  TrackPlayer.addEventListener('playback-error', data => {
    store.dispatch(audioPlayerRemoveCurrentQueuedTrack()).then(() => {
      store.dispatch(audioPlayerPlayNext(true));
    });
  });

  if (Device.isAndroid) {
    TrackPlayer.addEventListener('remote-duck', data => {
      let {
        paused: shouldPause,
        ducking: shouldDuck,
        permanent: permanentLoss
      } = data;

      if (shouldPause || shouldDuck) {
        store.dispatch(audioPlayerPause());
        didPauseTemporarily = !permanentLoss;
      } else if (didPauseTemporarily) {
        didPauseTemporarily = false;
        store.dispatch(audioPlayerPlay());
      }
    });

    TrackPlayer.addEventListener('remote-play-id', data => {});

    TrackPlayer.addEventListener('remote-play-search', data => {});

    TrackPlayer.addEventListener('remote-skip', data => {});

    TrackPlayer.addEventListener('remote-set-rating', data => {});
  }
};