Player.Listener event combinations data gaps
I'm exploring setting up a custom Listener that observes multiple events to track specific occurrences of things, for example tracking when playback starts for a media item either in response to a user trigger or when the media item transitions and the player is currently playing.
This is for analytics purposes, so in that example situation it would be incorrect to track an event if the playback state simply changes from buffering to ready, or if the media item transition occurs whilst playWhenReady is false, or if playWhenReady is set to true before any media is actually added/prepared.
I'm running into a situation where I'm not sure what the path forward is.
In the Player.Listener interface there are individualised event functions for different events, some I'm interested in (like onPlayWhenReadyChanged include a "reason" parameter.
There's also onEvents which is documented as something that should be used for combining events/states and having access to the Player which is also something that applies to my situation. However, as far as I can tell there's no way to find out what the change reason is for something like playWhenReady except via the specialised listener function.
As a more concrete example, in order to track my event correctly:
- The current media item must not be null
- The playback state must not be IDLE or ENDED (i.e. the media is prepared and buffering/playing)
- The reason for the
playWhenReadychange must be a user request
This is all achievable with individual callbacks where I store away the latest state from each (e.g. the current media item) as private variables in the listener implementation.
However there is a situation in which this isn't correct, which is if the listener happens to be added to the player AFTER a media item transition occurs, but before the change to playWhenReady as the internal state will not have the current media item from the player.
So I'm finding that I have a need for both a reference to the Player AND information about the reason for a change which is only provided in an individual callback. It feels like there would be lifecycle concerns with passing a reference to the Player as a constructor parameter to the listener implementation. The onEvents doc also mentions that interactions with the player aren't safe in the individual callbacks.
Thanks for the detailed problem description! As you already discovered, onEvents is the best choice to see all 'atomic' state updates in a single method. This method also gives you access to the Player to retrieve any current state values (in your example: the current media item, the playback state and playWhenReady). You are right that this doesn't allow to access additional event parameters like the reason for a playWhenReady change. If you need access to those, the best way is to overwrite the individual callback in addition to onEvents and just save the last parameter so you can use it from onEvents. As long as you check that the event happened, you are guaranteed that the value has been updated with the latest value and can be safely used. Please also see the last sentence in this guide that tries to highlight the same approach: https://developer.android.com/media/media3/exoplayer/listening-to-player-events#individual-callbacks
int playWhenReadyChangeReason;
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
playWhenReadyChangeReason = reason;
}
public void onEvents(Player player, Events events) {
if (events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
&& playWhenReadyChangeReason == Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST
&& player.getCurrentMediaItem() != null
&& (player.getPlaybackState() == Player.STATE_BUFFERING
|| player.getPlaybackState() == Player.STATE_READY)) {
// ...
}
}