Android & Kotlin

ExoPlayer in RecyclerView in Android

Pinterest LinkedIn Tumblr

In the previous tutorial, we demonstrate video streaming over the Internet using ExoPlayer. Somehow you missed previous articles you must read this ExoPlayer in Android for better understanding. In another article we saw how to play audio, video from SD card (local) as well, So you get an idea about ExoPlayer core functionality, DASH and UI library module.

I hope you read my previous tutorials(I can assume you have basic knowledge about ExoPlayer). In this tutorial, I will describe how to implement video playback in the RecyclerView Android. Just like some popular application eg. Magisto, Facebook, Twitter, Instagram.

This article based on ExoPlayer. In case some things missing in this article download full source and a working sample app is there.

Before starting code, Think about the problem statement. Basically, The following major challenges for us to implementing ExoPlayer in RecyclerView.

  • The first problem is managing ExoPlayer playback in RecyclerView
  • Second is we need to know which view on the scrolled list is currently active. Other words you can say which list item is visible 100%. So we can attach the player with a particular view.

Step to implement ExoPlayer in RecyclerView

  • Create Project and set up dependencies
  • Write a custom RecyclerView that are capable of manage video playback.
  • Write an adapter for holding list item
  • Create a model class with getter setter
  • Prepare an XML layout for media list item
  • Write ViewHolder class for representing media list item
  • Complete the RecyclerView Adapter
  • Do followings operation in MainActivity
  • In MainActivity layout, file adds custom ExoRecyclerView that create earlier.
  • Prepare demo content
  • Set the adapter to RecyclerView
  • Run the project

1. Create Project and set up dependencies

All conceptual part is done, let’s move to AndroidStudio create a new project with the default template. Follows few steps your project will be created, now we need to add some dependencies in app/gradle file.

Following dependencies are in this android app tutorial. I hope you already know the uses of use one.

  • RecyclerView – For using RecyclerView
  • ExoPlayer – Using ExoPlayer
  • Glide – For loading media thumbnails
  • Constraint Layout – Performace is much better to other layouts
1.1 Open the app/build.gradle file and add all above dependencies
  // RecyclerView
  implementation 'com.android.support:recyclerview-v7:28.0.0'

  // ExoPlayer
  implementation 'com.google.android.exoplayer:exoplayer:2.7.3'
  // Image loading
  implementation 'com.github.bumptech.glide:glide:4.9.0'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
1.2 Enable vector drawable by adding below line inside defaultConfig bracket
  vectorDrawables.useSupportLibrary = true

2. Write a custom RecyclerView that is capable manage video playback.

Doing that we have to create a new subclass of RecyclerView named is ExoPlayerRecyclerView. Mean create a new class ExoPlayerRecyclerView.java which extends RecyclerView. In this class, we have to manage two things. First, manage ExoPlayer playback. Second is to find that list item that visibility 100%. So that we will add a player on that particular view.

