پیاده سازی exoplayer در recyclerView در اندروید


پیاده سازی exoplayer در recyclerView

نحوه پیاده سازی کتابخانه exoplayer موضوع یکی از مقالات پیشین بلاگ بود. برای درک بهتر به این مقاله مراجعه کنید. در این مقاله به پیاده سازی و اجرای پلی بک با exoplayer در RecyclerView می پردازیم. این مورد را در برنامه هایی مثل اینستاگرام، فیسبوک و توییتر پرکاربرد است.

نتیجه نهایی به صورت زیر می باشد:

پیش از شروع کار باید بررسی کنیم چه مورادی مشکل ساز هستند.

  • مشکل اول پیاده سازی پلی بک با کتابخانه exoplayer در recyclerview ست
  • مسئله دوم این است که باید بدانیم در لحظه کدام view یا آیتم از لیست مربوطه در حال نمایش است.

مراحل پیاده سازی کتابخانه exoplayer در recyclerview

  • ساخت پروژه و ست کردن dependency های مربوطه
  • ساخت recyclerview با قابلیت سازگاری با کتابخانه exoplayer
  • ساخت آداپتر برای نگهداری آیتم های لیست
  • ساخت کلاس entity به همراه getter/setter های مربوطه
  • آماده سازی layout مربوطه برای لیست مدیاها
  • نوشتن کلاس ViewHolder برای نمایش لیست مدیاها
  • تکمیل نمودن آداپتر
  • تکمیل MainActivity
  • افزودن recyclerview ساخته شده به فایل layout مربوط به MainActivity
  • ست نمودن آداپتر با ریسایکرویو
  • اجرای پروژه

قدم اول : ساخت پروژه و ست کردن dependency ها

کتابخانه های لازم در این پروژه شامل موارد زیر می باشد که به فایل build.gradle اضافه می گردند:

  • RecyclerView
  • ExoPlayer
  • Glide – برای لود شدن تصویر اصلی مدیاها
  • Constraint Layout – پرفورمنس بهتر هنگام نمایش layout ها
// 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'

هم چنین برای فعال نمودن vector drawable در پروژه:

vectorDrawables.useSupportLibrary = true

قدم دوم : ساخت recyclerview با قابلیت سازگاری با کتابخانه exoplayer

بدیهی ست در این مرحله کلاسی extend شده از RecyclerView می سازیم. این همان مرحله ای ست که باید دو مشکل اصلی رفع شوند. برای حل مسئله اول قطعه کد زیر لازم است :

//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);
  }
}

برای نمایش آیتم مورد نظر هنگام scroll نمودن لیست قطعه کد زیر لازم می باشد :

/**
   * 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];
    }
  }

در نهایت کلاس ExoPlayerRecyclerView به شکل زیر خواهد بود :

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
  }
}

قدم سوم : ساخت آداپتر برای نگهداری آیتم های لیست

همیشه پیش از ساخت آداپتر باید کلاسی برای مدل یا entity مورد نظر بسازیم.

/**
 * 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;
  }
}

در ادامه layout مربوطه را برای نمایش آیتم های لیست آماده می سازیم :

<?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>

حالا می توانیم کلاس ViewHolder را برای نمایش آیتم های لیست بسازیم :

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;

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);
  }
}

با ساختن کلاس ViewHolder می توان به سراغ آداپتر رفته و آن را تکمیل نمود :

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;


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();
  }
}

قدم چهارم : تکمیل کلاس MainActivity

برای کامل شدن این کلاس ابتدا به سراغ layout مربوطه می رویم و ریسایکلرویو را به آن اضافه می کنیم:

<?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>

مورد دیگری که برای تکمیل MainActivity لازم است، ساخت کلاسی برای نمایش یک دمو از ویدئوهای موجود در لیست می باشد. به این منظور کلاسی همانند کلاس زیر می سازیم:

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);
  }

حالا به راحتی می توان به سراغ MainActivity رفت و آن را کامل نمود :

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);
  }
}

و به این ترتیب می توان کتابخانه ExoPlayer با recyclerView را آماده ساخت.

به این پست امتیاز دهید

روی ستاره های کلیک کنید و امتیاز بدید

میانگین امتیاز 1 / 5. تعداد: 1

از امتیاز شما متشکریم

2 دیدگاه در نوشته: “پیاده سازی exoplayer در recyclerView در اندروید

  1. اتا گفت:

    یه دنیا ممنون

    1. نسیم نژند گفت:

      امید داریم که مفید واقع شده باشه.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Enter Captcha Here : *

Reload Image