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() {
@Override
public 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() {
@Override
public 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() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.i(TAG, "onProgressChanged: " + progress);
if (isSeekBarProgress) {
updateProgress(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
isSeekBarProgress = true;
Log.i(TAG, "onStartTrackingTouch: " + mVideoView.isPlaying());
if (mVideoView.isPlaying()) {
mVideoView.pause();
updatePreviewStatus(true);
}
}
@Override
public 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() {
@Override
public void run() {
mCardView.setVisibility(View.VISIBLE);
mImageViewPreview.setImageBitmap(bitmaps[(int) (curProgress / 1000)]);
}
});
}
private void updatePreviewStatus(final boolean b) {
runOnUiThread(new Runnable() {
@Override
public 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">
<VideoView
android: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"/>
<SeekBar
android: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"/>
<!--<ImageView
android: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.CardView
android: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">
<ImageView
android: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);
}
};
@Override
protected 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() {
@Override
public 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() {
@Override
public void onCompletion(MediaPlayer mp) {
curProgress = 0;
mSeekBar.setProgress(0);
}
});
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.i(TAG, "onProgressChanged: " + progress);
if (isSeekBarProgress) {
updateProgress(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
isSeekBarProgress = true;
Log.i(TAG, "onStartTrackingTouch: " + mVideoView.isPlaying());
if (mVideoView.isPlaying()) {
mVideoView.pause();
updatePreviewStatus(true);
}
}
@Override
public 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() {
@Override
public 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();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected 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() {
@Override
public void run() {
mCardView.setVisibility(View.VISIBLE);
mImageViewPreview.setImageBitmap(bitmaps[(int) (curProgress / 1000)]);
}
});
}
private void updateSeekBarStatus(final boolean b) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mSeekBar.setPressed(b);
}
});
}
private void updatePreviewStatus(final boolean b) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mCardView.setVisibility(b ? View.VISIBLE : View.INVISIBLE);
}
});
}
}