2.1 Problem – how to manage ExoPlayer playback?
  • Create a class ExoPlayerRecyclerView.java which extends RecyclerView 
  • Get start position and end position of view from LayoutManager after that calculate currently visible view position.
  • Here now on target position, we will add ExoPlayerView and set ExoPlayer.
  //play the video in the row
  public void playVideo(boolean isEndOfList) {

    int targetPosition;

    if (!isEndOfList) {
      int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
          getLayoutManager())).findFirstVisibleItemPosition();
      int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

      // if there is more than 2 list-items on the screen, set the difference to be 1
      if (endPosition - startPosition > 1) {
        endPosition = startPosition + 1;
      }

      // something is wrong. return.
      if (startPosition < 0 || endPosition < 0) {
        return;
      }

      // if there is more than 1 list-item on the screen
      if (startPosition != endPosition) {
        int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
        int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);

        targetPosition =
            startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
      } else {
        targetPosition = startPosition;
      }
    } else {
      targetPosition = mediaObjects.size() - 1;
    }

    Log.d(TAG, "playVideo: target position: " + targetPosition);

    // video is already playing so return
    if (targetPosition == playPosition) {
      return;
    }

    // set the position of the list-item that is to be played
    playPosition = targetPosition;
    if (videoSurfaceView == null) {
      return;
    }

    // remove any old surface views from previously playing videos
    videoSurfaceView.setVisibility(INVISIBLE);
    removeVideoView(videoSurfaceView);

    int currentPosition =
        targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
            getLayoutManager())).findFirstVisibleItemPosition();

    View child = getChildAt(currentPosition);
    if (child == null) {
      return;
    }

    PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
    if (holder == null) {
      playPosition = -1;
      return;
    }
    mediaCoverImage = holder.mediaCoverImage;
    progressBar = holder.progressBar;
    volumeControl = holder.volumeControl;
    viewHolderParent = holder.itemView;
    requestManager = holder.requestManager;
    mediaContainer = holder.mediaContainer;

    videoSurfaceView.setPlayer(videoPlayer);
    viewHolderParent.setOnClickListener(videoViewClickListener);

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
        context, Util.getUserAgent(context, AppName));
    String mediaUrl = mediaObjects.get(targetPosition).getUrl();
    if (mediaUrl != null) {
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(Uri.parse(mediaUrl));
      videoPlayer.prepare(videoSource);
      videoPlayer.setPlayWhenReady(true);
    }
  }

  andwidthMeter =new

  DefaultBandwidthMeter();
  // Produces DataSource instances through which media data is loaded.

  DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(appContext,
      Util.getUserAgent(appContext, "android_wave_list"), defaultBandwidthMeter);
  // This is the MediaSource representing the media to be played.
  String uriString = videoInfoList.get(targetPosition).getUrl();
        if(uriString !=null)

  {
    MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
        .createMediaSource(Uri.parse(uriString));
    // Prepare the player with the source.
    player.prepare(videoSource);
    player.setPlayWhenReady(true);
  }
}
2.2 Second Problem – Get visible video surface scrolled list.
 /**
   * Returns the visible region of the video surface on the screen.
   * if some is cut off, it will return less than the @videoSurfaceDefaultHeight
   */
  private int getVisibleVideoSurfaceHeight(int playPosition) {
    int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
        getLayoutManager())).findFirstVisibleItemPosition();
    Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);

    View child = getChildAt(at);
    if (child == null) {
      return 0;
    }

    int[] location = new int[2];
    child.getLocationInWindow(location);

    if (location[1] < 0) {
      return location[1] + videoSurfaceDefaultHeight;
    } else {
      return screenDefaultHeight - location[1];
    }
  }

I have solved both problems, The complete code of ExoPlayerRecyclerView is

package com.androidwave.exoplayer.ui;

import android.content.Context;
import android.graphics.Point;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.adapter.PlayerViewHolder;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Objects;

/**
 * Created on : May 24, 2019
 * Author     : AndroidWave
 */
public class ExoPlayerRecyclerView extends RecyclerView {

  private static final String TAG = "ExoPlayerRecyclerView";
  private static final String AppName = "Android ExoPlayer";
  /**
   * PlayerViewHolder UI component
   * Watch PlayerViewHolder class
   */
  private ImageView mediaCoverImage, volumeControl;
  private ProgressBar progressBar;
  private View viewHolderParent;
  private FrameLayout mediaContainer;
  private PlayerView videoSurfaceView;
  private SimpleExoPlayer videoPlayer;
  /**
   * variable declaration
   */
  // Media List
  private ArrayList<MediaObject> mediaObjects = new ArrayList<>();
  private int videoSurfaceDefaultHeight = 0;
  private int screenDefaultHeight = 0;
  private Context context;
  private int playPosition = -1;
  private boolean isVideoViewAdded;
  private RequestManager requestManager;
  // controlling volume state
  private VolumeState volumeState;
  private OnClickListener videoViewClickListener = new OnClickListener() {
    @Override
    public void onClick(View v) {
      toggleVolume();
    }
  };

  public ExoPlayerRecyclerView(@NonNull Context context) {
    super(context);
    init(context);
  }

