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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/24514.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker容器 和 Kubernetes容器集群管理系统

一、快速了解Docker 1. 什么是Docker的定义 Docker 是一个开源的应用容器引擎&#xff0c;基于Go语言并遵从 Apache2.0 协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以…

javaScript 给图片加水印

背景 在很多地方&#xff0c;我们都可以看到&#xff0c;上传图片的时候&#xff0c;图片都会被加上默认的水印&#xff0c;水印的作用主要体现在以下几个方面&#xff1a; 1.版权保护&#xff1a;在商业用途的照片中添加水印可以帮助保护作者的版权&#xff0c;防止他人未经…

IOS复杂震动AHAP文件编辑指南

简介 目前部分游戏会在播放一些特定的音乐音效时&#xff0c;令设备产生贴合音效的复杂震动&#xff0c;给玩家一个更好的游戏体验。这种复杂震动就是通过苹果的CoreHaptics库实现的。 下面是关于CoreHaptics的官方文档 ​​​​​​​Core Haptics | Apple Developer Docum…

C++ Qt项目实战:构建高效的代码管理器

C Qt项目实战&#xff1a;构建高效的代码管理器 一、项目概述&#xff08;Introduction&#xff09;1.1 项目背景&#xff08;Project Background&#xff09;1.2 项目目标&#xff08;Project Goals&#xff09;1.3 项目应用场景&#xff08;Project Application Scenarios&am…

《操作系统》期末主观题梳理

操作系统简答题 文章目录 操作系统简答题第一章第二章第三章第四章第五章第六章第七章第八章第九章 第一章 在计算机系统上配置OS(operating system, 操作系统)的目标是什么?作用主要表现在哪几个方面? 在计算机系统上配置OS, 主要目标是实现&#xff1a;方便性、有效性、可…

加速数实融合,数据交易3.0模式上新

数据交易市场将迎来真正的突破&#xff1f; 目前看的确如此。随着去年底“数据二十条”的颁布&#xff0c;业界普遍认为数据基础制度将加速走向落地与完善&#xff0c;数据要素化今年有望迎来全面提速&#xff0c;将极大促进数据交易市场走向规模化。 IDC预测&#xff0c;到2…

css3新增特性

1. 初始化 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, …

【Cpp】哈希之手撕闭散列/开散列

文章目录 unorderedunordered系列关联式容器unordered_map和unordered_set概述unordered_map的文档介绍unordered_map的接口说明 底层结构 哈希哈希/散列表 概念哈希冲突哈希函数哈希函数设计原则&#xff1a;常见哈希函数 哈希冲突解决闭散列线性探测二次探测 开散列 哈希表的…

mysql学习

DISTINCT 检索不同行 该关键字的作用就是用来去重&#xff0c;可以将你所要展示的数据中完全相同的去重&#xff0c;只展示一个&#xff1b; LIMIT 限制结果 该关键字的作用就是你限制它返回几条数据&#xff0c;比如你想要获得前面5行的据&#xff0c;就可以使用limit 5&…

java 区分缺陷Defects/感染Infections/失败Failure

java 区分缺陷Defects/感染Infections/失败Failure 缺陷Defects 软件故障总是从代码中一个或多个缺陷的执行开始。 缺陷只是一段有缺陷、不正确的代码。 缺陷可能是程序语句的一部分或完整部分&#xff0c;也可能对应于不存在但应该存在的语句。 尽管程序员要对代码中的缺陷负…

利用Servlet编写第一个“hello world“(续)

利用Servlet编写第一个“hello world“ &#x1f50e;通过插件 Smart Tomcat 简化 打包代码 与 部署 操作下载Smart Tomcat配置Smart Tomcat &#x1f50e;Servlet 中的常见错误404(Not Found)&#x1f36d;请求路径出错&#x1f36d;war 包未被正确加载 405(Method Not Allowe…

【ChatGPT】ChatGPT自动生成思维导图

参考视频&#xff1a;https://edu.csdn.net/learn/38346/613917 应用场景&#xff1a;自学&#xff0c;“研一学生如何学习机器学习”的思维导图 问&#xff1a;写一个“研一学生如何学习机器学习”的思维导图内容&#xff0c;以markdown代码块格式输出 # 研一学生如何学习…

统计学的假设检验/置信区间计算

假设检验的核心其实就是反证法。反证法是数学中的一个概念&#xff0c;就是你要证明一个结论是正确的&#xff0c;那么先假设这个结论是错误的&#xff0c;然后以这个结论是错误的为前提条件进行推理&#xff0c;推理出来的结果与假设条件矛盾&#xff0c;这个时候就说明这个假…

《JavaEE》HTTPS

文章目录 HTTPS起源HTTPS对称加密非对称加密两者的区别 HTTPS的安全问题使用对称加密正常交互黑客入侵解决方案 非对称加密引入非对称加密后的流程 中间人攻击黑客的入侵方案加入后的流程解决方案黑客再次加注解决方案 ​&#x1f451;作者主页&#xff1a;Java冰激凌 &#x1…

毫米波雷达信号处理中的通道间相干与非相干积累问题

说明 相干和非相干积累是雷达信号处理中的常用方法&#xff0c;这两个概念一般是用在多脉冲积累这个问题上&#xff1a;积累可以提高信号的SNR&#xff0c;从而提高检出概率。不过本文内容与脉冲积累无关&#xff0c;本文讨论的话题是将这两个概念(non-coherent combination、c…

HCIA-MSTP替代技术之链路捆绑(LACP模式)

目录 手工链路聚合的不足&#xff1a; LACP链路聚合的原理 LACP模式&#xff1a; LACPDU&#xff1a; 1&#xff0c;设备优先级&#xff1a; 设备优先级的比较是&#xff1a;先比较优先级大小&#xff0c;0到32768&#xff0c;越小优先级越高&#xff0c;如果优先级相同&a…

OpenAI再出新作,AIGC时代,3D建模师的饭碗危险了!

大家好&#xff0c;我是千与千寻&#xff0c;也可以叫我千寻哥&#xff0c;说起来&#xff0c;自从ChatGPT发布之后&#xff0c;我就开始焦虑&#xff0c;担心自己程序员的饭碗会不会哪天就被AIGC取代了。 有人说我是过度焦虑了&#xff0c;但是我总觉有点危机感肯定没有坏处。…

【017】C++ 指针变量详解,理解指针变量

C 指针变量详解 引言一、内存概述二、指针变量2.1、地址和指针变量的关系2.2、定义指针变量2.3、指针变量的初始化2.4、指针类型2.5、案例2.6、注意事项 三、数组元素的指针3.1、概述3.2、在使用中 [ ] 就是 *()的缩写3.3、指向同一数组的元素的两个指针变量间的关系 四、字符串…

UOS桌面系统使用RLinux恢复数据

UOS桌面系统使用RLinux恢复数据 一、工具介绍二、注意事项三、准备四、制作live系统启动盘五、拷贝文件六、进入live系统一、工具介绍 R-Linux 是一款用于 Linux 和某些 Unixes 操作系统 Ext2/Ext3/Ext4 FS 文件系统的免费文件恢复实用工具。R-Linux 与 R-Studio 使用相同的 I…

病毒分析丨plubx

作者丨黑蛋 一、基本信息 文件名称 00fbfaf36114d3ff9e2c43885341f1c02fade82b49d1cf451bc756d992c84b06 文件格式 RAR 文件类型(Magic) RAR archive data, v5 文件大小 157.74KB SHA256 00fbfaf36114d3ff9e2c43885341f1c02fade82b49d1cf451bc756d992c84b06 SHA1 1c251974b2e…