Воспроизведение видео в RecyclerView — воспроизведение/пауза видео при прокрутке за пределы экрана

Я реализовал представление Recycler для видеоплееров, реализованное с использованием представления текстуры и медиаплеера.

Если я прокручиваю список вниз, я могу щелкнуть элемент, и видео заиграет. Однако с представлением переработчика, как только представление исчезает с экрана, оно затем перерабатывается для повторного использования. Если я прокручиваю назад, все виды теперь пустые (черные).

Я хочу добавить функциональность, которая, когда пользователь прокручивает видео за пределы экрана, делает паузу и сохраняет ссылку на это видео, так что, если они прокручивают обратно к этому видео, оно будет воспроизводиться с этой точки.

Я проверил это, однако я не хочу загружать видео, Я просто хочу стримить. Я не ищу кого-то, кто сделает это за меня, я просто ищу некоторые указатели и надеюсь, что кто-то может поделиться своими знаниями об этом... Заранее спасибо

Вот что я сделал до сих пор:

ВИДЕОПРОИГРЫВАТЕЛЬ

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

АКТИВНОСТЬ/АДАПТЕР

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

     }
}

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>

comment
Привет .. У меня такая же проблема с воспроизведением видео в recyclerview. не могли бы вы предоставить мне исходный код для воспроизведения видео в recyclerview?   -  person Alpha    schedule 16.08.2016
comment
@Alpha Я думаю, вы бы получили гораздо больше знаний, если бы попытались разобраться в этом сами, вместо того, чтобы искать исходный код для решения своей проблемы.   -  person DJ-DOO    schedule 16.08.2016


Ответы (4)


Путем переопределения onViewAttachedToWindow(VH holder) и onViewDetachedFromWindow(VH holder) в вашем адаптере , вы можете получать уведомления, когда каждый элемент входит или выходит из видимой области RecyclerView. Таким образом, можно сохранить состояние элемента. Например, вы можете создать HashMap<Integer, Long> в классе адаптера, который содержит время последнего воспроизведения. Таким образом, мы должны приостановить видео и сохранить время воспроизведения видео в HashMap с позицией элемента в качестве ключа в onViewDetachedFromWindow. Потом в onViewAttachedToWindow восстановить его с карты и...

На основе документации:

onViewAttachedToWindow вызывается, когда представление, созданное этим адаптером, было присоединено к окну.

onViewDetachedFromWindow вызывается, когда представление, созданное этим адаптером, было отсоединено от его окна.


Визуальный результат:

введите описание изображения здесь

person aminography    schedule 12.01.2019
comment
Фрагмент кода добавил бы ответу больше ценности, но в любом случае этот ответ показал мне подсказку. ✌️ - person skWyz; 15.09.2020

Я бы подошел к этому, добавив visiblelistener в класс viewholder recyclers. Библиотека Facebook fresco использует аналогичную технику для своей реализации "ImageView".

Fresco имеет открытый исходный код, поэтому легко проверить, как это делается.

person Adam Fręśko    schedule 11.08.2015

Ознакомьтесь с документацией здесь.
В методе

onViewRecycled(RecyclerView.ViewHolder holder)

вы можете получить время воспроизведения вашего видео, а позже, когда вы получите то же видео, просто установите время проигрывателя с сохраненным значением

person gropapa    schedule 11.08.2015
comment
Я добавил recyclerlistener в представление recycler, onViewRecycled никогда не вызывается... Есть мысли? - person DJ-DOO; 11.08.2015
comment
можете ли вы разместить здесь свой образец, потому что, просто наблюдая за вашим предыдущим кодом, он выглядит хорошо для меня... поэтому мне нужно немного больше - person gropapa; 12.08.2015
comment
Извините, я исправил размер кеша представления для представления переработчика, поэтому они не перерабатывались. Могу я спросить, у меня потенциально может быть 1000 элементов в моем представлении переработчика, и мне интересно, как я могу убедиться, что он правильно перерисовывает представления. - person DJ-DOO; 13.08.2015
comment
Честно говоря, я не знаю, зачем вам нужен такой большой кеш, не забывайте, что телефон — это ограниченная среда, поэтому есть шанс, что 1000 отображаемых объектов на экране плохо повлияют на производительность. - person gropapa; 14.08.2015
comment
Извините, 1000 могут быть небольшим преувеличением... но у меня будет много элементов в представлении переработчика, поскольку я создаю канал для социальных сетей... - person DJ-DOO; 17.08.2015
comment
Вы в итоге что-нибудь для этого придумали? Я столкнулся с аналогичной проблемой с видео в RecyclerView. - person Michael Garner; 05.01.2016
comment
@MichaelGarner Требования пришлось изменить, потому что были огромные проблемы с памятью. Медиаплеер займет много памяти. - person DJ-DOO; 16.04.2016

Вместо того, чтобы использовать только объект String, создайте класс, как показано ниже:

public class MyVideoObject {
    String url;
    int seek_position;
}

И, как упоминал @gropapa, вы можете установить seek_position в методе onViewRecycled. Когда представление воссоздано, вы можете начать воспроизведение с seek_position, сохраненного в объекте.

Я написал библиотеку, которая будет воспроизводить видео, когда ViewHolder видно, и приостанавливать, когда оно частично видно. В настоящее время он не сохраняет seek_position для просмотров, которые были переработаны, но возобновляет видео, которые не были переработаны (частично видны).

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

person KRUPEN GHETIYA    schedule 21.02.2017