Android 視頻滾動列表(偷懶型)

公司的項目需要一個視頻的滾動列表。
搜了些文章比較常見的是根據(jù)列表項的可視百分比來判斷的蕴掏。(然后發(fā)現(xiàn)N篇文章的實現(xiàn)貌似都是來源于這篇文章)實現(xiàn)起來略復雜。
這里想了一個在要求不高的情況下,實現(xiàn)相對簡便的方法:根據(jù)列表滾動時可見的第一個列表項的位置來播放和暫停對應列表項內的視頻盔沫。
它的效果大致是這樣的:

20171117_170043.gif

以下是它的實現(xiàn)彩掐。

首先當然是建立列表构舟。

這部分就直接用ListView吧,列表的具體的實現(xiàn)就不貼了堵幽。大致就是長這樣的一個列表:
Screenshot_2017-11-16-14-40-17-801_net.codepig.playerlist.png

接下來就是添加播放器旁壮。

這里需要注意的是,在ListView里不能使用我們常用的那種VideoView谐檀÷招常基于SurfaceView的VideoView由于沒有同步緩沖區(qū),它不能在ListView中正常顯示桐猬。(顯然SurfaceView+MediaPlayer的形式也不太適合了)我們需要基于TextureView的視頻播放器麦撵。
這里偷個懶,就直接用 PLDroidPlayer這個庫中的PLVideoTextureView了

在列表的Adapter中的添加播放器溃肪。

Adapter的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <RelativeLayout
            android:id="@+id/videoTable"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:src="@drawable/videoico"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
            <com.pili.pldroid.player.widget.PLVideoTextureView
                android:id="@+id/myVideoView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center" />
        </RelativeLayout>
        <TextView
            android:text="視頻名稱"
            android:id="@+id/videoName_t"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</LinearLayout>

Adapter部分代碼:

package net.codepig.playerlist.adapers;

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.pili.pldroid.player.AVOptions;
import com.pili.pldroid.player.PLMediaPlayer;
import com.pili.pldroid.player.widget.PLVideoTextureView;

import net.codepig.playerlist.R;
import net.codepig.playerlist.beans.VideoInfo;
import net.codepig.playerlist.deviceInfo;

import java.util.List;

/**
 * 視頻單元頁面
 * Created by QZD on 2017/11/13.
 */

public class PlayerAdapter extends BaseAdapter{
    private Context _context;
    private Activity mainActivity;
    private List<VideoInfo> myVideoData;
    private LayoutInflater inflater;
    private ViewHolder hodler = null;
    private PLMediaPlayer mPlayer=null;
//    private PLVideoTextureView myVideoView;
    private int _id;
    private String _name;
    private String _url="";

    private final String TAG="LOGCAT";

