视频播放器VideoView

android原生的两个视频播放器MediaPlayer和VideoView,VideoView内部是封装MediaPlayer,我之前开发时用的是VideoView,用完之后第一感受是更建议直接使用MediaPlayer。写这篇文章主要就是为了记录VideoView一些坑。

一.VideoView常用方法

VideoView的方法算得上比较少。
setVideoURI和setVideoPath 是设置视频的路径。
setMediaController 是设置MediaController,也就是设置播放器的导航栏。
setOnPreparedListener 监听在媒体文件加载并准备就绪时的回调
setOnCompletionListener 监听播放完成后的回调
setOnErrorListener 监听错误的回调
setOnInfoListener 监听信息事件的回调
pause和start 是暂停和开始
getDuration 获取视频时长
getCurrentPosition 获取当前视频播放到的节点
seekTo 设置视频跳到某个节点
以上这些是比较能常用到的方法,还有一些其他方法用到的时候可以看源码,代码量不是很多,都是最终调用MediaPlayer的方法。

如果要播放一个视频,可以这样做

videoView = findViewById(......);
videoView.setVideoURI(uri);
videoView.setOnPreparedListener (......);
videoView.setOnCompletionListener (......);
videoView.setOnErrorListener (......);
videoView.setOnInfoListener (......);
videoView.start();

我觉得使用起来还是挺简单的,就没必要详细去说明,主要就是为了记录一下,碰到的问题。

二.使用时碰到的问题

1.getDuration 获取广告的时长为0

直接使用getDuration会获取不到广告的时长,要在setOnPreparedListener的回调用获取才能获取到广告的时长。

2.切换到后台再切回来无法正常播放

例如在播放到一半的时候返回桌面,再点击应用进入,会发现视频无法继续播放。这是因为切换出去的时候内存会被释放掉。所以需要在切换出去的时候记录播放的点,切换回来的时候接着播放。

    @Override
    protected void onPause() {
        super.onPause();
        stopProgress = videoView.getCurrentPosition();
        videoView.pause();
    }
    @Override
    protected void onResume() {
        super.onResume();
        videoView.resume();
    }

注意这里调用resume()是因为切出去的时候被释放掉了,所以要重新创建,看resume的源码就知道里面做了什么操作。
然后在监听setOnPreparedListener 的方法中再开始播放,因为这种情况会再次给这个监听一个回调。

            videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mediaPlayer = mp;
                        new Handler(Looper.getMainLooper()).post(new Runnable() {
                            @Override
                            public void run() {
                               videoView.seekTo((int) stopProgress);
                               videoView.start();
                            }
                        });
                    }
            });

注意,如果之前有获取过videoview的MediaPlayer,这里如果继续使用之前的会有问题,所以mediaPlayer = mp;重新再获取一次。

3.seekTo跳转播放点会有偏差

比如说你调用seekTo(20),结果播放的点不是从20开始,而且会从0开始。
我在网上寻找这个问题,发现有人说这个问题是因为seekTo不会跳到传入的点,而是接近它的关键帧(这个东西是不是叫关键帧,我也不清楚)。
于是我就按照这个方法去查找视频的关键帧来进行测试。
(1)用ffmpeg查看关键帧
知道有个工具叫ffmpeg,它能够获取到视频的信息,下载之后是这样

视频播放器VideoView_第1张图片

我是windows的
然后cmd输入命令查看

ffprobe -show_frames test.mp4 >test.txt

就会把这个test视频的信息打印到test.txt里面,然后搜索key_frame=1,可以大概找出下图这样的信息

视频播放器VideoView_第2张图片

像我这找到一个点是在19.08。 这时如果我调用seekTo(20),它实际会跳到19.08这个点,这个是实测过的。
(2)解决seekto的问题
上面只是一个扩展,扩展如何抓i帧,看归看,最终我们还是必须解决问题。我目前知道的解决方法是,在Api26以上android已经修复这个问题,不会再去找关键帧,比如说seekTo(20),就真的是在20播放,而不是19.08。至于低版本的,目前我也没有什么办法,就是说如果想要解决这个问题,完全可以换个框架,用三方的,但这就偏离了我这文章的主题,所以我这里是针对videoview的。
那个Api26高版本解决了,但是,这个解决了是用的MediaPlayer的方法

mediaPlayer.seekTo((int) stopProgress, MediaPlayer.SEEK_CLOSEST);

但是我针对的是videoview,我们都知道videoview是封装了mediaPlayer的操作,上面的方法是有两个参数的,但是即便在api28的版本上,videoview的seekTo也没有两个参数的方法,简单来说就是谷歌修复了里面的东西,但没有管封装的那层的代码,所以我们可以这样做,在 setOnPreparedListener方法中能获取到MediaPlayer对象

                        new Handler(Looper.getMainLooper()).post(new Runnable() {
                            @Override
                            public void run() {
                                if (Build.VERSION.SDK_INT >= 26) {
                                    // 高版本能直接暂停播放
                                    videoView.seekTo((int) stopProgress);
                                    mediaPlayer.seekTo((int) stopProgress, MediaPlayer.SEEK_CLOSEST);
                                }
                                videoView.start();
                            }
                        });

这里只对高版本做处理,低版本不管。

这篇文章就主要讲这些,主要是videoview使用挺简单的,没什么好讲,而坑确实挺多了,我就是想写篇文章记录下使用videoview遇到的坑,而如果是专门做视频肯定是不会使用videoview的,都是自己开发的控件。

你可能感兴趣的:(视频播放器VideoView)