美文网首页
Android-VideoView.seekTo() 不正确问题

Android-VideoView.seekTo() 不正确问题

作者: 晖仔Milo | 来源:发表于2020-11-10 17:55 被阅读0次

困惑:
最近项目里出现了一些视频,当我们使用系统自带的VideoView去播放的时候,暂停播放后再次播放,或者拖动进度条改变seekTo之后,都无法正确的回到预期位置重新开始。而且奇怪的是,它总会总相邻的固定的几个时间点开始播放。比如我把进度拖动到10-19秒,手一松开就自动从10秒开始播放。那具体原因是什么呢,我们今天就来一探究竟。

第一步,Google。
搜到的第一条结果:https://stackoverflow.com/questions/7869148/android-video-seekto-error

QQ20201110-172456@2x.png QQ20201110-172514@2x.png

大致意思就是要从 VideoView 的 MediaPlayer 的 onSeekComplete() 入手,把VideoView.start() 放在它之后

// 设置 VideoView 的 OnPrepared 监听,拿到 MediaPlayer 对象。
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
            //设置 MediaPlayer 的 OnSeekComplete 监听
                mp.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
                    @Override
                    public void onSeekComplete(MediaPlayer mp) {
                    // seekTo 方法完成时的回调
                        if(isPause){
                            videoView.start();
                            isPause = false;
                        }
                    }
                });
            }
        });

第二步
改完之后发现问题还是存在,奇怪了,why?
原因有二:
1、上述操作是针对边下边播、或者来源于网络端的视频的,而楼主遇到的视频都是已经下好在本地的,所以药不对口;
2、真正的原因还是因为视频的关键帧不足,我们看一下VideoView.seekTo()的源码。

    @Override
    public void seekTo(int msec) {
        if (isInPlaybackState()) {
            mMediaPlayer.seekTo(msec);
            mSeekWhenPrepared = 0;
        } else {
            mSeekWhenPrepared = msec;
        }
    }
    /**
     * Seeks to specified time position.
     * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}.
     *
     * @param msec the offset in milliseconds from the start to seek to
     * @throws IllegalStateException if the internal player engine has not been
     * initialized
     */
    public void seekTo(int msec) throws IllegalStateException {
        seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */);
    }
    /**
     * Seek modes used in method seekTo(long, int) to move media position
     * to a specified location.
     *
     * Do not change these mode values without updating their counterparts
     * in include/media/IMediaSource.h!
     */
    /**
     * This mode is used with {@link #seekTo(long, int)} to move media position to
     * a sync (or key) frame associated with a data source that is located
     * right before or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_PREVIOUS_SYNC    = 0x00;
seekTo 默认使用了 SEEK_PREVIOUS_SYNC 作为 SeekMode,而SEEK_PREVIOUS_SYNC 的作用就是将位置移动到关键帧。

好了,明确了原因,我们就通过 ffmpeg 的指令来验证这一猜想吧。
首先,确保你电脑上编译了ffmpeg,然后在控制台输入查看帧信息的指令
ffprobe -show_frames -select_streams video input.mp4

接下来给大家贴一下我有问题的视频帧信息


QQ20201110-174736@2x.png

30秒的视频才4个关键帧(还包括了0秒的第一个关键帧),难怪一直seekTo不正确。

通过ffmpeg的指令

ffmpeg -i input.mp4 -keyint_min 60 -g 60 -sc_threshold 0 -y output.mp4

对关键帧数量进行处理后得到


QQ20201110-175224@2x.png

经过我的测试,发现只要符合每隔2.5秒一个关键帧的规律,视频seekTo都可以正确播放。
当然,由于对ffmpeg的指令还不是特别熟悉,而且ffmpeg不同版本对命令对支持也不尽相同,这里声明一下,应该还有别的指令可以对关键帧进行转化,不必拘泥于我上面写的。

当然,作为前端开发人员也可以考虑和后端、UGC同学沟通,在视频源头解决此问题。毕竟无论是要解决在线播放seekTo不准,或是本地先转化再播放都是不太合理的,对吧?

相关文章

网友评论

      本文标题:Android-VideoView.seekTo() 不正确问题

      本文链接:https://www.haomeiwen.com/subject/ctvjbktx.html