package com.androidwave.exoplayer.ui;
import android.content.Context;
import android.graphics.Point;
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.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
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;
private ArrayList<MediaObject> mediaObjects = new ArrayList<>();
private int videoSurfaceDefaultHeight = 0;
private int screenDefaultHeight = 0;
private int playPosition = -1;
private boolean isVideoViewAdded;
private RequestManager requestManager;
// controlling volume state
private VolumeState volumeState;
private OnClickListener videoViewClickListener = new OnClickListener() {
public void onClick(View v) {
public ExoPlayerRecyclerView(@NonNull Context context) {
public ExoPlayerRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
private void init(Context context) {
this.context = context.getApplicationContext();
Display display = ((WindowManager) Objects.requireNonNull(
getContext().getSystemService(Context.WINDOW_SERVICE))).getDefaultDisplay();
Point point = new 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);
setVolumeControl(VolumeState.ON);
addOnScrollListener(new RecyclerView.OnScrollListener() {
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)) {
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
addOnChildAttachStateChangeListener(new OnChildAttachStateChangeListener() {
public void onChildViewAttachedToWindow(@NonNull View view) {
public void onChildViewDetachedFromWindow(@NonNull View view) {
if (viewHolderParent != null && viewHolderParent.equals(view)) {
videoPlayer.addListener(new Player.EventListener() {
public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) {
public void onTracksChanged(TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) {
public void onLoadingChanged(boolean isLoading) {
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
case Player.STATE_BUFFERING:
Log.e(TAG, "onPlayerStateChanged: Buffering video.");
if (progressBar != null) {
progressBar.setVisibility(VISIBLE);
Log.d(TAG, "onPlayerStateChanged: Video ended.");
Log.e(TAG, "onPlayerStateChanged: Ready to play.");
if (progressBar != null) {
progressBar.setVisibility(GONE);
public void onRepeatModeChanged(int repeatMode) {
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
public void onPlayerError(ExoPlaybackException error) {
public void onPositionDiscontinuity(int reason) {
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
public void onSeekProcessed() {
public void playVideo(boolean 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) {
// if there is more than 1 list-item on the screen
if (startPosition != endPosition) {
int startPositionVideoHeight = getVisibleVideoSurfaceHeight(startPosition);
int endPositionVideoHeight = getVisibleVideoSurfaceHeight(endPosition);
startPositionVideoHeight > endPositionVideoHeight ? startPosition : endPosition;
targetPosition = startPosition;
targetPosition = mediaObjects.size() - 1;
Log.d(TAG, "playVideo: target position: " + targetPosition);
// video is already playing so return
if (targetPosition == playPosition) {
// set the position of the list-item that is to be played
playPosition = targetPosition;
if (videoSurfaceView == null) {
// remove any old surface views from previously playing videos
videoSurfaceView.setVisibility(INVISIBLE);
removeVideoView(videoSurfaceView);
targetPosition - ((LinearLayoutManager) Objects.requireNonNull(
getLayoutManager())).findFirstVisibleItemPosition();
View child = getChildAt(currentPosition);
PlayerViewHolder holder = (PlayerViewHolder) child.getTag();
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();
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);
int[] location = new int[2];
child.getLocationInWindow(location);
return location[1] + videoSurfaceDefaultHeight;
return screenDefaultHeight - location[1];
private void removeVideoView(PlayerView videoView) {
ViewGroup parent = (ViewGroup) videoView.getParent();
int index = parent.indexOfChild(videoView);
parent.removeViewAt(index);
isVideoViewAdded = false;
viewHolderParent.setOnClickListener(null);
private void addVideoView() {
mediaContainer.addView(videoSurfaceView);
videoSurfaceView.requestFocus();
videoSurfaceView.setVisibility(VISIBLE);
videoSurfaceView.setAlpha(1);
mediaCoverImage.setVisibility(GONE);
private void resetVideoView() {
removeVideoView(videoSurfaceView);
videoSurfaceView.setVisibility(INVISIBLE);
mediaCoverImage.setVisibility(VISIBLE);
public void releasePlayer() {
if (videoPlayer != null) {
public void onPausePlayer() {
if (videoPlayer != null) {
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) {
if (state == VolumeState.OFF) {
videoPlayer.setVolume(0f);
} else if (state == VolumeState.ON) {
videoPlayer.setVolume(1f);
private void animateVolumeControl() {
if (volumeControl != null) {
volumeControl.bringToFront();
if (volumeState == VolumeState.OFF) {
requestManager.load(R.drawable.ic_volume_off)
} else if (volumeState == VolumeState.ON) {
requestManager.load(R.drawable.ic_volume_on)
volumeControl.animate().cancel();
volumeControl.setAlpha(1f);
.setDuration(600).setStartDelay(1000);
public void setMediaObjects(ArrayList<MediaObject> mediaObjects) {
this.mediaObjects = mediaObjects;
private enum VolumeState {