Building Media Players with XML UI

While modern Android development often gravitates towards Jetpack Compose for UI development, a significant number of applications still rely on XML for creating user interfaces. Building a media player using XML-based layouts provides a solid understanding of fundamental Android UI concepts and media playback capabilities.

Understanding XML-Based UI for Media Players

XML layouts in Android offer a declarative way to define the UI components and their properties. Using XML to create a media player UI involves structuring the layout file with necessary views such as VideoView, ImageView (for cover art), and SeekBar (for progress control), along with playback control buttons.

Why Use XML for Building a Media Player?

  • Wide Compatibility: XML is compatible with a wide range of Android versions, making it suitable for older devices.
  • Readability and Maintainability: XML provides a clear and structured way to define UI components.
  • Tooling Support: Android Studio offers excellent tooling for XML layouts, including a visual editor and property inspectors.

Implementing a Media Player with XML UI

To build a media player using XML layouts, follow these steps:

Step 1: Create the XML Layout

Define the layout for your media player in an XML file (e.g., activity_media_player.xml):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_alignParentTop="true"/>

    <ImageView
        android:id="@+id/coverArtImageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_below="@+id/videoView"
        android:layout_marginTop="16dp"
        android:layout_centerHorizontal="true"
        android:src="@drawable/default_cover_art"/>

    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/coverArtImageView"
        android:layout_marginTop="16dp"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/seekBar"
        android:orientation="horizontal"
        android:gravity="center_horizontal"
        android:padding="16dp">

        <Button
            android:id="@+id/playButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Play"/>

        <Button
            android:id="@+id/pauseButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Pause"/>

        <Button
            android:id="@+id/stopButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Stop"/>

    </LinearLayout>

</RelativeLayout>

In this XML layout:

  • VideoView displays the video content.
  • ImageView shows the cover art.
  • SeekBar controls the playback progress.
  • LinearLayout contains the playback control buttons (Play, Pause, Stop).

Step 2: Implement the Activity

Create the Activity class to handle the media player logic:

import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.VideoView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private VideoView videoView;
    private ImageView coverArtImageView;
    private SeekBar seekBar;
    private Button playButton, pauseButton, stopButton;
    private MediaPlayer mediaPlayer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_player);

        // Initialize views
        videoView = findViewById(R.id.videoView);
        coverArtImageView = findViewById(R.id.coverArtImageView);
        seekBar = findViewById(R.id.seekBar);
        playButton = findViewById(R.id.playButton);
        pauseButton = findViewById(R.id.pauseButton);
        stopButton = findViewById(R.id.stopButton);

        // Load video
        String videoPath = "android.resource://" + getPackageName() + "/" + R.raw.sample_video;
        Uri uri = Uri.parse(videoPath);
        videoView.setVideoURI(uri);

        // Initialize MediaPlayer for finer control
        mediaPlayer = new MediaPlayer();
        try {
            mediaPlayer.setDataSource(this, uri);
            mediaPlayer.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Set up control buttons
        playButton.setOnClickListener(v -> {
            if (!mediaPlayer.isPlaying()) {
                mediaPlayer.start();
            }
        });

        pauseButton.setOnClickListener(v -> {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
            }
        });

        stopButton.setOnClickListener(v -> {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
                mediaPlayer.prepareAsync(); // Prepare async to avoid blocking the UI thread
            }
        });

        // Set up SeekBar
        seekBar.setMax(mediaPlayer.getDuration());
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser) {
                    mediaPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });

        // Update SeekBar periodically
        new Thread(() -> {
            while (mediaPlayer != null) {
                try {
                    Thread.sleep(1000);
                    if (mediaPlayer.isPlaying()) {
                        runOnUiThread(() -> seekBar.setProgress(mediaPlayer.getCurrentPosition()));
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
}

Key aspects of this Activity include:

  • View Initialization: findViewById is used to connect XML-defined views to Java code.
  • Media Loading: The VideoView is set up to play a video from the raw resources. We also setup MediaPlayer to give us more granular control over the media playback
  • Button Controls: Click listeners are set up for the Play, Pause, and Stop buttons.
  • SeekBar Integration: The SeekBar is configured to control the playback progress.
  • Background Thread: A background thread updates the SeekBar position periodically.

Step 3: Add Video File

Add a sample video file to the res/raw directory. If the raw directory doesn’t exist, you’ll need to create it.

Step 4: Add Permissions

Add the necessary permissions to the AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />

Best Practices for XML-Based Media Player Development

  • Use ConstraintLayout: For complex layouts, use ConstraintLayout to manage the placement of UI elements efficiently.
  • Handle Orientation Changes: Implement logic to handle orientation changes gracefully, preserving the playback state.
  • Background Tasks: Perform media-related tasks in background threads to avoid blocking the main UI thread.
  • Resource Management: Properly manage and release resources to prevent memory leaks.

Conclusion

Building a media player with XML-based UI in Android provides a hands-on way to understand the core components of Android UI development. While Jetpack Compose offers a modern alternative, XML remains a reliable option for many applications, especially those targeting a wide range of Android devices. By following the steps outlined and adhering to best practices, you can create a functional and robust media player using XML layouts.