  public ExoPlayerRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init(context);
  }

  private void init(Context context) {
    this.context = context.getApplicationContext();
    Display display = ((WindowManager) Objects.requireNonNull(
        getContext().getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay();
    Point point = new Point();
    display.getSize(point);

    videoSurfaceDefaultHeight = point.x;
    screenDefaultHeight = point.y;

    videoSurfaceView = new PlayerView(this.context);
    videoSurfaceView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);

    BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
    TrackSelection.Factory videoTrackSelectionFactory =
        new AdaptiveTrackSelection.Factory(bandwidthMeter);
    TrackSelector trackSelector =
        new DefaultTrackSelector(videoTrackSelectionFactory);

    //Create the player using ExoPlayerFactory
    videoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
    // Disable Player Control
    videoSurfaceView.setUseController(false);
    // Bind the player to the view.
    videoSurfaceView.setPlayer(videoPlayer);
    // Turn on Volume
    setVolumeControl(VolumeState.ON);

    addOnScrollListener(new RecyclerView.OnScrollListener() {
      @Override
      public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
          if (mediaCoverImage != null) {
            // show the old thumbnail
            mediaCoverImage.setVisibility(VISIBLE);
          }

          // There's a special case when the end of the list has been reached.
          // Need to handle that with this bit of logic
          if (!recyclerView.canScrollVertically(1)) {
            playVideo(true);
          } else {
            playVideo(false);
          }
        }
      }

      @Override
      public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
      }
    });

    addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
      @Override
      public void onChildViewAttachedToWindow(@NonNull View view) {

      }

      @Override
      public void onChildViewDetachedFromWindow(@NonNull View view) {
        if (viewHolderParent != null && viewHolderParent.equals(view)) {
          resetVideoView();
        }
      }
    });

    videoPlayer.addListener(new Player.EventListener() {
      @Override
      public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {

      }

      @Override
      public void onTracksChanged(TrackGroupArray trackGroups,
          TrackSelectionArray trackSelections) {

      }

      @Override
      public void onLoadingChanged(boolean isLoading) {

      }

      @Override
      public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
        switch (playbackState) {

          case Player.STATE_BUFFERING:
            Log.e(TAG, "onPlayerStateChanged: Buffering video.");
            if (progressBar != null) {
              progressBar.setVisibility(VISIBLE);
            }

            break;
          case Player.STATE_ENDED:
            Log.d(TAG, "onPlayerStateChanged: Video ended.");
            videoPlayer.seekTo(0);
            break;
          case Player.STATE_IDLE:

            break;
          case Player.STATE_READY:
            Log.e(TAG, "onPlayerStateChanged: Ready to play.");
            if (progressBar != null) {
              progressBar.setVisibility(GONE);
            }
            if (!isVideoViewAdded) {
              addVideoView();
            }
            break;
          default:
            break;
        }
      }

      @Override
      public void onRepeatModeChanged(int repeatMode) {

      }

      @Override
      public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

      }

      @Override
      public void onPlayerError(ExoPlaybackException error) {

      }

      @Override
      public void onPositionDiscontinuity(int reason) {

      }

      @Override
      public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {

      }

      @Override
      public void onSeekProcessed() {

      }
    });
  }

  public void playVideo(boolean isEndOfList) {

    int targetPosition;

    if (!isEndOfList) {
      int startPosition = ((LinearLayoutManager) Objects.requireNonNull(
          getLayoutManager())).findFirstVisibleItemPosition();
      int endPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();

      // if there is more than 2 list-items on the screen, set the difference to be 1
      if (endPosition - startPosition > 1) {
        endPosition = startPosition + 1;
      }

      // something is wrong. return.
      if (startPosition < 0 || endPosition < 0) {
        return;
      }

      // if there is more than 1 list-item on the screen
      if (startPosition != endPosition) {
        int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
        int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);

        targetPosition =
            startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
      } else {
        targetPosition = startPosition;
      }
    } else {
      targetPosition = mediaObjects.size() - 1;
    }

    Log.d(TAG, "playVideo: target position: " + targetPosition);

    // video is already playing so return
    if (targetPosition == playPosition) {
      return;
    }

    // set the position of the list-item that is to be played
    playPosition = targetPosition;
    if (videoSurfaceView == null) {
      return;
    }

    // remove any old surface views from previously playing videos
    videoSurfaceView.setVisibility(INVISIBLE);
    removeVideoView(videoSurfaceView);

    int currentPosition =
        targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
            getLayoutManager())).findFirstVisibleItemPosition();

    View child = getChildAt(currentPosition);
    if (child == null) {
      return;
    }

    PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
    if (holder == null) {
      playPosition = -1;
      return;
    }
    mediaCoverImage = holder.mediaCoverImage;
    progressBar = holder.progressBar;
    volumeControl = holder.volumeControl;
    viewHolderParent = holder.itemView;
    requestManager = holder.requestManager;
    mediaContainer = holder.mediaContainer;

    videoSurfaceView.setPlayer(videoPlayer);
    viewHolderParent.setOnClickListener(videoViewClickListener);

    DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
        context, Util.getUserAgent(context, AppName));
    String mediaUrl = mediaObjects.get(targetPosition).getUrl();
    if (mediaUrl != null) {
      MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory)
          .createMediaSource(Uri.parse(mediaUrl));
      videoPlayer.prepare(videoSource);
      videoPlayer.setPlayWhenReady(true);
    }
  }

  /**
   * Returns the visible region of the video surface on the screen.
   * if some is cut off, it will return less than the @videoSurfaceDefaultHeight
   */
  private int getVisibleVideoSurfaceHeight(int playPosition) {
    int at = playPosition - ((LinearLayoutManager) Objects.requireNonNull(
        getLayoutManager())).findFirstVisibleItemPosition();
    Log.d(TAG, "getVisibleVideoSurfaceHeight: at: " + at);

    View child = getChildAt(at);
    if (child == null) {
      return 0;
    }

    int[] location = new int[2];
    child.getLocationInWindow(location);

    if (location[1] < 0) {
      return location[1] + videoSurfaceDefaultHeight;
    } else {
      return screenDefaultHeight - location[1];
    }
  }

  // Remove the old player
  private void removeVideoView(PlayerView videoView) {
    ViewGroup parent = (ViewGroup) videoView.getParent();
    if (parent == null) {
      return;
    }

    int index = parent.indexOfChild(videoView);
    if (index >= 0) {
      parent.removeViewAt(index);
      isVideoViewAdded = false;
      viewHolderParent.setOnClickListener(null);
    }
  }

  private void addVideoView() {
    mediaContainer.addView(videoSurfaceView);
    isVideoViewAdded = true;
    videoSurfaceView.requestFocus();
    videoSurfaceView.setVisibility(VISIBLE);
    videoSurfaceView.setAlpha(1);
    mediaCoverImage.setVisibility(GONE);
  }

  private void resetVideoView() {
    if (isVideoViewAdded) {
      removeVideoView(videoSurfaceView);
      playPosition = -1;
      videoSurfaceView.setVisibility(INVISIBLE);
      mediaCoverImage.setVisibility(VISIBLE);
    }
  }

  public void releasePlayer() {

    if (videoPlayer != null) {
      videoPlayer.release();
      videoPlayer = null;
    }

    viewHolderParent = null;
  }

  public void onPausePlayer() {
    if (videoPlayer != null) {
      videoPlayer.stop(true);
    }
  }

  private void toggleVolume() {
    if (videoPlayer != null) {
      if (volumeState == VolumeState.OFF) {
        Log.d(TAG, "togglePlaybackState: enabling volume.");
        setVolumeControl(VolumeState.ON);
      } else if (volumeState == VolumeState.ON) {
        Log.d(TAG, "togglePlaybackState: disabling volume.");
        setVolumeControl(VolumeState.OFF);
      }
    }
  }

  private void setVolumeControl(VolumeState state) {
    volumeState = state;
    if (state == VolumeState.OFF) {
      videoPlayer.setVolume(0f);
      animateVolumeControl();
    } else if (state == VolumeState.ON) {
      videoPlayer.setVolume(1f);
      animateVolumeControl();
    }
  }

  private void animateVolumeControl() {
    if (volumeControl != null) {
      volumeControl.bringToFront();
      if (volumeState == VolumeState.OFF) {
        requestManager.load(R.drawable.ic_volume_off)
            .into(volumeControl);
      } else if (volumeState == VolumeState.ON) {
        requestManager.load(R.drawable.ic_volume_on)
            .into(volumeControl);
      }
      volumeControl.animate().cancel();

      volumeControl.setAlpha(1f);

      volumeControl.animate()
          .alpha(0f)
          .setDuration(600).setStartDelay(1000);
    }
  }

  public void setMediaObjects(ArrayList<MediaObject> mediaObjects) {
    this.mediaObjects = mediaObjects;
  }

  /**
   * Volume ENUM
   */
  private enum VolumeState {
    ON, OFF
  }
}

