I am trying to play videos in the form of a playlist one after the other. I am using android Exoplayer to play my files, but there are no listeners in mediaplayer to listen to an end of the media file. Is there a way to listen to the end of media file using exoplayer.
11 Answers
I know this is old, but just to expand on the accepted answer:
exoPlayer.addListener(this);
.....
@Override
public void onPlayerStateChanged(boolean playWhenReady, int state) {
if (state == ExoPlayer.STATE_ENDED){
//player back ended
}
}

- 5,354
- 5
- 28
- 44
-
2`onPositionDiscontinuity()` is called in my case how to handle then? – Maveňツ May 16 '17 at 13:23
-
It works when I stream online videos but onPlayerStateChanged will not call when I play video locally – Javad Shirkhani Jul 02 '23 at 08:07
Exoplayer offer advance implementation which media player do not.
in Advance example of Exoplayer by Android, in FullPlayerActivity.java they have implemented onStateChanged, which offers STATE_ENDED
You can download example from the right hand section of this page under term RELATED SAMPLES

- 11,853
- 12
- 43
- 64

- 15,176
- 7
- 58
- 83
-
They have updated the sdk and samples, a little search will yield results. Btw I have updated the broken link. – Murtaza Khursheed Hussain Sep 17 '15 at 06:25
-
1Small detail It is onPlayerStateChanged. Possible they updated it. – Keith Loughnane Apr 06 '17 at 12:41
-
5When MediaSource is LoopingMediaSource, the STATE_ENDED event is not fired. – Borzh Nov 07 '17 at 01:09
Google has deprecate ExoPlayer.STATE_ENDED
and has replaced with Player.STATE_ENDED
.
Since this post has been a while, I will write a Kotlin
version of the listener down below.
this.simpleExoPlayer?.addListener(object : Player.DefaultEventListener() {
override fun onPlayerStateChanged(playWhenReady: Boolean,playbackState: Int) {
when (playbackState) {
Player.STATE_IDLE -> {}
Player.STATE_BUFFERING -> {}
Player.STATE_READY -> {}
Player.STATE_ENDED -> {}
}
}
})
UPDATE: 05-2020
Since DefaultEventListener
and onPlayerStateChanged()
are deprecated.
player.addListener(object : Player.EventListener {
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED) {
// your code
}
}
})

- 2,297
- 24
- 24
-
DefaultEventListener() is deprecated use instead this.player?.addListener(object :Player.EventListener{ override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { when (playbackState) { Player.STATE_IDLE -> { progressBar.visibility=View.VISIBLE} Player.STATE_BUFFERING -> { progressBar.visibility=View.VISIBLE } Player.STATE_READY -> { progressBar.visibility=View.GONE} Player.STATE_ENDED -> { progressBar.visibility=View.GONE} } } }) – Black mamba Jul 08 '19 at 12:09
-
2seems like `EventListener` is also deprecated now, it seems to just need `Player.Listener` now – a_local_nobody Aug 09 '21 at 09:49
You can do this:
playerExo.addListener(new ExoPlayer.Listener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch(playbackState) {
case ExoPlayer.STATE_BUFFERING:
break;
case ExoPlayer.STATE_ENDED:
//do what you want
break;
case ExoPlayer.STATE_IDLE:
break;
case ExoPlayer.STATE_PREPARING:
break;
case ExoPlayer.STATE_READY:
break;
default:
break;
}
}
@Override
public void onPlayWhenReadyCommitted() {
}
@Override
public void onPlayerError(ExoPlaybackException error) {
}
});
playerExo.seekTo(0);
playerExo.setPlayWhenReady(true);
But @Murtaza Khursheed Hussain answer is right! Have a nice code day! Let me know if you need something else!

