Android SeekBar控制视频播放进度(二)——seekTo()不准确

Android SeekBar控制视频播放进度二——seekTo不准确

简介

上一篇文章中,我们介绍了使用SeekBar控制视频播放,使用过程中发现,对于一些视频,我们拖动SeekBar进度条调节播放进度时,调节到指定位置后,进度条会往回跳,并不会在我们拖动位置继续播放。
在这里插入图片描述

网上搜索了解到,VideoView.seekTo()方法的策略决定的。具体看一下seekTo()方法:

seekTo()

  1. 如下是VideoView.seekTo(int msec)的代码实现,我们就是通过调用该方法实现进度调节。通过查看代码,我们知道该方法实际调用的是 MediaPlayer.seekTo(msec);
@Override
public void seekTo(int msec) {
    if (isInPlaybackState()) {
        mMediaPlayer.seekTo(msec);
        mSeekWhenPrepared = 0;
    } else {
        mSeekWhenPrepared = msec;
    }
}
  1. 继续查看 MediaPlayer.seekTo(msec);方法的实现,该方法调用seekTo(long msec, @SeekMode int mode)方法,默认的modeSEEK_PREVIOUS_SYNC
/**
 * 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 */);
}

/**
 * Moves the media to specified time position by considering the given mode.
 * <p>
 * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user.
 * There is at most one active seekTo processed at any time. If there is a to-be-completed
 * seekTo, new seekTo requests will be queued in such a way that only the last request
 * is kept. When current seekTo is completed, the queued request will be processed if
 * that request is different from just-finished seekTo operation, i.e., the requested
 * position or mode is different.
 *
 * @param msec the offset in milliseconds from the start to seek to.
 * When seeking to the given time position, there is no guarantee that the data source
 * has a frame located at the position. When this happens, a frame nearby will be rendered.
 * If msec is negative, time position zero will be used.
 * If msec is larger than duration, duration will be used.
 * @param mode the mode indicating where exactly to seek to.
 * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame
 * that has a timestamp earlier than or the same as msec. Use
 * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame
 * that has a timestamp later than or the same as msec. Use
 * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame
 * that has a timestamp closest to or the same as msec. Use
 * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may
 * or may not be a sync frame but is closest to or the same as msec.
 * {@link #SEEK_CLOSEST} often has larger performance overhead compared
 * to the other options if there is no sync frame located at msec.
 * @throws IllegalStateException if the internal player engine has not been
 * initialized
 * @throws IllegalArgumentException if the mode is invalid.
 */
public void seekTo(long msec, @SeekMode int mode) {
    if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
        final String msg = "Illegal seek mode: " + mode;
        throw new IllegalArgumentException(msg);
    }
    // TODO: pass long to native, instead of truncating here.
    if (msec > Integer.MAX_VALUE) {
        Log.w(TAG, "seekTo offset " + msec + " is too large, cap to " + Integer.MAX_VALUE);
        msec = Integer.MAX_VALUE;
    } else if (msec < Integer.MIN_VALUE) {
        Log.w(TAG, "seekTo offset " + msec + " is too small, cap to " + Integer.MIN_VALUE);
        msec = Integer.MIN_VALUE;
    }
    _seekTo(msec, mode);
}
  1. SeekMode 有如下几种模式,

SEEK_PREVIOUS_SYNC: seek到上一个关键帧
SEEK_NEXT_SYNC: seek到下一个关键帧
SEEK_CLOSEST_SYNC: seek到最近的关键帧
SEEK_CLOSEST: seek到最近的帧(不需要是关键帧)

    /**
     * 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;
    /**
     * 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 after or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_NEXT_SYNC        = 0x01;
    /**
     * 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
     * closest to (in time) or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_CLOSEST_SYNC     = 0x02;
    /**
     * This mode is used with {@link #seekTo(long, int)} to move media position to
     * a frame (not necessarily a key frame) associated with a data source that
     * is located closest to or at the given time.
     *
     * @see #seekTo(long, int)
     */
    public static final int SEEK_CLOSEST          = 0x03;

  1. 所以当视频在跳转到相应的 position 位置缺少关键帧的情况下,调用 seekTo 方法是无法在当前位置开始播放。这时会寻找离指定 position 最近的关键帧位置开始播放。
    我们通过seekTo函数调用的实际是默认的mode——SEEK_PREVIOUS_SYNC ,这时会寻找position的上一个关键帧。所以调节视频进度后,视频会往回跳一段,并没有在我们拖动位置继续播放。

视频帧 和 视频关键帧

上面的方法提到了帧和关键帧,下面我们简单的介绍一下两者的关联和区别。我们知道视频是由一帧一帧的图像组成的,而关键帧则是其中的某些帧。理想情况下,我们将所有的普通帧都变为关键帧,那么调节视频播放进度时将不会发生回跳情况。
在这里插入图片描述

解决办法

方法一

根据SeekMode 几种模式的描述,调用时指定modeSEEK_CLOSEST

方法二

对视频源文件进行处理,增加其关键帧数量。使用FFmpeg对视频处理,增加视频的关键帧。

  1. 首先我们通过如下命令查看当前视频中关键帧的数量:
ffprobe -show_frames /Users/Admin/Desktop/test.mp4 >video_log.txt

将视频信息输出到文本文件中,打开video_log.txt文件,搜索关键字pict_type=I查看关键帧。可以看到我们当前视频只有11个关键帧。
在这里插入图片描述
2. 对视频增加关键帧,keyint=30每隔 30 帧设置一个关键帧。命令如下:

ffmpeg.exe -i "/Users/Admin/Desktop/test.mp4" -c:v libx264 -preset superfast -x264opts keyint=30 -acodec copy -f mp4 "/Users/Admin/Desktop/test_out.mp4"

使用步骤1的命令,查看处理后的视频信息。可以看到处理后,我们视频的关键帧数量有99个。
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要在 Android SeekBar 中实时更新视频进度,你需要在代码中实现以下步骤: 1. 初始化 SeekBar 和 MediaPlayer。 2. 在 SeekBar 上设置 OnSeekBarChangeListener,以监听拖动事件。 3. 在拖动事件中,更新 MediaPlayer 的当前位置。 4. 在 MediaPlayer 中设置 OnPreparedListener,以获取视频的总长度。 5. 使用 Handler 定期更新 SeekBar进度,以反映当前视频进度。 下面是一些示例代码,用于演示如何在 Android SeekBar 中实时更新视频进度: ``` // 初始化 SeekBar 和 MediaPlayer SeekBar seekBar = findViewById(R.id.seek_bar); MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.video); // 监听 SeekBar 的拖动事件 seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { // 如果是用户拖动 SeekBar,更新 MediaPlayer 的当前位置 mediaPlayer.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) {} }); // 获取视频的总长度 mediaPlayer.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { int duration = mediaPlayer.getDuration(); seekBar.setMax(duration); } }); // 定期更新 SeekBar进度 final Handler handler = new Handler(); Runnable runnable = new Runnable() { @Override public void run() { int currentPosition = mediaPlayer.getCurrentPosition(); seekBar.setProgress(currentPosition); handler.postDelayed(this, 1000); } }; handler.postDelayed(runnable, 1000); ``` 上面的代码中,我们使用了 Handler 定期更新 SeekBar进度,间隔为 1 秒钟。在实际应用中,你可以根据需要调整更新的频率。另外,由于视频播放需要消耗大量的资源,建议在退出页面时及时释放 MediaPlayer。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值