Android播放器拖动进度条的小图预览

news/2025/1/10 17:11:26/

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的回调函数onProgressChangedonStartTrackingTouchonStopTrackingTouch。触摸函数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);}});}
}

http://www.ppmy.cn/news/121877.html

相关文章

vue-admin-templae源码分析

1.源码结构: build下index.js为工程发布dist工具 mock为接口数据模拟服务器 node_modules为npm install 安装的nodejs依赖模块 public为静态资源 src不工程源码目录 tests为单元测试目录 开发与生产环境文件 vue.config.js为工程配置文件 登陆页面,登陆按钮点击事件调用完整流…

LLM:LLaMA模型和微调的Alpaca模型

待写 LLaMA模型 论文原文:https://arxiv.org/abs/2302.13971v1 预训练数据 模型架构 模型就是用的transformer的decoder,所以在结构上它与GPT是非常类似的,只是有一些细节需要注意一下。 1、RMS Pre-Norm 2、SwiGLU激活函数 3、RoPE旋转位置编码 Alpaca模型 [Stanford …

Pandas+ChatGPT强强结合诞生PandasAI,数据分析师行业要变天了?

大家好&#xff0c;我是千与千寻&#xff0c;可以叫我千寻&#xff0c;我自己主要的编程语言是Python和Java。 说到Python编程语言&#xff0c;使用Python语言主要使用的是数据科学领域的从业者。 Python编程语言之所以在数据科学领域十分火热&#xff0c;源于Python语言的三…

防雷箱也可以是智能的——同为(TOWE)科技智能防雷箱系列产品

雷电灾害被我国国防电工委列为“电子时代的一大公害”&#xff0c;我国由于幅员辽阔&#xff0c;地处温带和亚热带地区&#xff0c;全国雷电多发的4个区分别为南方区、高原区、北方区和新疆区。据统计&#xff0c;在我国&#xff0c;每年因雷击造成的人员伤亡近万人&#xff0c…

智慧防雷+智能防雷安全检测系统

雷电是由雷云&#xff08;带电的云层&#xff09;对地面建筑物及大地放电引发的自然天气现象。如果缺乏有效的雷电防护措施&#xff0c;雷电会给人们的生产、生活带来严重的危害。雷电可以击毁建筑物&#xff0c;破坏供配电系统、通信设备&#xff0c;造成计算机信息系统中断&a…

聊聊太阳能光伏发电系统的防雷设计

随着科学技术水平的日益进步&#xff0c;人类对能源的需求量越来越大。但是&#xff0c;能源与环境问题始终是摆在人类面前最具挑战性的难题。近些年&#xff0c;太阳能、风能、水能、地热能等再生能源发展迅速&#xff0c;成为可持续发展的新途径。我国太阳能资源十分丰富&…

安防监控防雷+防雷工程综合应用解决方案

现代的安防控制设备均系微电子化产品&#xff0c;这些控制设备具有高密度、高速度、低电压和低功耗等特性。因安防控制电子设备的精密&#xff0c;耐过电压能力下降&#xff0c;其对各种诸如雷电过电压、电力系统操作过电压、静电放电、电磁辐射等电磁干扰非常敏感&#xff0c;…

强承载力,防雷抗浪涌,同为(TOWE)大功率数据中心机房PDU插排测评

供电系统是整个数据中心机房的动力来源&#xff0c;扮演着“心脏和大动脉”的重要角色&#xff0c;负责将能量输送到机房每一台用电设备&#xff0c;其稳定和安全关系到数据中心服务器集中式收集、存储、处理数据、发送信息等相关服务和核心业务正常运行。一旦机房的供配电系统…