Play playlist with MediaPlayer

Learn play playlist with mediaplayer with practical examples, diagrams, and best practices. Covers android development techniques with visual explanations.

Playing a Playlist with Android's MediaPlayer

Hero image for Play playlist with 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.

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.

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.