困惑:
最近项目里出现了一些视频,当我们使用系统自带的VideoView去播放的时候,暂停播放后再次播放,或者拖动进度条改变seekTo之后,都无法正确的回到预期位置重新开始。而且奇怪的是,它总会总相邻的固定的几个时间点开始播放。比如我把进度拖动到10-19秒,手一松开就自动从10秒开始播放。那具体原因是什么呢,我们今天就来一探究竟。
第一步,Google。
搜到的第一条结果:https://stackoverflow.com/questions/7869148/android-video-seekto-error
![](https://img.haomeiwen.com/i3914746/4719767cab3c3c46.png)
![](https://img.haomeiwen.com/i3914746/710d4965a2ae80a6.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
接下来给大家贴一下我有问题的视频帧信息
![](https://img.haomeiwen.com/i3914746/86ad7fb824bfe173.png)
30秒的视频才4个关键帧(还包括了0秒的第一个关键帧),难怪一直seekTo不正确。
通过ffmpeg的指令
ffmpeg -i input.mp4 -keyint_min 60 -g 60 -sc_threshold 0 -y output.mp4
对关键帧数量进行处理后得到
![](https://img.haomeiwen.com/i3914746/461b7f28341de87f.png)
经过我的测试,发现只要符合每隔2.5秒一个关键帧的规律,视频seekTo都可以正确播放。
当然,由于对ffmpeg的指令还不是特别熟悉,而且ffmpeg不同版本对命令对支持也不尽相同,这里声明一下,应该还有别的指令可以对关键帧进行转化,不必拘泥于我上面写的。
当然,作为前端开发人员也可以考虑和后端、UGC同学沟通,在视频源头解决此问题。毕竟无论是要解决在线播放seekTo不准,或是本地先转化再播放都是不太合理的,对吧?
网友评论