想着练习下学习下 ijkplayer ,但不知道做个啥,就想着做个今日头条类似的视频播放列表,当item滑出了可视区域就自动播放下一个视频,因为播放器需要opengl渲染,所以就需要glsurfaceview,最开始的思路就是每个item都有一个surfaceview然后新建一个IjkMediaPlayer.使用后发现还没加入播放等动作就已经卡顿的不得了。如下图:
音视频之模拟今日头条列表视频-LMLPHP
条状图就是手机开启了gpu呈现模式分析(在绿色横线以下就是每帧<=16ms)大于就是有些卡顿了,对于滚动界面来说就有必要优化了。

后来改换思路整个列表只使用一个MyVideoView(glsurfaceview+ijkplayer的整合)
然后通过addview到指定的item中,然后跟随滑动,就普通的addview,removeview这两个操作就已经要60ms左右,这对于一个滑动列表肯定也是不行的,不过相对之前已经有所进步了。

最后想干脆我也不addview进去了,直接新建一个MyVideoView,滑动的时候跟随着滑动,不addview也不用removeview,就简单的跟随滑动,发现要完全滑出了界面,就自动播放下一个item视频。

滑动效果如下:感觉已经能满足需求了(视频的播放暂停,进度条没加入进去,这个不会影响滑动卡顿问题,添加简单所以就没继续写了。)
音视频之模拟今日头条列表视频-LMLPHP
gif效果图:
音视频之模拟今日头条列表视频-LMLPHP

现在简单看看代码:
MyVideoView(是GlSurfaceView 和ijkPlayer的封装):

public void play(final String path) {
    new Thread() {
        @Override
        public void run() {
            super.run();
            synchronized (MyVideoView.class) {
                try {
                    playRelease();
                    player = new IjkMediaPlayer();
                    player.setDisplay(surfaceView.getHolder());
                    player.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    player.setDataSource(path);
                    player.prepareAsync();
                    player.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(IMediaPlayer iMediaPlayer) {
                            if (lis != null) lis.startSuccess();
                            iMediaPlayer.start();
                            Log.e("xhc", " start ...");
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }
    }.start();
}

将Ijkplayer的释放,新建,都是在子线程中处理的,为的就是不阻塞主线程。

下面来看看recycleview的滑动事件的监听把:

 if(currentPosition >= 0 && currentPosition < adapter.getItemCount()){
        /**
         * 1.正在播放的视频已经滑出去,a.自动播放下一条,autoPlay = true b.播放器停止autoPlay = false
         * 2.正在播放的视频还在可视区域 a.继续播放
         */
        if(currentPosition < linearLayoutManager.findFirstVisibleItemPosition() && autoPlay){
            //自动播放下一条
            lastPosition = currentPosition;
            currentPosition = linearLayoutManager.findFirstVisibleItemPosition();
            playVideo(currentPosition , listVB.get(currentPosition));
        }
        else if(currentPosition > linearLayoutManager.findLastVisibleItemPosition() && autoPlay){
            lastPosition = currentPosition;
            currentPosition = linearLayoutManager.findLastVisibleItemPosition();
            playVideo(currentPosition , listVB.get(currentPosition));
        }
        else if((currentPosition < linearLayoutManager.findFirstVisibleItemPosition() ||
                currentPosition > linearLayoutManager.findLastVisibleItemPosition()) && !autoPlay){
            //没有自动播放,如果超出了可视区域直接停止播放器
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    mv.playRelease();
                }
            }.start();
            return ;
        }
        contain.setTranslationY(linearLayoutManager.findViewByPosition(currentPosition).getY());
    }
}

基本逻辑就是
1.当前播放的视频的position是比当前可视界面的第一个item的position还小,那么就播放下一个
2.如果当前视频的item的position比可视界面的最后一个还大,就播放上条视频。

VideoBean vb = new VideoBean();
vb.setVideoPath("sdcard/FFmpeg/video_src/v1080.mp4");

VideoBean vb2 = new VideoBean();
vb2.setVideoPath("sdcard/FFmpeg/video_src/test.mp4");

VideoBean vb3 = new VideoBean();
vb3.setVideoPath("sdcard/FFmpeg/video_src/time.mp4");

VideoBean vb4 = new VideoBean();
vb4.setVideoPath("sdcard/FFmpeg/video_src/input.mp4");

VideoBean vb5 = new VideoBean();
vb5.setVideoPath("rtmp://58.200.131.2:1935/livetv/hunantv");

VideoBean vb6 = new VideoBean();
vb6.setVideoPath("rtmp://media3.sinovision.net:1935/live/livestream");

测试视频有本地一些mp4和一些rmtp网络流,因为rtmp的网络给予tcp并且自己也要握手,所以加载时间更久点。

视频的测试文件在项目中可看到

这里可以找到测试文件

源码地址

04-19 00:00