    public PlayerAdapter(Context context, List<VideoInfo> data) {
        super();
        _context = context;
        mainActivity=(Activity) context;
        myVideoData = data;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public View getView(final int postion, View convertView, ViewGroup parent) {
        hodler = new ViewHolder();
        convertView = inflater.inflate(R.layout.player_adapter_l, null);
        hodler.videoName_t = convertView.findViewById(R.id.videoName_t);
        hodler.videoTable = convertView.findViewById(R.id.videoTable);
        hodler.myVideoView = convertView.findViewById(R.id.myVideoView);
        convertView.setTag(hodler);

        hodler.videoTable.getLayoutParams().width= deviceInfo._screenWidth;
        hodler.videoTable.getLayoutParams().height=deviceInfo._screenHeight;
//        Log.d(TAG,"screenSize:"+deviceInfo._screenWidth+"-"+deviceInfo._screenHeight);

        VideoInfo _vInfo=myVideoData.get(postion);
        _name=_vInfo.get_name();
        hodler.videoName_t.setText(_vInfo.get_name());
        _id=_vInfo.get_id();
        _url=_vInfo.get_url();
        if(!_url.equals("")) {
            setVideo(_url);
        }

        return convertView;
    }

    /**
     * 初始化播放器
     * @param url
     */
    private void setVideo(String url){
        int codec = mainActivity.getIntent().getIntExtra("mediaCodec", AVOptions.MEDIA_CODEC_AUTO);
        AVOptions options = new AVOptions();
        options.setInteger(AVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000);
        options.setInteger(AVOptions.KEY_MEDIACODEC, codec);
        hodler.myVideoView.setAVOptions(options);
        hodler.myVideoView.setVideoPath(url);
        hodler.myVideoView.start();

        hodler.myVideoView.setOnErrorListener(new PLMediaPlayer.OnErrorListener(){
            @Override
            public boolean onError(PLMediaPlayer mp, int errorCode) {
                Log.d(TAG,"errorCode:"+errorCode);
                return true;
            }
        });
        hodler.myVideoView.setOnCompletionListener(new PLMediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(PLMediaPlayer mp) {
//                Log.d(TAG, "player onCompletion:"+videoDuration/1000+"-"+_curTime/1000);
                hodler.myVideoView.seekTo(0);
                hodler.myVideoView.start();
            }
        });
        hodler.myVideoView.setOnPreparedListener(new PLMediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(PLMediaPlayer mediaPlayer, int percent) {
                Log.d(TAG, "player onPrepared");
                if(mPlayer==null){
                    mPlayer=mediaPlayer;
                }
                //播放
                if(hodler.myVideoView!=null){
                    hodler.myVideoView.start();
                }else{
                    Log.d(TAG, _name+"no myVideoView");
                }
            }
        });
        hodler.myVideoView.setOnBufferingUpdateListener(new PLMediaPlayer.OnBufferingUpdateListener() {
            @Override
            public void onBufferingUpdate(PLMediaPlayer mp, int percent) {
                try {
                    int _pec = hodler.myVideoView.getBufferPercentage();//百分比到99就停免胃,進度條會留空
                    if (_pec == 99) {
                        _pec = 100;
                    }
                }catch (Exception e){
                    Log.d(TAG,"percentage error:"+e.toString());
                }
            }
        });
        hodler.myVideoView.setOnVideoSizeChangedListener(new PLMediaPlayer.OnVideoSizeChangedListener() {
            @Override
            public void onVideoSizeChanged(PLMediaPlayer plMediaPlayer, int width, int height) {
                Log.d(TAG,"VideoSize:"+width+"_"+height);
            }
        });
    }

    @Override
    public int getCount() {
        if (myVideoData != null) {
            return myVideoData.size();
        } else {
            return 0;
        }
    }

    @Override
    public Object getItem(int position) {
        return myVideoData.get(position);
    }

    @Override
    public long getItemId(int postion) {
        // TODO Auto-generated method stub
        return postion;
    }

    public static class ViewHolder {
        public TextView videoName_t;
        public RelativeLayout videoTable;
        public PLVideoTextureView myVideoView;
    }
}

添加完播放器大致長這樣:


Screenshot_2017-11-16-14-38-17-068_net.codepig.playerlist.png

接下來就是重點了,要根據(jù)列表的滾動來播放和暫停視頻惫撰。

這里根據(jù)當前滾動的位置來進行判斷羔沙。

首先添加滾動監(jiān)聽:
        myVideoList.setAdapter(playerAdapter);
        myVideoList.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
//                Log.d(TAG,"onScrollStateChanged:"+scrollState);
                //SCROLL_STATE_FLING = 滾動中;SCROLL_STATE_IDLE = 結束滾動厨钻;SCROLL_STATE_TOUCH_SCROLL = 開始滾動;
                if(scrollState==SCROLL_STATE_IDLE){
                    Log.d(TAG,"FirstVisiblePosition:"+myVideoList.getFirstVisiblePosition());
                    View v0=myVideoList.getChildAt(0);
                    if(v0!=null){
                        int scrollTop=v0.getTop();
                        Log.d(TAG,"scroll top:"+scrollTop);
                    }
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            }
        });

這里通過getFirstVisiblePosition()獲得可見的第一個元素扼雏,并使用getTop()獲得該元素的偏移量坚嗜。

接下來增加對元素內視頻的操作,這里通過更新列表的數(shù)據(jù)來實現(xiàn)诗充。

