I've implemented a Recycler view of video players implemented using a texture view and media player.
If I scroll down through the list I can click on the item and the video plays. However, with the recycler view once the view goes off screen it is then recycled for reuse. If I scroll back up all the views are now blank (black).
I am looking to add functionality that when the user scrolls the video off screen that it will pause and keep reference to that video so that if they scroll back to that video it will play from that point.
I have checked this out however I don't want to download the video, I just want to stream. I am not looking for someone to do this for me, I'm just looking for some pointers and hoping someone could share their knowledge on this... Thanks in advance
This is what I have done so far:
VIDEO PLAYER
public class CustomVideoPlayer implements TextureView.SurfaceTextureListener, VideoControllerView.MediaPlayerControl, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnVideoSizeChangedListener {
private Context mContext;
private String mUrl;
private MediaPlayer mMediaPlayer;
private Surface mSurface;
private VideoControllerView mControllerView;
private TextureView mTextureView;
private CardView mCardView;
private ProgressBar mProgress;
private FrameLayout mView;
private RelativeLayout mLayout;
public CustomVideoPlayer(Context ctx, TextureView view, ProgressBar progressDialog, FrameLayout holderView){
this.mContext = ctx;
mTextureView = view;
mTextureView.setSurfaceTextureListener(this);
mProgress = progressDialog;
mControllerView = new VideoControllerView(ctx);
mView = holderView;
mTextureView.setOnTouchListener(new ControlTouchListener());
}
@Override
public boolean canPause() {
return true;
}
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
@Override
public int getBufferPercentage() {
return 0;
}
@Override
public int getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}
@Override
public int getDuration() {
return mMediaPlayer.getDuration();
}
@Override
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}
@Override
public void pause() {
mMediaPlayer.pause();
}
@Override
public void seekTo(int i) {
mMediaPlayer.seekTo(i);
}
@Override
public void start() {
mMediaPlayer.start();
}
@Override
public boolean isFullScreen() {
return false;
}
@Override
public void toggleFullScreen() {
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
}
@Override
public void onCompletion(MediaPlayer mp) {
}
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mSurface = new Surface(surface);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
public void changePlayState(){
if(mMediaPlayer.isPlaying()){
mMediaPlayer.pause();
}else{
mMediaPlayer.start();
}
}
public void startVideo(String url){
if(mMediaPlayer!=null){
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = new MediaPlayer();
}else{
mMediaPlayer = new MediaPlayer();
}
if(!mMediaPlayer.isPlaying()){
try {
mMediaPlayer.setSurface(mSurface);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(url);
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnBufferingUpdateListener(this);
mMediaPlayer.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
mMediaPlayer.setOnPreparedListener(this);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onPrepared(MediaPlayer mp) {
Log.i(VersysVideoPlayer.class.getSimpleName(), "ON PREPARED CALLED");
mControllerView.setMediaPlayer(this);
mControllerView.setAnchorView(mView);
mControllerView.show();
mProgress.setVisibility(View.GONE);
mMediaPlayer.start();
}
//Touch listener to display video controls
class ControlTouchListener implements View.OnTouchListener{
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
mControllerView.show();
}
return false;
}
}
}
ACTIVITY/ADAPTER
public class VideoViewListActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_view_list);
//Create instance of Recycler view
final RecyclerView videoList = (RecyclerView) findViewById(R.id.feed_list);
LinearLayoutManager llm = new LinearLayoutManager(this);
videoList.setLayoutManager(llm);
videoList.setHasFixedSize(true);
final List<String> list = new ArrayList<>();
list.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
list.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
list.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
list.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
list.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
list.add("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
final VideoAdapter adapter = new VideoAdapter(list, this);
videoList.setAdapter(adapter);
videoList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
TextureView view = adapter.getVideoPlayer();
Log.i("PERCENTAGE VISIBLE: ", String.valueOf(getVisiblePercent(adapter.getVideoPlayer())));
if(getVisiblePercent(view)==100) {
return;
}
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_video_view_list, menu);
return true;
}
public static int getVisiblePercent(View v) {
if (v.isShown()) {
Rect r = new Rect();
v.getGlobalVisibleRect(r);
double sVisible = r.width() * r.height();
double sTotal = v.getWidth() * v.getHeight();
return (int) (100 * sVisible / sTotal);
} else {
return -1;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Recycler View Adapter
*/
class VideoAdapter extends RecyclerView.Adapter<VideoAdapter.VideoFeedHolder> {
public TextureView mPreview;
private CardView mCardView;
private List<String> mUrls;
private Context mContext;
private Surface mSurface;
VideoControllerView controller;
private View mAnchor;
private ProgressBar mProgressDialog;
private ImageView mHolder;
private int mPosition;
private VersysVideoPlayer mVideoPlayer;
OnItemClickListener mItemClickListener;
public VideoAdapter(List<String> url, Context ctx) {
mUrls = url;
mContext = ctx;
controller = new VideoControllerView(ctx);
}
@Override
public VideoAdapter.VideoFeedHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.video_item_feed, viewGroup, false);
VideoFeedHolder holder = new VideoFeedHolder(v);
return holder;
}
@Override
public void onBindViewHolder(final VideoFeedHolder videoFeedHolder, final int i) {
final VersysVideoPlayer videoPlayer = new VersysVideoPlayer(mContext, videoFeedHolder.mTexturePreview, mProgressDialog, videoFeedHolder.controlHolder);
videoFeedHolder.placeholder.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
videoPlayer.startVideo(mUrls.get(i));
videoFeedHolder.placeholder.setVisibility(View.GONE);
videoFeedHolder.bar.setVisibility(View.VISIBLE);
}
});
mPosition = i;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
public String getUrl() {
return mUrls.get(mPosition);
}
@Override
public int getItemCount() {
return mUrls.size();
}
public TextureView getVideoPlayer() {
return mPreview;
}
public class VideoFeedHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextureView mTexturePreview;
ProgressBar bar;
ImageView placeholder;
FrameLayout controlHolder;
RelativeLayout touchLayout;
public VideoFeedHolder(View itemView) {
super(itemView);
mTexturePreview = (TextureView) itemView.findViewById(R.id.video_player);
mPreview = mTexturePreview;
mCardView = (CardView) itemView.findViewById(R.id.cv);
bar = (ProgressBar)itemView.findViewById(R.id.buffereing);
placeholder = (ImageView) itemView.findViewById(R.id.holder);
mProgressDialog = bar;
controlHolder = (FrameLayout) itemView.findViewById(R.id.media_controller_anchor);
}
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onItemClick(v, getAdapterPosition());
}
}
}
}
}
VIDEO FEED ITEM XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:layout_margin="10dp"
android:layout_width="match_parent"
android:layout_height="400dp"
android:id="@+id/cv">
<RelativeLayout
android:id="@+id/anchor"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/detail_layout"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/profile_pic"
android:background="@drawable/profiler"
android:layout_marginLeft="10dp"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/user_name"
android:layout_alignTop="@+id/profile_pic"
android:layout_toRightOf="@+id/profile_pic"
android:text="Joe Bloggs"
android:layout_marginLeft="10dp"
android:textColor="#000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/date"
android:layout_below="@+id/user_name"
android:layout_toRightOf="@+id/profile_pic"
android:layout_marginLeft="10dp"
android:text="10 Aug 2015"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/desc"
android:layout_below="@+id/profile_pic"
android:layout_marginLeft="10dp"
android:text="This a sample video of a bird getting hit on the head and a rabbit waking from a nap!!"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<RelativeLayout
android:layout_below="@+id/detail_layout"
android:layout_margin="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextureView
android:id="@+id/video_player"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/media_controller_anchor"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</FrameLayout>
<ImageView
android:id="@+id/holder"
android:layout_centerInParent="true"
android:background="@drawable/default_video_poster"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/buffereing"
android:visibility="gone"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>
By overridding onViewAttachedToWindow(VH holder)
and onViewDetachedFromWindow(VH holder)
in your adapter, you can get notified when each item enters or exits from the visible area of RecyclerView
. So it is possible to save the item state. For example you can create a HashMap<Integer, Long>
in adapter class which holds the last time of playing. In this way, we should pause the video and store the video playing time in the HashMap
with item position as key, in onViewDetachedFromWindow
. Then in onViewAttachedToWindow
restore it from the map and ...
Based on documentation:
onViewAttachedToWindow
is called when a view created by this adapter has been attached to a window.
onViewDetachedFromWindow
is called when a view created by this adapter has been detached from its window.
I would approach this by adding visibilitylistener to recyclers viewholder class. Facebook fresco library uses similar technique for its "ImageView" implementation.
Fresco is open source so its simple to checkout how its done.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With