Play multiple videos at the same time using TextureView in Android programmatically

211 views Asked by At

I would like to crop and play multiple videos in one activity using texture view in android programmatically. The code below throws an exception " Caused by: java.lang.IllegalArgumentException: surfaceTexture must not be null".

Mainactivity.java class

import androidx.appcompat.app.AppCompatActivity;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.Surface;
import android.view.TextureView;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private TextureView videoView1;
    private TextureView videoView2;
    private MediaPlayer mediaPlayer1;
    private MediaPlayer mediaPlayer2;

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

        videoView1 = findViewById(R.id.video_view_1);
        videoView2 = findViewById(R.id.video_view_2);

        String path = "android.resource://" + getPackageName() + "/" + R.raw.patila;


        mediaPlayer1 = new MediaPlayer();
        try {
            mediaPlayer1.setDataSource(path);
            mediaPlayer1.setSurface(new Surface(videoView1.getSurfaceTexture()));
            mediaPlayer1.setOnPreparedListener(mp -> mp.start());
            mediaPlayer1.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }


        mediaPlayer2 = new MediaPlayer();
        try {
            mediaPlayer2.setDataSource(path);
            mediaPlayer2.setSurface(new Surface(videoView2.getSurfaceTexture()));
            mediaPlayer2.setOnPreparedListener(mp -> mp.start());
            mediaPlayer2.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mediaPlayer1.release();
        mediaPlayer2.release();
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <TextureView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/video_view_1"/>
    <TextureView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/video_view_2"/>

</LinearLayout>

I have done multiple research about the same and how can achieve multiple video cropping and playing on one activity. I'm really confused on the best methodology to achieve the same because I'm new to TextureView. Kindly assist me on how to go about it.

1

There are 1 answers

0
Zach Dean On

When working with a TextureView (a similar thing applies to SurfaceView) you have to listen to callbacks about the state of the SurfaceTexture.

In the case of the TextureView you need to implement TextureView.SurfaceTextureListener (see: docs). It is only within the callback onSurfaceTextureAvailable that the SurfaceTexture is non-null and so in onCreate the SurfaceTexture is null and thus you are getting the error you're getting.

When testing I didn't have much success with accessing a file in the raw resources directory and so I opted for including a test video file in the assets instead. The setDataSource function can also be used by providing an AssetFileDescriptor.

This code should work. You may be able to refactor the release calls on the MediaPlayer instances into the onSurfaceTextureDestroyed callback. You may also be able to refactor the anonymous SurfaceTextureListener into a class to neaten up the code but I kept it like this for ease.

For some good resources look at; Android Graphics Architecture Docs and the Grafika Sample Applications

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.res.AssetFileDescriptor;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private TextureView videoView1;
    private TextureView videoView2;
    private MediaPlayer mediaPlayer1 = new MediaPlayer();
    private MediaPlayer mediaPlayer2 = new MediaPlayer();

    private AssetFileDescriptor path;

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

        videoView1 = findViewById(R.id.video_view_1);
        videoView2 = findViewById(R.id.video_view_2);

        // Obtain an AssetFileDescriptor for the media file
        try {
            path = getAssets().openFd("test.mp4");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // Set the SurfaceTextureListener for videoView1
        videoView1.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
                setUpMediaPlayer(mediaPlayer1, videoView1);
            }

            @Override
            public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
                Log.d("TextureView", "onSurfaceTextureSizeChanged");
            }

            @Override
            public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
                Log.d("TextureView", "onSurfaceTextureUpdated");
            }
        });

        // Set the SurfaceTextureListener for videoView2
        videoView2.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
                setUpMediaPlayer(mediaPlayer2, videoView2);
            }

            @Override
            public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
                Log.d("TextureView", "onSurfaceTextureSizeChanged");
            }

            @Override
            public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
                Log.d("TextureView", "onSurfaceTextureUpdated");
            }
        });

    }

    // Helper function for common set up of both MediaPlayers
    private void setUpMediaPlayer(MediaPlayer mediaPlayer, TextureView videoView) {
        try {
            mediaPlayer.setDataSource(path);
            mediaPlayer.setSurface(new Surface(videoView.getSurfaceTexture()));
            mediaPlayer.setOnPreparedListener(mp -> mp.start());
            mediaPlayer.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mediaPlayer1.release();
        mediaPlayer2.release();
    }
}