修改一下上面的監(jiān)聽苍蔬,判斷當前第二個可見item的位置,當?shù)竭_指定位置時將播放標識置為true蝴蜓。原先播放中的item的播放標識置為false碟绑。
然后更新數(shù)據(jù)。

         myVideoList.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                //SCROLL_STATE_FLING = 滾動中茎匠;SCROLL_STATE_IDLE = 結束滾動格仲;SCROLL_STATE_TOUCH_SCROLL = 開始滾動;
                if(scrollState==SCROLL_STATE_IDLE){
                    int _index=myVideoList.getFirstVisiblePosition()+1;
                    View v1=myVideoList.getChildAt(1);//取可見元素的第二個
                    if(v1!=null){
                        int scrollTop=v1.getTop();
                        if(scrollTop<200){
                            if(_oldItem!=_index) {
                                _infoList.get(_index).set_playing(true);
                                _infoList.get(_oldItem).set_playing(false);
                                _oldItem=_index;
                                playerAdapter.notifyDataSetChanged();
                            }
                        }
//                        Log.d(TAG,"scroll top:"+scrollTop);
                    }
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            }
        });

這個的位置判斷上直接寫死了200像素,作為一個DEMO诵冒,位置判斷的數(shù)值上不是很講究抓狭。這個其實應該根據(jù)滾動方向和item的高度來計算的。

在Adapter的getView()方法中根據(jù)_playing的狀態(tài)播放或停止視頻:(停止的時候要記得釋放掉播放器資源哦造烁,不然列表中這么多視頻的內存占用是很可怕的哦否过。)

    @Override
    public View getView(final int postion, View convertView, ViewGroup parent) {
        hodler = new ViewHolder();
        convertView = inflater.inflate(R.layout.player_adapter_l, null);
        hodler.videoName_t = convertView.findViewById(R.id.videoName_t);
        hodler.videoTable = convertView.findViewById(R.id.videoTable);
        hodler.myVideoView = convertView.findViewById(R.id.myVideoView);
        convertView.setTag(hodler);

        hodler.videoTable.getLayoutParams().width= deviceInfo._screenWidth;
        hodler.videoTable.getLayoutParams().height=deviceInfo._screenHeight;
        Log.d(TAG,"screenSize:"+deviceInfo._screenWidth+"-"+deviceInfo._screenHeight);

        VideoInfo _vInfo=myVideoData.get(postion);
        _name=_vInfo.get_name();
        hodler.videoName_t.setText(_vInfo.get_name());
        _id=_vInfo.get_id();
        _url=_vInfo.get_url();
        if(!_url.equals("")) {
        //視頻的播放和停止
            if(_vInfo.get_playing()){
                setVideo(_url);
            }else{
                if(hodler.myVideoView!=null) {
                    if (hodler.myVideoView.isPlaying()) {
                        hodler.myVideoView.stopPlayback();
                        hodler.myVideoView.releaseSurfactexture();
                    }
                }
            }
        }
        return convertView;
    }

嗯,完工惭蟋。完整代碼可以參看下面的github地址苗桂。
改天再整列表的可視百分比判斷。


相關github項目地址:https://github.com/codeqian/playerlist

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末告组,一起剝皮案震驚了整個濱河市煤伟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌木缝,老刑警劉巖便锨,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機双藕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吱殉,“玉大人,你說我怎么就攤上這事厘托∮仰ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵铅匹,是天一觀的道長押赊。 經(jīng)常有香客問我,道長包斑,這世上最難降的妖魔是什么流礁? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任涕俗,我火速辦了婚禮,結果婚禮上崇棠,老公的妹妹穿的比我還像新娘。我一直安慰自己丸卷,他們只是感情好枕稀,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谜嫉,像睡著了一般萎坷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沐兰,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天哆档,我揣著相機與錄音,去河邊找鬼住闯。 笑死瓜浸,一個胖子當著我的面吹牛,可吹牛的內容都是我干的比原。 我是一名探鬼主播插佛,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼量窘!你這毒婦竟也來了雇寇?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚌铜,失蹤者是張志新(化名)和其女友劉穎锨侯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冬殃,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡囚痴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了审葬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渡讼。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耳璧,靈堂內的尸體忽然破棺而出成箫,到底是詐尸還是另有隱情,我是刑警寧澤旨枯,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布蹬昌,位于F島的核電站,受9級特大地震影響攀隔,放射性物質發(fā)生泄漏皂贩。R本人自食惡果不足惜栖榨,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望明刷。 院中可真熱鬧婴栽,春花似錦、人聲如沸辈末。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挤聘。三九已至轰枝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間组去,已是汗流浹背鞍陨。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留从隆,地道東北人诚撵。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像键闺,于是被迫代替她去往敵國和親砾脑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內容