Now ExoPlayerRecyclerView.java component is ready to use. Now I will explain how to use this component.

3. Write an adapter for holding list item

Before moving ahead we need to write some helper class for RecyclerView adapter,

3.1 Create a model class with getter setter

Go to the src folder and create the model folder for separating all model classes. Inside this folder create a POJO class named MediaObject.java. That has getter and setter of title and cover image URL and media URL

package com.androidwave.exoplayer.model;

/**
 * Created by Morris on 03,June,2019
 */
public class MediaObject {
  private int uId;
  private String title;
  private String mediaUrl;
  private String mediaCoverImgUrl;
  private String userHandle;

  public String getUserHandle() {
    return userHandle;
  }

  public void setUserHandle(String mUserHandle) {
    this.userHandle = mUserHandle;
  }

  public int getId() {
    return uId;
  }

  public void setId(int uId) {
    this.uId = uId;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String mTitle) {
    this.title = mTitle;
  }

  public String getUrl() {
    return mediaUrl;
  }

  public void setUrl(String mUrl) {
    this.mediaUrl = mUrl;
  }

  public String getCoverUrl() {
    return mediaCoverImgUrl;
  }

  public void setCoverUrl(String mCoverUrl) {
    this.mediaCoverImgUrl = mCoverUrl;
  }
}
3.2 Prepare an XML layout for media list item

