Play Video in RecyclerView - Play/Pause video when scrolled off screen
Solution 1
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.
Visual Result:
Solution 2
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.
DJ-DOO
Updated on July 17, 2022Comments
-
DJ-DOO almost 2 years
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>