registerEventHandler was a reasonable solution for a problem we don't have anymore, I think we can change that now.
In Android, the registerEventHandler accomplishes two things:
Here is how I think the new event system should work:
Since 1.0.0, we don't need to rely on a single event handler, we can expose the new event emitter, here is how you'd use it:
TrackPlayer.addListener('playback-state', (data) => {
// data.state
});
The biggest advantage is that you can add multiple listeners and handle these events anywhere in your app, here is a quick example of an event being handled directly from a react component:
class StateView extends Component {
onStateChange = data => this.setState({playerState: data.state});
componentDidMount() {
TrackPlayer.addListener('playback-state', this.onStateChange);
TrackPlayer.getState().then(state => this.setState({playerState: data.state}));
}
componentWillUnmount() {
TrackPlayer.removeListener('playback-state', this.onStateChange);
}
render() {
return (
<Text>
The player is currently {this.state.playerState === TrackPlayer.STATE_PLAYING ? 'playing' : 'not playing'}
</Text>
);
}
}
We still have to separate the "backend logic" from the UI, but we don't have to separate the "frontend logic" from the UI anymore.
This service should be 100% separated from the actual app, its only objective is to control the player even when the app is in background and the UI is not active.
Here is an example:
backend.js
// This function will run when the player service starts
module.exports = function () {
// The service can receive any type of event
TrackPlayer.addListener('remote-play', () => TrackPlayer.play());
}
index.js
AppRegistry.registerComponent(...);
TrackPlayer.registerBackendService(() => require('backend.js'));
As for 1.0.0, the current implementation for Android, iOS and UWP should be enough. No native code needs to be changed to accomplish this new feature.
To keep it backwards compatible, the new implementation for registerEventHandler should look like this, which is pretty similar to what we currently have:
function warpEventResponse(handler, event, payload) {
const additionalKeys = payload || {};
handler({ type: event, ...additionalKeys });
}
function registerEventHandler(handler) {
const events = [
'playback-state',
'playback-error',
'playback-queue-ended',
'playback-track-changed',
'remote-play',
'remote-pause',
'remote-stop',
'remote-next',
'remote-previous',
'remote-jump-forward',
'remote-jump-backward',
'remote-seek',
];
TrackPlayer.registerBackendService(() => {
return function() {
for (let i = 0; i < events.length; i++) {
TrackPlayer.addListener(events[i], warpEventResponse.bind(null, handler, events[i]));
}
};
});
}
I already have the implementation code finished working on Android, iOS and UWP, but I'd love to hear some feedback about this new event system first.
@Guichaguri no objections here. But a question: is there any risk of registering duplicate listeners? Say for instance the app was destroyed in the background while music was playing and you resumed the app a while later, would that register a duplicate?
No, there's no problem on registering multiple listeners. As long as you unregister the ones that are linked to the UI, and keep the ones registered on the headless task (registerBackend), everything should be fine.
@Guichaguri This looks nice! I'm all for exposing an interface that is more in line with the provided React API. I.e. EventEmitter.
About the "backend" part. The word "backend" confuses me a bit. Is it just another word for headless? If so, I would probably prefer to use the word headless.
Why do we would need a separate 'TrackPlayer.registerBackendService'. Wouldn't it be possible to just use the provided
AppRegistry.registerHeadlessTask('SomeTaskName', () => require('SomeTaskName'));
Well. That was my input for now. 馃檶
@anderslemke It would, but the headless task would only be initialized on Android. The idea of the registerBackendService is that it registers a headless task on Android, but runs it directly on other platforms.
Something like this:
function registerBackendService(serviceFactory) {
if (Platform.OS === 'android') {
// Registers the headless task
AppRegistry.registerHeadlessTask('TrackPlayer', serviceFactory);
} else {
// Initializes and runs the service in the next tick
setImmediate(serviceFactory());
}
}
Or maybe even better: initializing the service on iOS after the setupPlayer promise resolves
I'd love to hear feedback about it in action: #345
I am going to test this new way of registering events. Sometimes playback-track-changed do not fire or fire twice, this behavior forced me to change the logic of my app, I was adding all musics at once, now, I add them one by one, and use the method playback-queue-ended which is also firing twice, I had to do a little trick to ignore the second time it fires.
So I will be giving some feedback about this new way of registering events.
Most helpful comment
@anderslemke It would, but the headless task would only be initialized on Android. The idea of the
registerBackendServiceis that it registers a headless task on Android, but runs it directly on other platforms.Something like this:
Or maybe even better: initializing the service on iOS after the
setupPlayerpromise resolves