11

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>
Community
  • 1
  • 1
DJ-DOO
  • 4,545
  • 15
  • 58
  • 98
  • Hi..I have getting same problem to play video in recyclerview. so can you please provide me source code to play video in recyclerview? – Alpha Aug 16 '16 at 05:39
  • 1
    @Alpha I think you would gain a whole lot more knowledge if you tried to figure it out for yourself instead of looking for source code to fix your problem – DJ-DOO Aug 16 '16 at 10:20

4 Answers4

16

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:

enter image description here

aminography
  • 21,986
  • 13
  • 70
  • 74
  • 1
    A piece of code would have added more value to the answer, but anyways this answer showed me the hint. ✌️ – skWyz Sep 15 '20 at 18:58
1

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.

Adam Fręśko
  • 1,064
  • 9
  • 14
0

Have a look at the documentation here.
In the method

onViewRecycled(RecyclerView.ViewHolder holder)

you can get the time your video was playing at, and later on when you'll get the same video just set the player time with the saved value

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
gropapa
  • 577
  • 6
  • 10
  • I've added the recyclerlistener to the recycler view by onViewRecycled is never called...Any thoughts? – DJ-DOO Aug 11 '15 at 09:57
  • can you put your sample here, because just watching your previous code, it looks good to me...so i need a bit more – gropapa Aug 12 '15 at 10:41
  • Sorry, I had fixed the view cache size to the recycler view, this is why they weren't being recycled. Can I ask, I could potentially have 1000s of items in my recycler view and I'm wondering how I can ensure it redraws the views correctly. – DJ-DOO Aug 13 '15 at 14:13
  • Honestly i don't know why you would want such a big cache, don't forget a phone is a constrained environment so there s a chance having a 1000 displayed objetcts on the screen will have a bad impact regarding the performances – gropapa Aug 14 '15 at 14:19
  • Sorry 1000's may be a bit of exaggeration..but I will have a lot of items in the recycler view as I'm building a type of social media feed... – DJ-DOO Aug 17 '15 at 10:13
  • Did you end up figuring anything out for this? I am facing a similar issue with videos in RecyclerView. – Michael Garner Jan 05 '16 at 17:41
  • @MichaelGarner The requirements had to be changed because there were huge issues with memory. The media player will take lot of memory. – DJ-DOO Apr 16 '16 at 15:24
0

Instead of using just String object, create class as below:

public class MyVideoObject {
    String url;
    int seek_position;
}

And as @gropapa mentioned, you can set seek_position in the onViewRecycled method. When the view is recreated, you can start playback from the seek_position stored in the object.

There is a library I wrote which will play videos when ViewHolder is visible and pause when it is partially visible. Currently it does not store seek_position for views which are recycled but it resumes videos which are not recycled(partially visible).

AutoPlayVideos: https://github.com/Krupen/AutoplayVideos

MashukKhan
  • 1,946
  • 1
  • 27
  • 46
KRUPEN GHETIYA
  • 211
  • 2
  • 6