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

news2024/11/13 6:36:44

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);
            }
        });
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

海康、大华、tplink监控摄像头和硬盘录像机接入GB28181平台配置细节

海康、大华、tplink等各种型号监控摄像头或硬盘录像机(NVR/HVR)接入GB28181平台&#xff0c;配置过程都非常简单明了&#xff0c;但有些细节需要注意&#xff0c;避免走弯路踩泥坑。 首先要说明一点的是&#xff0c;只要监控设备和GB28181平台的网络是连通的&#xff0c;都可以…

5月第4周榜单丨飞瓜数据B站UP主排行榜单(哔哩哔哩)发布!

飞瓜轻数发布2023年5月22日-5月28日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

chatgpt赋能python:Python中提取纯数字的方法

Python中提取纯数字的方法 在数据清洗和数据分析中&#xff0c;经常需要将文本中的数字提取出来&#xff0c;用于后续的计算或统计分析。Python作为一种流行的数据处理语言&#xff0c;提供了多种方法来完成这个任务。 方法一&#xff1a;使用正则表达式 正则表达式是一种强…

什么是精细化管理?怎样做好精细化管理?

俗话说&#xff0c;细节决定成败&#xff0c;企业管理也一样&#xff0c;精细化管理尤为重要。 01 什么是精细化管理 精细化管理是企业管理的一种理念&#xff0c;可追溯至20世纪50年代泰勒科学管理。也可以说是一种文化&#xff0c;以最大限度地减少管理所占用的资源和降低管…

超详细的 Wireshark 使用教程

一、wireshark是什么&#xff1f; wireshark是非常流行的网络封包分析软件&#xff0c;简称小鲨鱼&#xff0c;功能十分强大。可以截取各种网络封包&#xff0c;显示网络封包的详细信息。 wireshark是开源软件&#xff0c;可以放心使用。可以运行在Windows和Mac OS上。对应的…

(双指针 ) 18. 四数之和 ——【Leetcode每日一题】

❓18. 四数之和 难度&#xff1a;中等 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重…

Linux系统下imx6ull QT编程—— C++重载(六)

Linux QT编程 文章目录 Linux QT编程前言一、函数重载示例 二、运算符重载1.示例 前言 C 允许在同一作用域中的某个函数和运算符指定多个定义&#xff0c;分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明&#xf…

如何实现 ESP 设备多证书管理?

设置特定分区存储证书文件多证书文件管理证书格式转换证书下载使用证书文件 1、设置特定分区存储证书文件 在项目工程下分区表文件下定义证书分区表文件&#xff0c;如下&#xff1a; 如上&#xff0c;转换的 certificate.bin 下载地址就为 0x41000证书分区文件的大小可不做设…

Benewake(北醒) 快速实现 TF02-Pro-IIC 与电脑通信操作说明

目录 1. 概述2. 测试准备2.1 工具准备2.2通讯协议转换 3. IIC通讯测试3.1 引脚说明3.2 测试步骤3.2.1 TF02-Pro-IIC 与 PC 建立连接3.2.2 获取测距值3.2.3 更改 slave 地址 1. 概述 通过本文档的概述&#xff0c;能够让初次使用测试者快速了解测试 IIC 通信协议需要的工具以及…

度量学习:使用多类N对损失改进深度度量学习

度量学习系列 Author: 码科智能 使用多类N对损失改进深度度量学习 度量学习是ReID任务中常用的方式之一&#xff0c;今天来看下一篇关于如何改进度量学习的论文。来自2016年NeurIPS上的一篇论文&#xff0c;被引用超过900次。 论文&#xff1a;Improved Deep Metric Learni…

针对蓝桥杯竞赛(Python)的基础知识 No.1

首先我们要知道Python是有着大量的库&#xff08;模块、类、函数&#xff09;的&#xff0c;所谓善借其器,善用其利 Q1、日期问题 掌握 datetime库 eg&#xff1a;小蓝每周六、周日都晨跑&#xff0c;每月的 1、11、21、31日也晨跑。其它时间不晨跑。已知 2022年1月1日是周六&…

Allegro输出光绘文件规范

光绘输出操作规范 1.1添加钻孔表 添加钻孔表的具体步骤为: 1.通过屏幕右边的Visibility选项的Views列表,将Drill层打开 2.将Visibility选项中的PIN和Via选项都选中,见下图所示: 1.2添加钻孔文件 参数设好之后关闭NC Drill/Parameters窗口,输出数控机床钻孔文件的命令…

1130 Infix Expression(34行代码+超详细注释)

分数 25 全屏浏览题目 切换布局 作者 CHEN, Yue 单位 浙江大学 Given a syntax tree (binary), you are supposed to output the corresponding infix expression, with parentheses reflecting the precedences of the operators. Input Specification: Each input fil…

练习Vue烘培坊项目

烘培坊项目 文章目录 烘培坊项目项目概述项目页面展示后台管理页面登录页面文章详情页面稿件发布页面 项目关键代码实现后台管理页面稿件管理页面内容列表页面文章详情页面烘培坊主页面注册页面登录页面个人信息页面稿件发布页面 项目概述 烘培坊&#xff08;Bakery&#xff0…

WTI纽约原油CFD期货怎么交易?交易方法有哪些?

我们通常把未加工过的石油称为原油&#xff0c;原油也有“黑色黄金”之称。原油的用途无处不在&#xff0c;无论是工业制品或者日常生活用品等都离不开原油。原油一般以“桶&#xff08;barrel&#xff09;”作为单位&#xff0c;1桶约等于159升。在国际上影响力较大的基准原油…

教会你----如何烧录Arduinod代码进入ESP8266 MCU中,让你清楚的了解这个烧录方式的正确操作。

本次开发板为ESP8266 MUC 以下视频是烧录的操作&#xff0c;专给小白的视频 . .分隔符....................................................................................................... . .主要在 RST按久一点&#xff0c; 在放手的一瞬间接着按下 Flash …

电商网站的构建思维和技术

电商网站的架构及技术 3.1框架和技术 本系统主要以.net框架和C#语言位主要的开发工具&#xff0c;前端使用QUI前端框架。技术插件有Redis集群缓存、RabbitMQ 消息、MySql数据库。 实际上&#xff0c;在电商系统中&#xff0c;大部分数据都是可以缓存的&#xff0c;不能使用缓…

影响布伦特原油CFD期货行情的因素有哪些?

原油有很多种&#xff0c;其中比较有知名度的是布伦特原油&#xff0c;该原油是欧洲的原油产品&#xff0c;后来相继的有北海、地中海、也门以及非洲等诸多国家和地区以此为标准推出该产品。在国际金融市场中&#xff0c;布伦特原油特指洲际交易所&#xff08;ICE&#xff09;的…

flink1.17.0 集成kafka,并且计算

前言 flink是实时计算的重要集成组件&#xff0c;这里演示如何集成&#xff0c;并且使用一个小例子。例子是kafka输入消息&#xff0c;用逗号隔开&#xff0c;统计每个相同单词出现的次数&#xff0c;这么一个功能。 一、kafka环境准备 1.1 启动kafka 这里我使用的kafka版本…

驾校驾考APP开发功能有哪些?

随着汽车成为越来越多人出行的代步工具之后&#xff0c;需要考驾照的人也是越来越多。小编记得我当初考驾照的时候还是抱着一个小本本每天刷题练习&#xff0c;小本本都快翻烂了。移动互联网的普及让驾考也开始走向线上&#xff0c;刷题、模拟、甚至是考试都可以通过驾考APP小程…