Create a layout file inside the res => layout folder named is layout_media_list_item.xml, which holds the view of the list item. So add some view component in this layout file.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    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="wrap_content"
    android:layout_margin="8dp"
    android:background="@color/white"
    >

  <TextView
      android:id="@+id/tvTitle"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginEnd="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:letterSpacing="-0.02"
      android:lineSpacingExtra="5sp"
      android:textColor="@color/navy"
      android:textSize="18sp"
      android:textStyle="bold"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      tools:text="Was india better then south africa in current serise?"
      />

  <ImageView
      android:id="@+id/imageView"
      android:layout_width="25dp"
      android:layout_height="25dp"
      android:layout_marginBottom="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      app:layout_constraintBottom_toTopOf="@+id/mediaContainer"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/tvTitle"
      app:srcCompat="@drawable/ic_user"
      />

  <TextView
      android:id="@+id/tvUserHandle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:letterSpacing="-0.02"
      android:textColor="@color/windows_blue"
      android:textSize="10sp"
      app:layout_constraintBottom_toBottomOf="@+id/imageView"
      app:layout_constraintStart_toEndOf="@+id/imageView"
      app:layout_constraintTop_toTopOf="@+id/imageView"
      tools:text="by @morris12"
      />

  <FrameLayout
      android:id="@+id/mediaContainer"
      android:layout_width="match_parent"
      android:layout_height="300dp"
      android:layout_marginBottom="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginStart="8dp"
      android:adjustViewBounds="true"
      android:background="@android:color/black"
      android:gravity="center"
      android:scaleType="center"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/imageView"
      >

    <ImageView
        android:id="@+id/ivMediaCoverImage"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:scaleType="centerCrop"
        android:src="@drawable/cover"
        />


    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"
        style="?android:attr/progressBarStyle"
        />
    <ImageView
        android:id="@+id/ivVolumeControl"
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_gravity="end|bottom"
        android:layout_marginBottom="15dp"
        android:layout_marginEnd="15dp"
        android:alpha="0"
        android:animateLayoutChanges="true"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_volume_on"
        />
  </FrameLayout>

</android.support.constraint.ConstraintLayout>
3.4 Write ViewHolder class for representing media list item

Create a new class inside the adapter package named is PlayerViewHolder. This class will extends RecyclerView.ViewHolder. Initialize the all list item inside this view.

package com.androidwave.exoplayer.adapter;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;

/**
 * Created by Morris on 03,June,2019
 */
public class PlayerViewHolder extends RecyclerView.ViewHolder {

  /**
   * below view have public modifier because
   * we have access PlayerViewHolder inside the ExoPlayerRecyclerView
   */
  public FrameLayout mediaContainer;
  public ImageView mediaCoverImage, volumeControl;
  public ProgressBar progressBar;
  public RequestManager requestManager;
  private TextView title, userHandle;
  private View parent;

  public PlayerViewHolder(@NonNull View itemView) {
    super(itemView);
    parent = itemView;
    mediaContainer = itemView.findViewById(R.id.mediaContainer);
    mediaCoverImage = itemView.findViewById(R.id.ivMediaCoverImage);
    title = itemView.findViewById(R.id.tvTitle);
    userHandle = itemView.findViewById(R.id.tvUserHandle);
    progressBar = itemView.findViewById(R.id.progressBar);
    volumeControl = itemView.findViewById(R.id.ivVolumeControl);
  }

  void onBind(MediaObject mediaObject, RequestManager requestManager) {
    this.requestManager = requestManager;
    parent.setTag(this);
    title.setText(mediaObject.getTitle());
    userHandle.setText(mediaObject.getUserHandle());
    this.requestManager
        .load(mediaObject.getCoverUrl())
        .into(mediaCoverImage);
  }
}
3.5 Complete the RecyclerView Adapter

Doing that create an adapter class named is MediaRecyclerAdapter and paste below code

package com.androidwave.exoplayer.adapter;

import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.androidwave.exoplayer.R;
import com.androidwave.exoplayer.model.MediaObject;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;

/**
 * Created by Morris on 03,June,2019
 */
public class MediaRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

  private ArrayList<MediaObject> mediaObjects;
  private RequestManager requestManager;

  public MediaRecyclerAdapter(ArrayList<MediaObject> mediaObjects,
      RequestManager requestManager) {
    this.mediaObjects = mediaObjects;
    this.requestManager = requestManager;
  }

  @NonNull
  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    return new PlayerViewHolder(
        LayoutInflater.from(viewGroup.getContext())
            .inflate(R.layout.layout_media_list_item, viewGroup, false));
  }

  @Override
  public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
    ((PlayerViewHolder) viewHolder).onBind(mediaObjects.get(i), requestManager);
  }

  @Override
  public int getItemCount() {
    return mediaObjects.size();
  }
}

4. Do followings operation in MainActivity

All part is done, Now come to activity part.

4.1 Lets add ExoPlayerRecyclerView inside the XML file

