React-native-track-player: [Android] Multiple listeners after app re-launch.

Created on 6 Feb 2019  路  15Comments  路  Source: react-native-kit/react-native-track-player

I am using stopWithApp: true

When I launch my app the first time, TrackPlayer.registerPlaybackService() gets called with the function to add listeners.

If I close my app while audio is playing, it does stop as expected. However, it appears the listeners do NOT get removed.

When I re-launch my app, TrackPlayer.registerPlaybackService() is NOT called again, but it seems the function that adds listeners DOES. From this point on each event is handled twice. If I continue to repeat the process, each event gets handled 3, 4, 5 etc times.

Since I am using stopWithApp: true it is unclear if I need to manually call destroy(). But, I have tried with and without with similar results. Interestingly, if I DO call destroy, my function to add listeners is called right after, when the app is closing.

I have also tried to safe guard the call the TrackPlayer.setupPlayer(). I set my own flag to see if it has already been setup, and only call it if it has not. This is also curious, because I would expect my app to not remember this when it is re-launched, but it DOES. So it seems like somehow some part of the app is not really getting destroyed.

What else can I do to make sure events are only handled once per event?

Android Bug

All 15 comments

Possibly related to #423 , although I haven't noticed this happening on iOS. Will test tomorrow.

I have re-created this in the example, with only logging added to detect it.

index.js

//Added code
console.log('TrackPlayer:', 'App Index'); 
//---
AppRegistry.registerComponent('example', () => App);
TrackPlayer.registerPlaybackService(() => require('./service'));

service.js

module.exports = async function() {

  //Added code  
  console.log('TrackPlayer:', 'Playback Service');

  TrackPlayer.addEventListener('playback-state', (data) => {
    console.log('TrackPlayer:', 'playback-state', data);
  })
  //---
 ...
}

Filtered Output:
react-native run-android --variant=release
App builds, installs, and runs. Lands on initial screen

2019-02-06 08:56:25.332 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'App Index'

Select Playlist Example

2019-02-06 08:58:14.315 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'Playback Service'
2019-02-06 08:58:14.380 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 1 }

Press Play
Audio plays after a couple seconds

2019-02-06 08:58:52.287 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 0 }
2019-02-06 08:58:52.294 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 1 }
2019-02-06 08:58:52.312 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 0 }
2019-02-06 08:58:52.372 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 6 }
2019-02-06 08:58:54.264 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 3 }

While audio plays, press Android multitasking menu button.
Close app by swiping away.
Music stops.
No further logs yet.

Relaunch app from home screen.
Still no further logs, meaning neither App Index or Playback Service were called again yet.

Select Playlist Example

2019-02-06 09:02:05.372 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'Playback Service'
2019-02-06 09:02:05.416 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 1 }
2019-02-06 09:02:05.422 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 1 }

Note Playback Service logged again, even though App index never was. Results in doubled event handling
Press Play

2019-02-06 09:02:38.967 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 0 }
2019-02-06 09:02:38.984 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 0 }
2019-02-06 09:02:38.985 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 1 }
2019-02-06 09:02:38.993 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 1 }
2019-02-06 09:02:38.994 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 0 }
2019-02-06 09:02:39.002 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 0 }
2019-02-06 09:02:39.036 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 6 }
2019-02-06 09:02:39.040 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 6 }
2019-02-06 09:02:40.290 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 3 }
2019-02-06 09:02:40.306 28579-28620/? I/ReactNativeJS: 'TrackPlayer:', 'playback-state', { state: 3 }

Note all events are doubled.

As a further test I added this, which DID resolved the issue.

service.js

import TrackPlayer from 'react-native-track-player';

let didAddListeners = false;

module.exports = async function() {

  if (didAddListeners) {
    console.log('TrackPlayer:', 'Listeners have already been added. Do not add again.');
    return;
  }
  didAddListeners = true;

  console.log('TrackPlayer:', 'Playback Service');

  TrackPlayer.addEventListener('playback-state', (data) => {
    console.log('TrackPlayer:', 'playback-state', data);
  })

   ...

};