- 1,046
- 13
- 21
-
1
-
-
You can use `Player.DefaultEventListener()` where all interface methods are stubbed. So you can remove unneeded methods like `onPlayWhenReadyCommitted` and `onPlayWhenReadyCommitted` – Taras Lozovyi Aug 19 '19 at 08:38
You have to implement the interface Player.EventLister and add to your exoPlayer.
Just write your code on the method onPlayerStateChanged. See the code.
exoPlayer.addListener( new Player.EventListener() {
@Override
public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
@Override
public void onLoadingChanged(boolean isLoading) {
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch(playbackState) {
case Player.STATE_BUFFERING:
break;
case Player.STATE_ENDED:
//Here you do what you want
break;
case Player.STATE_IDLE:
break;
case Player.STATE_READY:
break;
default:
break;
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
@Override
public void onPlayerError(ExoPlaybackException error) {
}
@Override
public void onPositionDiscontinuity(int reason) {
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
@Override
public void onSeekProcessed() {
}
});

- 500
- 7
- 9
Google has deprecate ExoPlayer.STATE_ENDED. Use Player.STATE_ENDED instead. See the code below.
player.addListener(new Player.EventListener() {
@Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
@Override
public void onLoadingChanged(boolean isLoading) {
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playWhenReady && playbackState == Player.STATE_READY) {
// Active playback.
} else if (playbackState == Player.STATE_ENDED) {
//The player finished playing all media
//Add your code here
} else if (playWhenReady) {
// Not playing because playback ended, the player is buffering, stopped or
// failed. Check playbackState and player.getPlaybackError for details.
} else {
// Paused by app.
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
@Override
public void onPlayerError(ExoPlaybackException error) {
}
@Override
public void onPositionDiscontinuity(int reason) {
//THIS METHOD GETS CALLED FOR EVERY NEW SOURCE THAT IS PLAYED
// int latestWindowIndex = player.getCurrentWindowIndex();
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
@Override
public void onSeekProcessed() {
}
});
You can check ExoPlayer Developer page for more information.

- 34,372
- 9
- 61
- 83

- 406
- 5
- 8
For non-concatenated MediaSource (representing whatever individual piece of media you want to play), you'll receive STATE_ENDED when the piece of media has finished playing.
For a ConcatenatingMediaSource it occurs when the entire concatenation has finished playing (i.e. you've played up to the end of the final item in the concatenation). So, STATE_ENDED occurs when the entire MediaSource has finished playing.
For a ConcatenatingMediaSource, the best callback to detect end of current media and start of next media playback is "onPositionDiscontinuity" You should use onPositionDiscontinuity() to figure out when transitions occur. Please note, onPositionDiscontinuity is invoked for some other cases too, but you can call getCurrentWindowIndex() and compare it to what window you think you're in to determine whether a transition has occurred. You can do something like below:
public void onPositionDiscontinuity() {
int newIndex = player.getCurrentWindowIndex();
if (newIndex != currentIndex) {
// The index has changed ==> last media has ended and next media playback is started.
currentIndex = newIndex;
}
}
Please note: onPositionDiscontinuity() is not gets called for the 1st item in the playlist unless we explicitly call player.seekTo(position,0). So to handle anything you are doing for all media playback in the playlist you have to handle separately for the 1st item in the playlist.

- 775
- 8
- 19
-
`onPositionDiscontinuity()` is called when the track is changed, but what if I want to do something right before it changes? – Sam Chen Mar 31 '21 at 13:24
-
HI @SamChen, onPositionDiscontinuity is dispatched apparently after the first frame of the "next video" is already rendered. But I don’t think there is any such event available that could fire just before the first frame of the "next video" got rendered. But you could add a **VideoFrameMetadataListener** to the video renderer that calls a method on the rendering thread every time a frame is about to be released [link](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/video/VideoFrameMetadataListener.html) – Akki Apr 01 '21 at 06:35
-
@SamChen Found one discussion on this which may help [link] (https://github.com/google/ExoPlayer/issues/6576) – Akki Apr 01 '21 at 08:02
-
Thank you, but what I mean is not the "next video", is the end of current video. I also created the post for this: https://stackoverflow.com/questions/66889341/android-exoplayer-playlist-pause-current-track-when-it-finishes. – Sam Chen Apr 01 '21 at 15:11
-
@SamChen, I have posted a few options which you can try on your post. I hope that would help. Good luck!! – Akki Apr 01 '21 at 20:08
ExoPlayer.STATE_ENDED is depreciated. Use Player.STATE_ENDED instead. Example:
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if(playbackState == Player.STATE_ENDED){
// your brain put here
}
}

- 1,685
- 2
- 16
- 17
I was using playlist with exoplayer and I wanted to stop the media after it was complete.
For me the Player.STATE_ENDED was being called when the playlist ended for me inside
So to fulfill my use case I first set this in exoplayer
simpleExoPlayer.setRepeatMode(Player.REPEAT_MODE_ONE);
So instead of using EventListener, I had used the AnalyticsListener which gives the onPositionDiscontinuity to override with eventTime parameter and just add a simple code to know whether the media has restarted.
@Override
public void onPositionDiscontinuity(EventTime eventTime, int reason) {
if (eventTime.currentPlaybackPositionMs / 1000 < 1) {
exoPlayer.setPlayWhenReady(false);
tvReplay.setVisibility(View.VISIBLE);
}
}
Hope this helps your usecase

- 466
- 8
- 21
-
`onPositionDiscontinuity()` is called after the track has changed or right before it changes? Because your formula `currentPlaybackPositionMs / 1000 < 1` looks like the beginning of the track, which should be the next track. – Sam Chen Mar 31 '21 at 13:29
-
Since I have set the REPEAT_MODE_ONE, the track would loop back to its initial state. So it the same track at the beginning – saurabhlahoti Apr 01 '21 at 12:10
-
I see, but is there a way to make it pause at the end of the current track? – Sam Chen Apr 01 '21 at 15:13
-
In that case, you can store the timestamp of the media that is currently being played in this function "onPositionDiscontinuity". And reset and run a counter till the end of the total media length. Keep the repeat mode as REPEAT_MODE_NONE. So everytime a track changes or is seeked onPositionDiscontinuity method will be called and your counter will be reset and run till the end of the media. And when it ends you can pause your media. – saurabhlahoti Apr 03 '21 at 06:36
Since DefaultEventListener
and onPlayerStateChanged()
are deprecated.
Use Player.EventListener
and onPlaybackStateChanged()
Kotlin code:
player!!.addListener(object : Player.EventListener {
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED) {
// your code here
}
}
})

- 155
- 1
- 10
Use this
exoPlayer.addListener(new Player.EventListener() {
@Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
}
@Override
public void onLoadingChanged(boolean isLoading) {
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == Player.STATE_ENDED) {
}
}
@Override
public void onRepeatModeChanged(int repeatMode) {
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
}
@Override
public void onPlayerError(ExoPlaybackException error) {
}
@Override
public void onPositionDiscontinuity(int reason) {
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
}
@Override
public void onSeekProcessed() {
}
});

- 111
- 2
- 1