If you selected some default template while creating a project, MainActivity.java and activity_main.xml class will automatically be created. Open the activity_main and add below code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    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"
    >

  <com.androidwave.exoplayer.ui.ExoPlayerRecyclerView
      android:id="@+id/exoPlayerRecyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="#dedede"
      android:dividerHeight="8dp"
      />

</android.support.constraint.ConstraintLayout>
4.2 Prepare demo content

For displaying data on RecyclerView We have to prepare a demo content.

 private void prepareVideoList() {
    VideoInfo videoInfo = new VideoInfo();
    videoInfo.setId(1);
    videoInfo.setUserHandle("@h.pandya");
    videoInfo.setTitle("Do you think the concept of marriage will no longer exist in the future?");
    videoInfo.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-1.png");
    videoInfo.setUrl("https://androidwave.com/media/androidwave-video-1.mp4");

    VideoInfo videoInfo2 = new VideoInfo();
    videoInfo2.setId(2);
    videoInfo2.setUserHandle("@hardik.patel");
    videoInfo2.setTitle(
        "If my future husband doesn't cook food as good as my mother should I scold him?");
    videoInfo2.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-2.png");
    videoInfo2.setUrl("https://androidwave.com/media/androidwave-video-2.mp4");

    VideoInfo videoInfo3 = new VideoInfo();
    videoInfo3.setId(3);
    videoInfo3.setUserHandle("@arun.gandhi");
    videoInfo3.setTitle("Give your opinion about the Ayodhya temple controversy.");
    videoInfo3.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-3.png");
    videoInfo3.setUrl("https://androidwave.com/media/androidwave-video-3.mp4");

    VideoInfo videoInfo4 = new VideoInfo();
    videoInfo4.setId(4);
    videoInfo4.setUserHandle("@sachin.patel");
    videoInfo4.setTitle("When did kama founders find sex offensive to Indian traditions");
    videoInfo4.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-4.png");
    videoInfo4.setUrl("https://androidwave.com/media/androidwave-video-6.mp4");

    VideoInfo videoInfo5 = new VideoInfo();
    videoInfo5.setId(5);
    videoInfo5.setUserHandle("@monika.sharma");
    videoInfo5.setTitle("When did you last cry in front of someone?");
    videoInfo5.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-5.png");
    videoInfo5.setUrl("https://androidwave.com/media/androidwave-video-5.mp4");

    videoInfoList.add(videoInfo);
    videoInfoList.add(videoInfo2);
    videoInfoList.add(videoInfo3);
    videoInfoList.add(videoInfo4);
    videoInfoList.add(videoInfo5);
    videoInfoList.add(videoInfo);
    videoInfoList.add(videoInfo2);
    videoInfoList.add(videoInfo3);
    videoInfoList.add(videoInfo4);
    videoInfoList.add(videoInfo5);
  }

4.3 Finally MainActivity source code looks like

package com.androidwave.exoplayer;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import com.androidwave.exoplayer.adapter.MediaRecyclerAdapter;
import com.androidwave.exoplayer.model.MediaObject;
import com.androidwave.exoplayer.ui.ExoPlayerRecyclerView;
import com.androidwave.exoplayer.utils.DividerItemDecoration;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;

import static android.widget.LinearLayout.VERTICAL;

/**
 * Created by Morris on 03,June,2019
 */
public class MainActivity extends AppCompatActivity {

  ExoPlayerRecyclerView mRecyclerView;

  private ArrayList<MediaObject> mediaObjectList = new ArrayList<>();
  private MediaRecyclerAdapter mAdapter;
  private boolean firstTime = true;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
    // Prepare demo content
    prepareVideoList();

    //set data object
    mRecyclerView.setMediaObjects(mediaObjectList);
    mAdapter = new MediaRecyclerAdapter(mediaObjectList, initGlide());

    //Set Adapter
    mRecyclerView.setAdapter(mAdapter);

