Play playlist with MediaPlayer
Categories:
Playing a Playlist with Android's MediaPlayer

Learn how to manage and play a sequence of audio files using Android's MediaPlayer, including handling state transitions and resource management for a smooth user experience.
Playing a single audio file with Android's MediaPlayer
is straightforward, but managing a playlist of multiple audio files requires a more structured approach. This article will guide you through creating a robust system to play a sequence of audio files, handling transitions, and ensuring proper resource management to prevent memory leaks and crashes.
Understanding MediaPlayer Lifecycle and State
The MediaPlayer
object has a distinct lifecycle with various states. Understanding these states is crucial for correctly managing playback, especially when dealing with multiple tracks. Improper state transitions can lead to IllegalStateException
or other unexpected behavior. When playing a playlist, you'll typically move through states like Idle
, Initialized
, Prepared
, Started
, Paused
, and Stopped
for each track.
stateDiagram-v2 [*] --> Idle Idle --> Initialized: setDataSource() Initialized --> Prepared: prepare() / prepareAsync() Prepared --> Started: start() Started --> Paused: pause() Paused --> Started: start() Started --> PlaybackCompleted: onCompletion() PlaybackCompleted --> Idle: reset() Prepared --> Stopped: stop() Stopped --> Idle: reset() Idle --> End: release() PlaybackCompleted --> End: release() Stopped --> End: release() End --> [*] state PlaybackCompleted <<choice>>
MediaPlayer State Diagram
Implementing a Playlist Manager
To play a playlist, we'll create a simple manager class that holds a list of audio URIs and manages the MediaPlayer
instance. This class will be responsible for initializing the player for each track, handling completion, and moving to the next track. We'll use prepareAsync()
to avoid blocking the UI thread and setOnCompletionListener()
to detect when a track finishes.
import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;
import java.io.IOException;
import java.util.List;
public class PlaylistPlayer {
private MediaPlayer mediaPlayer;
private List<Uri> playlist;
private int currentTrackIndex = 0;
private Context context;
private OnPlaylistCompletionListener completionListener;
public interface OnPlaylistCompletionListener {
void onPlaylistCompleted();
void onTrackChanged(int newTrackIndex);
}
public PlaylistPlayer(Context context, List<Uri> playlist) {
this.context = context;
this.playlist = playlist;
initMediaPlayer();
}
private void initMediaPlayer() {
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnCompletionListener(mp -> {
currentTrackIndex++;
if (currentTrackIndex < playlist.size()) {
playCurrentTrack();
if (completionListener != null) {
completionListener.onTrackChanged(currentTrackIndex);
}
} else {
// Playlist finished
if (completionListener != null) {
completionListener.onPlaylistCompleted();
}
releasePlayer();
}
});
mediaPlayer.setOnPreparedListener(MediaPlayer::start);
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
// Handle errors, e.g., log them or skip track
System.err.println("MediaPlayer error: " + what + ", " + extra);
currentTrackIndex++; // Try next track
if (currentTrackIndex < playlist.size()) {
playCurrentTrack();
} else if (completionListener != null) {
completionListener.onPlaylistCompleted();
}
return true; // Indicate that the error was handled
});
}
public void play() {
if (playlist == null || playlist.isEmpty()) {
System.out.println("Playlist is empty.");
return;
}
currentTrackIndex = 0;
playCurrentTrack();
}
private void playCurrentTrack() {
if (mediaPlayer == null) {
initMediaPlayer(); // Re-initialize if released
}
try {
mediaPlayer.reset(); // Reset to Idle state
mediaPlayer.setDataSource(context, playlist.get(currentTrackIndex));
mediaPlayer.prepareAsync(); // Prepare asynchronously
} catch (IOException e) {
System.err.println("Error setting data source: " + e.getMessage());
// Try next track or handle error
currentTrackIndex++;
if (currentTrackIndex < playlist.size()) {
playCurrentTrack();
} else if (completionListener != null) {
completionListener.onPlaylistCompleted();
}
}
}
public void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
public void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.reset();
}
}
public void releasePlayer() {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
public void setOnPlaylistCompletionListener(OnPlaylistCompletionListener listener) {
this.completionListener = listener;
}
public int getCurrentTrackIndex() {
return currentTrackIndex;
}
public boolean isPlaying() {
return mediaPlayer != null && mediaPlayer.isPlaying();
}
public void skipToNext() {
if (currentTrackIndex < playlist.size() - 1) {
currentTrackIndex++;
playCurrentTrack();
if (completionListener != null) {
completionListener.onTrackChanged(currentTrackIndex);
}
} else {
// Already at the end or playlist empty
if (completionListener != null) {
completionListener.onPlaylistCompleted();
}
releasePlayer();
}
}
public void skipToPrevious() {
if (currentTrackIndex > 0) {
currentTrackIndex--;
playCurrentTrack();
if (completionListener != null) {
completionListener.onTrackChanged(currentTrackIndex);
}
} else {
// Already at the beginning
System.out.println("Already at the first track.");
}
}
public void seekTo(int positionMs) {
if (mediaPlayer != null && (mediaPlayer.isPlaying() || mediaPlayer.getCurrentPosition() >= 0)) {
mediaPlayer.seekTo(positionMs);
}
}
public int getDuration() {
return mediaPlayer != null ? mediaPlayer.getDuration() : 0;
}
public int getCurrentPosition() {
return mediaPlayer != null ? mediaPlayer.getCurrentPosition() : 0;
}
}
PlaylistPlayer.java: A class to manage MediaPlayer for a playlist.
release()
on the MediaPlayer
when it's no longer needed (e.g., in onDestroy()
of your Activity/Fragment) to free up system resources. Failing to do so can lead to memory leaks and performance issues.Using the PlaylistPlayer in an Activity
Now, let's see how to integrate the PlaylistPlayer
into an Android Activity. You'll need to provide a list of Uri
objects for your audio files. These can be local files, assets, or remote URLs. For simplicity, we'll use raw resources in this example.
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.net.Uri;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private PlaylistPlayer playlistPlayer;
private TextView currentTrackTextView;
private Button playButton, pauseButton, stopButton, nextButton, prevButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
currentTrackTextView = findViewById(R.id.currentTrackTextView);
playButton = findViewById(R.id.playButton);
pauseButton = findViewById(R.id.pauseButton);
stopButton = findViewById(R.id.stopButton);
nextButton = findViewById(R.id.nextButton);
prevButton = findViewById(R.id.prevButton);
List<Uri> playlist = new ArrayList<>();
playlist.add(Uri.parse("android.resource://" + getPackageName() + "/raw/track1"));
playlist.add(Uri.parse("android.resource://" + getPackageName() + "/raw/track2"));
playlist.add(Uri.parse("android.resource://" + getPackageName() + "/raw/track3"));
playlistPlayer = new PlaylistPlayer(this, playlist);
playlistPlayer.setOnPlaylistCompletionListener(new PlaylistPlayer.OnPlaylistCompletionListener() {
@Override
public void onPlaylistCompleted() {
currentTrackTextView.setText("Playlist Completed!");
playButton.setText("Play");
}
@Override
public void onTrackChanged(int newTrackIndex) {
currentTrackTextView.setText("Now Playing: Track " + (newTrackIndex + 1));
playButton.setText("Pause");
}
});
playButton.setOnClickListener(v -> {
if (playlistPlayer.isPlaying()) {
playlistPlayer.pause();
playButton.setText("Play");
} else {
playlistPlayer.play();
currentTrackTextView.setText("Now Playing: Track " + (playlistPlayer.getCurrentTrackIndex() + 1));
playButton.setText("Pause");
}
});
pauseButton.setOnClickListener(v -> {
playlistPlayer.pause();
playButton.setText("Play");
});
stopButton.setOnClickListener(v -> {
playlistPlayer.stop();
playButton.setText("Play");
currentTrackTextView.setText("Stopped");
});
nextButton.setOnClickListener(v -> playlistPlayer.skipToNext());
prevButton.setOnClickListener(v -> playlistPlayer.skipToPrevious());
// Initial UI state
currentTrackTextView.setText("Ready to play");
}
@Override
protected void onDestroy() {
super.onDestroy();
if (playlistPlayer != null) {
playlistPlayer.releasePlayer();
}
}
}
MainActivity.java: Integrating the PlaylistPlayer.
AndroidManifest.xml
if you are playing audio from external storage or the internet. For example, <uses-permission android:name="android.permission.INTERNET" />
for network streams.XML Layout for the Player Controls
Here's a basic XML layout (activity_main.xml
) to provide controls for playing, pausing, stopping, and skipping tracks in your playlist.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/currentTrackTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ready to play"
android:textSize="24sp"
android:layout_marginBottom="32dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/prevButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Prev"/>
<Button
android:id="@+id/playButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"/>
<Button
android:id="@+id/pauseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pause"
android:layout_marginEnd="16dp"/>
<Button
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop"
android:layout_marginEnd="16dp"/>
<Button
android:id="@+id/nextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next"/>
</LinearLayout>
</LinearLayout>
activity_main.xml: Layout for player controls.