Display Progress Dialog When RTSP Stream is Buffering in VLC Player on Android

Display Progress Dialog When RTSP Stream is Buffering in VLC Player on Android

LibVLC library can be used to display RTSP stream from IP camera on Android application. Until the video stream gets to start showing in VLC player, a black surface appears for a user. User might think that the video stream is not loading.

This tutorial provides example how to display the progress dialog when RTSP stream is buffering in VLC Player on Android application.

First, open the module's build.gradle file and add LibVLC library in dependencies section.

app/build.gradle

dependencies {
    // Other dependencies
    // ...
    implementation 'org.videolan.android:libvlc-all:3.4.4'
}

Application requires Internet access. Request the INTERNET permission in the manifest file.

app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">
 
    <uses-permission android:name="android.permission.INTERNET" />
 
    <application>
        ...
    </application>
 
</manifest>

In the layout XML file, add a VLCVideoLayout. It will be used to display RTSP stream from IP camera.

app/src/main/res/layout/activity_main.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">

    <org.videolan.libvlc.util.VLCVideoLayout
        android:id="@+id/videoLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Create a new layout XML file named progress.xml. It will be used to display the progress bar alongside with text "Video stream is buffering…".

app/src/main/res/layout/progress.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <ProgressBar
        android:layout_width="40dp"
        android:layout_height="40dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Video stream is buffering..."
        android:layout_gravity="center_vertical"
        android:layout_marginStart="10dp" />

</LinearLayout>

We created a new ProgressDialog class which responsible to show and hide a progress dialog. View resource R.layout.progress is used as content of the dialog. Progress dialog will be shown if video stream buffering takes more than 1 second (1000 milliseconds). This value can be adjusted.

app/src/main/java/com/example/app/ProgressDialog.java

package com.example.app;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;

public class ProgressDialog
{
    private static final int delayMs = 1000;
    private boolean isVisible = false;
    private long startTime = 0;
    private final Dialog dialog;

    public ProgressDialog(Context context)
    {
        dialog = new AlertDialog.Builder(context)
            .setView(R.layout.progress)
            .setCancelable(false)
            .create();
    }

    public void show()
    {
        if (isVisible) {
            return;
        }
        if (startTime == 0L) {
            startTime = System.currentTimeMillis();
        }
        if (System.currentTimeMillis() - startTime > delayMs) {
            isVisible = true;
            dialog.show();
        }
    }

    public void hide()
    {
        startTime = 0;
        isVisible = false;
        dialog.hide();
    }
}

app/src/main/java/com/example/app/ProgressDialog.kt

package com.example.app

import android.app.AlertDialog
import android.app.Dialog
import android.content.Context

class ProgressDialog(context: Context)
{
    private var delayMs: Int = 1000
    private var isVisible: Boolean  = false
    private var startTime: Long = 0
    private var dialog: Dialog = AlertDialog.Builder(context)
        .setView(R.layout.progress)
        .setCancelable(false)
        .create()

    fun show()
    {
        if (isVisible) {
            return
        }
        if (startTime == 0L) {
            startTime = System.currentTimeMillis()
        }
        if (System.currentTimeMillis() - startTime > delayMs) {
            isVisible = true
            dialog.show()
        }
    }

    fun hide()
    {
        startTime = 0
        isVisible = false
        dialog.hide()
    }
}

The MainActivity class implements MediaPlayer.EventListener interface and override onEvent method. Media player event listener is attached to current activity by using the setEventListener method.

When the activity starts, RTSP stream from IP camera is starting to capture. The onEvent method is invoked when media player state is changed. When a RTSP stream is buffering, the Event.Buffering event is triggered. We show the progress dialog if buffering takes more than 1 second. When buffering value reaches 100%, we hide the progress dialog.

The network-caching option is responsible for delay of RTSP stream coming from an IP camera. If this option is too low, then stream capture can freeze.

When the activity is stopped, we stop the media player and detach the video layout from the player. When the activity is destroyed, we release resources.

Don't forget to change the RTSP URL of your IP camera. We used Reolink E1 Pro camera for testing.

app/src/main/java/com/example/app/MainActivity.java

package com.example.app;

import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.util.VLCVideoLayout;

public class MainActivity extends AppCompatActivity implements MediaPlayer.EventListener
{
    private static final String url = "rtsp://user:pass@192.168.0.9:554/h264Preview_01_main";

    private LibVLC libVlc;
    private MediaPlayer mediaPlayer;
    private VLCVideoLayout videoLayout;
    private ProgressDialog progressDialog;

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

        libVlc = new LibVLC(this);
        mediaPlayer = new MediaPlayer(libVlc);
        mediaPlayer.setEventListener(this);
        videoLayout = findViewById(R.id.videoLayout);
        progressDialog = new ProgressDialog(this);
    }

    @Override
    public void onEvent(MediaPlayer.Event event)
    {
        if (event.type == MediaPlayer.Event.Buffering) {
            if (event.getBuffering() == 100f) {
                progressDialog.hide();
            } else {
                progressDialog.show();
            }
        }
    }

    @Override
    protected void onStart()
    {
        super.onStart();

        mediaPlayer.attachViews(videoLayout, null, false, false);

        Media media = new Media(libVlc, Uri.parse(url));
        media.setHWDecoderEnabled(true, false);
        media.addOption(":network-caching=600");

        mediaPlayer.setMedia(media);
        media.release();
        mediaPlayer.play();
    }

    @Override
    protected void onStop()
    {
        super.onStop();

        mediaPlayer.stop();
        mediaPlayer.detachViews();
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();

        mediaPlayer.release();
        libVlc.release();
    }
}

app/src/main/java/com/example/app/MainActivity.kt

package com.example.app

import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.util.VLCVideoLayout

class MainActivity : AppCompatActivity(), MediaPlayer.EventListener
{
    private var url: String = "rtsp://user:pass@192.168.0.9:554/h264Preview_01_main"

    private lateinit var libVlc: LibVLC
    private lateinit var mediaPlayer: MediaPlayer
    private lateinit var videoLayout: VLCVideoLayout
    private lateinit var progressDialog: ProgressDialog

    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        libVlc = LibVLC(this)
        mediaPlayer = MediaPlayer(libVlc)
        mediaPlayer.setEventListener(this)
        videoLayout = findViewById(R.id.videoLayout)
        progressDialog = ProgressDialog(this)
    }

    override fun onEvent(event: MediaPlayer.Event)
    {
        if (event.type == MediaPlayer.Event.Buffering) {
            if (event.buffering == 100f) {
                progressDialog.hide()
            } else {
                progressDialog.show()
            }
        }
    }

    override fun onStart()
    {
        super.onStart()

        mediaPlayer.attachViews(videoLayout, null, false, false)

        val media = Media(libVlc, Uri.parse(url))
        media.setHWDecoderEnabled(true, false)
        media.addOption(":network-caching=600")

        mediaPlayer.media = media
        media.release()
        mediaPlayer.play()
    }

    override fun onStop()
    {
        super.onStop()

        mediaPlayer.stop()
        mediaPlayer.detachViews()
    }

    override fun onDestroy()
    {
        super.onDestroy()

        mediaPlayer.release()
        libVlc.release()
    }
}

Leave a Comment

Cancel reply

Your email address will not be published.