    if (firstTime) {
      new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
          mRecyclerView.playVideo(false);
        }
      });
      firstTime = false;
    }
  }

  private void initView() {
    mRecyclerView = findViewById(R.id.exoPlayerRecyclerView);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this, VERTICAL, false));
    Drawable dividerDrawable = ContextCompat.getDrawable(this, R.drawable.divider_drawable);
    mRecyclerView.addItemDecoration(new DividerItemDecoration(dividerDrawable));
    mRecyclerView.setItemAnimator(new DefaultItemAnimator());
  }

  private RequestManager initGlide() {
    RequestOptions options = new RequestOptions();

    return Glide.with(this)
        .setDefaultRequestOptions(options);
  }

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

  private void prepareVideoList() {
    MediaObject mediaObject = new MediaObject();
    mediaObject.setId(1);
    mediaObject.setUserHandle("@h.pandya");
    mediaObject.setTitle(
        "Do you think the concept of marriage will no longer exist in the future?");
    mediaObject.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-1.png");
    mediaObject.setUrl("https://androidwave.com/media/androidwave-video-1.mp4");

    MediaObject mediaObject2 = new MediaObject();
    mediaObject2.setId(2);
    mediaObject2.setUserHandle("@hardik.patel");
    mediaObject2.setTitle(
        "If my future husband doesn't cook food as good as my mother should I scold him?");
    mediaObject2.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-2.png");
    mediaObject2.setUrl("https://androidwave.com/media/androidwave-video-2.mp4");

    MediaObject mediaObject3 = new MediaObject();
    mediaObject3.setId(3);
    mediaObject3.setUserHandle("@arun.gandhi");
    mediaObject3.setTitle("Give your opinion about the Ayodhya temple controversy.");
    mediaObject3.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-3.png");
    mediaObject3.setUrl("https://androidwave.com/media/androidwave-video-3.mp4");

    MediaObject mediaObject4 = new MediaObject();
    mediaObject4.setId(4);
    mediaObject4.setUserHandle("@sachin.patel");
    mediaObject4.setTitle("When did kama founders find sex offensive to Indian traditions");
    mediaObject4.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-4.png");
    mediaObject4.setUrl("https://androidwave.com/media/androidwave-video-6.mp4");

    MediaObject mediaObject5 = new MediaObject();
    mediaObject5.setId(5);
    mediaObject5.setUserHandle("@monika.sharma");
    mediaObject5.setTitle("When did you last cry in front of someone?");
    mediaObject5.setCoverUrl(
        "https://androidwave.com/media/images/exo-player-in-recyclerview-in-android-5.png");
    mediaObject5.setUrl("https://androidwave.com/media/androidwave-video-5.mp4");

    mediaObjectList.add(mediaObject);
    mediaObjectList.add(mediaObject2);
    mediaObjectList.add(mediaObject3);
    mediaObjectList.add(mediaObject4);
    mediaObjectList.add(mediaObject5);
    mediaObjectList.add(mediaObject);
    mediaObjectList.add(mediaObject2);
    mediaObjectList.add(mediaObject3);
    mediaObjectList.add(mediaObject4);
    mediaObjectList.add(mediaObject5);
  }
}

Conclusion

You are almost finished. That’s all for ExoPlayer in RecyclerViiew. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development. I

Download Sample Project- ExoPlayer in RecyclerView in Android

If you have any queries, feel free to ask them in the comment section below. Happy Coding 🙂

Read more on android architecture components tutorials