My gut tells me this is a hack and probably has other issues... But, I guess I will do this for now.

Semi-related, can someone clarify if we need to use destroy() at all if we are using stopWithApp: true, and also do NOT have a stop button on our notifications?

No, the module will self-destroy when you have stopWithApp set to true.


It seems the Javascript Context remains in memory even after the app is closed and no services are running, that's why it registers the event twice.

To fix it, I think we'll have to implement a playback-destroy event that runs when the service stops. Here are some options I can think of:

  • Make it an internal event, which automatically removes all listeners with eventEmitter.removeAllListeners('event-name'), but that might cause issues too (e.g. the service is destroyed manually and the app still has a few listeners bound to React components, if the service starts again, those listeners will be gone).
  • Allow access to the event, but having to be careful because you can't run any other TrackPlayer function on that event, or the service will start again (and it will probably crash too). Breaking change.
  • Create a new playback service design, which includes a destroy callback. Breaking change.
  • Store all event listener references being created by the service and remove them using the playback-destroy event. If an event listener is added asynchronously, it wouldn't be removed.
  • Create internal logic similar to yours (based on a boolean value) to prevent the playback service function from being called multiple times. That hack doesn't look like it would cause any other issues, but I might be wrong. The problem is that other type of logic added to the service wouldn't be triggered again.

What do you think?

I am also finding this to be an issue in 1.1.2. Going to try the workaround presented above.

Additionally, I'm finding that on Android only, the playback-queue-ended event is being triggered when the player events are registered.

So many options! 馃槵

I鈥檓 honestly not sure what would be best overall. But this option seems logical to me.

Store all event listener references being created by the service and remove them using the playback-destroy event. If an event listener is added asynchronously, it wouldn't be removed.

If I understand correctly, any listeners added in the service function, would be automatically removed, which feels natural. And then any listeners added within components would need to be manually cleaned up, as is already documented.

This made me think of another option, that perhaps would only need documented instead of any real change.

I can鈥檛 test this until tomorrow, but would it work to have the user (developer) keep track of references to all the listeners added in the service function, and remove them all manually (if they exist) right before adding them?

I didn鈥檛 think to do this until I looked at the docs for adding listeners in components instead of in the service.

@curiousdustin That sounds like it would work but it doesn't seem possible to remove listeners currently. Have you had any luck?

I have not tried this within the service function, but my idea was to do it similar to how the component example in the docs does it.

this.onTrackChange = TrackPlayer.addEventListener('playback-track-changed', async (data) => {    });
this.onTrackChange.remove();

If anyone is still having an issue with this, I just dealt with a similar (if not the same issue). TrackPlayer.setupPlayer() was not being called in the root of app and multiple instances were being created, there was nothing wrong with the listeners. Where are you calling TrackPlayer.setupPlayer() ?

We have just resolved this issue, we write as below.

https://github.com/react-native-kit/react-native-track-player/issues/683

Hope you will be fine.

Issue still exists in version 1.1.8.
Even with TrackPlayer.destroy() or use stopWithApp: true, listeners won't completely removed.
Using packages like react-native-exit-app does this for you

@Karstagg TrackPlayer.setupPlayer() should called once in root of the app or multiple instances should called in component?

Hi guys,

I got the same problem for two days before to see this issue.
The listeners in question came from the services file so I cannot use componentWillUnmount (for example).
The little 'hack' I did for others with the same problem:

`let listeners: EmitterSubscription[] = [];

module.exports = async function () {

listeners.map(listener => { listener.remove() });

listeners = [
    TrackPlayer.addEventListener(Event.RemotePlay, async () => {
        // Do what you want
    }),
    ...You can add all your listeners here
];

};`

Thanks for the lib btw !

Was this page helpful?
0 / 5 - 0 ratings

Related issues

moduval picture moduval  路  3Comments

Sathyanarayan09 picture Sathyanarayan09  路  3Comments

KalebPortillo picture KalebPortillo  路  4Comments

b3rkaydem1r picture b3rkaydem1r  路  3Comments

EhteshamAnwar picture EhteshamAnwar  路  3Comments