Android播放器拖动进度条的小图预览
- 背景
- 效果图
- 关键代码
- 1. 获取指定位置的视频帧
- 2. 预览图的显示和隐藏
- 完整代码
- 1. xml布局文件`activity_video.xml`
- 2. Activity文件`VideoActivity.java`
背景
我们在使用一些播放器时,拖动进度条会有一个预览框,上一篇博客Android SeekBar控制视频播放进度(一)实现了拖动进度条调节播放进度的功能,今天我们继续完善上一篇博客的功能,增加小图预览功能。效果图如下:
效果图
关键代码
1. 获取指定位置的视频帧
MediaMetadataRetriever
是Android原生提供的获取音视频文件信息的一个类,我们可以通过这个类的相关方法获取一些基本信息,如视频时长、宽高、帧率、方向、某一帧的图片等。
进入界面时我们开启一个子线程,以1秒的时间间隔提前把视频中的图片截取好。拖动滑动条时,只需根据当前的位置,找到最近的已经提前截取好的某帧图片即可。
为了减少内存占用,可以把截取的预览图的分辨率设置的小一些。
// 指定视频源
mmr = new MediaMetadataRetriever();
mmr.setDataSource(this, Uri.parse("android.resource://"+ getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));mVideoView.setVideoURI(Uri.parse("android.resource://"+ getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));mVideoView.requestFocus();// 视频加载完成回调函数
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.setLooping(true);// 获取视频的长度MAX_PROGRESS = mVideoView.getDuration();mSeekBar.setMax((int) MAX_PROGRESS);// 计算按照1s截取预览图,一共有多少张预览图bitmaps = new Bitmap[(int) (MAX_PROGRESS / 1000) + 1];Log.i(TAG, "onCreate: " + MAX_PROGRESS + "," + curProgress + "," + bitmaps.length);// 提前截取预览图,1s的时间间隔截取,用于快进时显示getPreviewImage();// 开始线程,更新进度条的进度handler.postDelayed(runnable, 0);mVideoView.start();}
});private void getPreviewImage() {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < bitmaps.length - 1; i++) {if (refreshFlag) {// 获取当前快进帧图像的bitmap对象 单位是微秒// 压缩图片,减少内存占用bitmaps[i] = Bitmap.createScaledBitmap(mmr.getFrameAtTime(i * 1000 * 1000L,MediaMetadataRetriever.OPTION_PREVIOUS_SYNC),PREVIEW_IMG_WIDTH,PREVIEW_IMG_HEIGHT,true);Log.i(TAG, "run: " + bitmaps[i].getByteCount());}}}}).start();}
2. 预览图的显示和隐藏
监听SeekBar
的回调函数onProgressChanged
,onStartTrackingTouch
,onStopTrackingTouch
。触摸函数onStartTrackingTouch
触发时暂停视频播放,小窗预览图显示,onProgressChanged
调节进度过程中,不停的更新预览图信息,停止调节进度onStopTrackingTouch
时继续从当前位置播放视频,同时隐藏小窗预览图。
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {Log.i(TAG, "onProgressChanged: " + progress);if (isSeekBarProgress) {updateProgress(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {isSeekBarProgress = true;Log.i(TAG, "onStartTrackingTouch: " + mVideoView.isPlaying());if (mVideoView.isPlaying()) {mVideoView.pause();updatePreviewStatus(true);}}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {Log.i(TAG, "onStopTrackingTouch: ");int pro = seekBar.getProgress();mVideoView.seekTo(pro);if (!mVideoView.isPlaying()) {mVideoView.seekTo(pro);mVideoView.start();updatePreviewStatus(true);}isSeekBarProgress = false;}
});private void updateProgress(int pro) {curProgress = pro;if (curProgress >= MAX_PROGRESS) {curProgress = MAX_PROGRESS - 10;} else if (curProgress < 0) {curProgress = 0;}runOnUiThread(new Runnable() {@Overridepublic void run() {mCardView.setVisibility(View.VISIBLE);mImageViewPreview.setImageBitmap(bitmaps[(int) (curProgress / 1000)]);}});}private void updatePreviewStatus(final boolean b) {runOnUiThread(new Runnable() {@Overridepublic void run() {mCardView.setVisibility(b ? View.VISIBLE : View.INVISIBLE);}});
}
完整代码
1. xml布局文件activity_video.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000"android:keepScreenOn="true"tools:context=".activitys.VideoActivity"><VideoViewandroid:id="@+id/video_view"android:layout_width="wrap_content"android:layout_height="match_parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"/><SeekBarandroid:id="@+id/seekbar"android:layout_width="500dp"android:layout_height="25dp"android:background="@drawable/bg_rounded"android:layout_marginBottom="45dp"android:progressTint="#7FFFD4"android:thumbTint="#7FFFD4"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintBottom_toBottomOf="parent"/><!--<ImageViewandroid:id="@+id/iv_preview"android:layout_width="200dp"android:layout_height="180dp"android:visibility="invisible"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"/>--><androidx.cardview.widget.CardViewandroid:id="@+id/cardView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="10dp"app:cardCornerRadius="15dp"app:cardElevation="20dp"app:cardPreventCornerOverlap="true"app:cardUseCompatPadding="true"android:visibility="invisible"app:layout_constraintBottom_toTopOf="@id/seekbar"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"><ImageViewandroid:id="@+id/iv_preview"android:layout_width="192dp"android:layout_height="108dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"/></androidx.cardview.widget.CardView></androidx.constraintlayout.widget.ConstraintLayout>
2. Activity文件VideoActivity.java
public class VideoActivity extends AppCompatActivity {private static final String TAG = "VideoActivity";private VideoView mVideoView;private SeekBar mSeekBar;private ImageView mImageViewPreview;private CardView mCardView;private float curProgress;private float MAX_PROGRESS;private MediaMetadataRetriever mmr;private Bitmap[] bitmaps;private static final int PREVIEW_IMG_WIDTH = 192;private static final int PREVIEW_IMG_HEIGHT = 108;private boolean isSeekBarProgress = false;private Handler handler = new Handler();private Runnable runnable = new Runnable() {public void run() {if (mVideoView.isPlaying()) {if (!isSeekBarProgress) {int current = mVideoView.getCurrentPosition();mSeekBar.setProgress(current);}}handler.postDelayed(runnable, 100);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_video);mmr = new MediaMetadataRetriever();mmr.setDataSource(this, Uri.parse("android.resource://"+ getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));mSeekBar = findViewById(R.id.seekbar);mImageViewPreview = findViewById(R.id.iv_preview);mVideoView = findViewById(R.id.video_view);mCardView = findViewById(R.id.cardView);mVideoView.setVideoURI(Uri.parse("android.resource://"+ getApplicationContext().getPackageName() + "/" + R.raw.vid_bigbuckbunny));
// MediaController mediaController = new MediaController(this);
// mVideoView.setMediaController(mediaController);mVideoView.requestFocus();mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.setLooping(true);MAX_PROGRESS = mVideoView.getDuration();mSeekBar.setMax((int) MAX_PROGRESS);bitmaps = new Bitmap[(int) (MAX_PROGRESS / 1000) + 1];Log.i(TAG, "onCreate: " + MAX_PROGRESS + "," + curProgress + "," + bitmaps.length);// 提前截取预览图,1s的时间间隔截取,用于快进时显示getPreviewImage();// 开始线程,更新进度条的进度handler.postDelayed(runnable, 0);mVideoView.start();}});mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {curProgress = 0;mSeekBar.setProgress(0);}});mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {Log.i(TAG, "onProgressChanged: " + progress);if (isSeekBarProgress) {updateProgress(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {isSeekBarProgress = true;Log.i(TAG, "onStartTrackingTouch: " + mVideoView.isPlaying());if (mVideoView.isPlaying()) {mVideoView.pause();updatePreviewStatus(true);}}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {Log.i(TAG, "onStopTrackingTouch: ");int pro = seekBar.getProgress();mVideoView.seekTo(pro);if (!mVideoView.isPlaying()) {mVideoView.seekTo(pro);mVideoView.start();updatePreviewStatus(true);}isSeekBarProgress = false;}
});}private void getPreviewImage() {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < bitmaps.length - 1; i++) {if (refreshFlag) {// 获取当前快进帧图像的bitmap对象 单位是微秒// 压缩图片,减少内存占用bitmaps[i] = Bitmap.createScaledBitmap(mmr.getFrameAtTime(i * 1000 * 1000L,MediaMetadataRetriever.OPTION_PREVIOUS_SYNC),PREVIEW_IMG_WIDTH,PREVIEW_IMG_HEIGHT,true);Log.i(TAG, "run: " + bitmaps[i].getByteCount());}}}}).start();}@Overrideprotected void onResume() {super.onResume(); }@Overrideprotected void onPause() {super.onPause();}@Overrideprotected void onDestroy() {super.onDestroy();handler.removeCallbacks(runnable);mmr.release();mmr.close();}private void updateProgress(int pro) {curProgress = pro;if (curProgress >= MAX_PROGRESS) {curProgress = MAX_PROGRESS - 10;} else if (curProgress < 0) {curProgress = 0;}runOnUiThread(new Runnable() {@Overridepublic void run() {mCardView.setVisibility(View.VISIBLE);mImageViewPreview.setImageBitmap(bitmaps[(int) (curProgress / 1000)]);}});}private void updateSeekBarStatus(final boolean b) {runOnUiThread(new Runnable() {@Overridepublic void run() {mSeekBar.setPressed(b);}});}private void updatePreviewStatus(final boolean b) {runOnUiThread(new Runnable() {@Overridepublic void run() {mCardView.setVisibility(b ? View.VISIBLE : View.INVISIBLE);}});}
}