50 Comments

  1. shubham raj Reply

    i have implemented this, but when when i am doing notifyItemChanged, video gets restart from 0 playtime, but i don’t want to disturb video player and just changing a view in the item on a button click, how can i implement this?

  2. Thanks for this wonderfull toturial.

    i want to play videos in res/raw floder i ‘ve replaced the link like this:

    mediaObject.setCoverUrl(
            "android.resource://" + getPackageName() + "/" + R.raw.video1);
    mediaObject.setUrl("android.resource://" + getPackageName() + "/" + R.raw.video1);
    

    the thumbnails load but the videos doesn’t play,
    how can i make it work?

    • Namaskaram! thanks for your help,

      i did not see how I can get videos from res/Raw ( this was from external storage )
      and i need the videos to be embedded in the app and offline, the app will be used by remote rural people where there is no access to internet. and if the videos are in storage they can be deleted easily and delivering future videos will be impossible. the ideal was to make an app where we can update the videos once in some months and ask them to find a way they can update the app. I need help achieving this, am very new to android development, but I learn firster.

  3. I am facing this issue,
    java.lang.NullPointerException
        at java.util.Objects.requireNonNull(Objects.java:203)

  4. Sir.
    Thank for your achievement.
    Could I use this code in commercial app ?
    Is it free?

  5. sachin rakibe Reply

    i have implemented this code but video start to play when 1 minute part of video has lode ..
    i want to start video frequently as some 4 to 5 second part get lode

    • You can male a change these values
      package com.androidwave.exoplayer;
      public class VideoPlayerConfig {
      //Minimum Video you want to buffer while Playing
      public static final int MIN_BUFFER_DURATION = 3000;
      //Max Video you want to buffer during PlayBack
      public static final int MAX_BUFFER_DURATION = 5000;
      //Min Video you want to buffer before start Playing it
      public static final int MIN_PLAYBACK_START_BUFFER = 1500;
      //Min video You want to buffer when user resumes video
      public static final int MIN_PLAYBACK_RESUME_BUFFER = 5000;
      public static final String DEFAULT_VIDEO_URL =
      "https://androidwave.com/media/androidwave-video-exo-player.mp4";
      }

      for more details read this article https://androidwave.com/video-streaming-exoplayer-in-android/

  6. hii can you guide me why video is not playing at zero position when i enter first time. i need to scroll everytime to play zero position video

  7. Amit Verma Reply

    If I am doing notifyDataSetChanged in recyler view video getting played sound is coming but video does not show please help me in that

    • Deepesh kumar

      Sir pls provide me the source code , why you deleted the source code ?

  8. Could you offer a sample ViewPager2+ExoPlayer instead of recyclerview?

  9. what if there is no video link available,
    actually, i have to display post either it has video or not , then how to show video view or diable it

  10. App crashes on scroll in MOTO -G while it is working fine in others.

  11. there is an issue when I scroll and targetposition is equals to playing position , audio is playing but video is invisible

  12. Ankit rana Reply

    it is not working with multiple viewtype (viewholder). its video not stop when i am on another viewholder like imageviewholder and i am back on videovideo holde then previous video stop and new play.

  13. thank you @droidwave . Your code works very well in my application. but. there is a slight problem.
    so. I use your exoplayerrecyclerview to play videos on recyclerview, the problem is when I call the notifyitemchange (position) function to update an item on videoViewHolder. the video paused, but the audio still runs in the background. How to make it not stop and keep video play running when the notifyitemchange (position) function is called ??

  14. private void prepareVideoList() {
    }

    I want to get the data for this part of the code from Firebase. But can’t understand.
    Please show this as an example.

  15. how to handle is in recycleview have multiple type data ? image and video, how to make exoplayer recycleview play only ig type is video ? thanks

    • Adam Hurwitz

      I haven’t done this experiment yet, but my first approach would be to manage the ExoPlayer instance from a foreground service, which returns the playerView that can be started/stopped within the RecyclerAdapter as required.

  16. How can i insert (google video ads or banner ads) after each 3 video this.

  17. i want to get video from api. how can i setup to play video in recycleview.

    • Fetch the data from API and set it on RecyclerView Adapter. I think you asking somethings else

    • no,my question is same.i have backend with api.i want to fetch data from my backend or api.

    • Inbox me in facebook will discuss your problem in detail

  18. ExoPlayerRecyclerView.java
    videoSurfaceView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM);
    <- here I met the above post error.
    so, I remarked the line, then the app is executed,
    Now I can hear the audio, but the playing video is showed only full-black.
    Could you please help me?

  19. Hi,

    Brilliant article. Kindly, can you guide me how can I play YouTube videos. I can pass video url of youtube video to play.

    Can you help on this?

    thanks

    Waqas

  20. Hi, I really enjoyed your article … But I would like you to help me with the following:
    I want this to be implemented within a fragment … Could you help me ???

  21. Akash Garg Reply

    How to ensure the exoplayer prebuffer for instant seamless playing in HLS while scrolling.

  22. Can you please help me use exoplayer in recyclerview (videos from firebase)

    • You have to just replace the URL from firebase file URL.

  23. Rohan Luthra Reply

    Sir, I am not able to add this to fragments.
    The onCreateView() in VideoAdapter is not being called.
    Also i’m getting a Error log – “RecyclerView: No adapter attached; skipping layout”
    The code is working correctly when implemented in an Activity. Please help.

  24. Rohan Luthra Reply

    Hey, Can you tell me how to implement this in Fragments using View Pager?
    Thank you,

    • Hi, have you got, how to handled in Paused State , On Resume state

    • Player.EventListener() listener is sent callback event here
      @Override
      public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
      switch (playbackState) {

      case Player.STATE_BUFFERING:
      // videoSurfaceView.setAlpha(0.5f);
      break;
      case Player.STATE_ENDED:
      player.seekTo(0);
      break;
      case Player.STATE_IDLE:

      break;
      case Player.STATE_READY:
      videoSurfaceView.setVisibility(VISIBLE);
      videoSurfaceView.setAlpha(1);
      mCoverImage.setVisibility(GONE);

      break;
      default:
      break;
      }
      }

    • Hello Sir, How to handle it when intent to new activity the video is playing in background.

  25. Aoa sir how are u?

    you are doing good job to help other developers thanks i need your help i am working with view pager fragment and i wants when i go to video view pager player should start and when i back from that page player should stop actually i am having issue with stop the player and restart the player please help?

  26. Hello Sir,
    i m new to android your tutorial is good. I want to full source code exoplayer in Recylerview and Autoplayback.
    if i pause at some point for example if i pause the video at 20sec if i go back and come again same activity it should be played(continued) from 20sec. how can i do this…..

  27. Hello Sir,
    I want to full source code exoplayer in Recylerview and Autoplayback.

    Thanks!!
    Hitesh

Write A Comment