【安卓】视频播放器实现过程,超详细注释,自定义视频进度条,打开本地文件播放视频等功能。

news2024/12/29 19:18:37

一、实现效果

TV模拟器效果截图
废话不多说,直接上代码,里面有详细注释,不清楚的评论区留言。

二、布局代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="@color/background"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">
        <ImageButton
            android:id="@+id/video_file"
            android:layout_width="12dp"
            android:layout_height="12dp"
            android:background="@mipmap/video_file"/>
        <TextView
            android:id="@+id/open_video"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:clickable="true"
            android:textSize="8dp"
            android:text="本地视频"/>
    </LinearLayout>

    <VideoView
        android:id="@+id/video"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_marginLeft="3dp"
        android:layout_alignParentBottom="true"
        android:gravity="center_vertical"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/play_video"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:clickable="true"
            android:text="播放"
            android:textColor="@color/white"
            android:layout_weight="2"
            android:textSize="10dp"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text=" | "/>
        <TextView
            android:id="@+id/pause_video"
            android:layout_width="0dp"
            android:clickable="true"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="暂停"
            android:textColor="@color/white"
            android:textSize="10dp"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text=" | "/>
        <TextView
            android:id="@+id/replay_video"
            android:layout_width="0dp"
            android:clickable="true"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="重播"
            android:textColor="@color/white"
            android:textSize="10dp"/>
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text=" | "/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/seekbar_start_second"
            android:text="0"
            android:textSize="8dp"
            android:layout_weight="1"
            android:textColor="@color/white"/>
        <SeekBar
            android:layout_width="0dp"
            android:id="@+id/seekbar_video"
            android:layout_height="wrap_content"
            android:layout_weight="60"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/seekbar_max_second"
            android:text="0"
            android:textSize="8dp"
            android:layout_weight="1"
            android:textColor="@color/white"/>
    </LinearLayout>
</RelativeLayout>

三、Activity代码

package com.example.tabtest.activity;

import android.app.Activity;;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.VideoView;
import com.example.tabtest.R;
import com.example.tabtest.util.FormatTime;

/**
 * author lishilin
 * date 2023-7-25
 * 描述 视频播放器,自定义播放进度条,可以打开文件管理器选择本地视频进行播放
 */

public class VideoActivity extends Activity implements View.OnClickListener {
    private VideoView video;
    private TextView startPlay;
    private TextView pausePlay;
    private int totalSeconds;//视频总时长,单位,毫秒
    private int currentSeconds;//当前视频进度
    private int seekBarMax;//进度条最大值
    private SeekBar seekBar;//进度条
    private TextView seekBarMaxSecond;//进度条最大秒数
    private TextView seekBarStartSecond;//进度条开始秒数
    private FormatTime formatTime;
    private TextView replay;
    private TextView openVideo;
    private ImageButton videoFile;
    @Override
    public void onCreate(Bundle saveInstanceState){
        super.onCreate(saveInstanceState);
        setContentView(R.layout.video_play);
        initView();
        formatTime = new FormatTime();
        updateSeekbar();//监听进度条滑块
        playListener();//设置视频加载监听器
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//强制页面横屏
    }

    /**
     * 点击事件
     * @param view
     */
    @Override
    public void onClick(View view){
        switch (view.getId()){
            case R.id.play_video:
                if (!video.isPlaying()){
                    video.start();
                }
                break;
            case R.id.pause_video:
                if (video.isPlaying()){
                    video.pause();
                }
                break;
            case R.id.replay_video:
                video.resume();
                break;
            case R.id.open_video:
            case R.id.video_file:
                toOpenVideo();
                break;
            default:
                break;
        }
    }

    /**
     * 初始化组件
     */
    private void initView(){
        video = (VideoView) findViewById(R.id.video);
        String videoPath = "https://vfx.mtime.cn/Video/2019/07/12/mp4/190712140656051701.mp4";//网络视频地址
        video.setVideoPath(videoPath);
//        Uri uri = Uri.parse("android.resource://"+getPackageName()+"/"+R.raw.test); //本地视频地址
//        video.setVideoURI(uri);//设置视频播放路径
        video.requestFocus();//视频获取焦点
        seekBar = (SeekBar) findViewById(R.id.seekbar_video);
        seekBarMaxSecond = (TextView) findViewById(R.id.seekbar_max_second);
        seekBarStartSecond = (TextView) findViewById(R.id.seekbar_start_second);
        replay = (TextView) findViewById(R.id.replay_video);
        replay.setOnClickListener(this);
        startPlay = (TextView) findViewById(R.id.play_video);
        pausePlay = (TextView) findViewById(R.id.pause_video);
        openVideo = (TextView) findViewById(R.id.open_video);
        videoFile = (ImageButton) findViewById(R.id.video_file);
        videoFile.setOnClickListener(this);
        openVideo.setOnClickListener(this);
        startPlay.setOnClickListener(this);
        pausePlay.setOnClickListener(this);
    }
    /**
     * 设置视频播放时候的动作监听,并作出相应的处理,比如进度条更新等操作
     */
    private void playListener(){
        //监听视频准备加载完毕之后执行
        video.setOnPreparedListener(mediaPlayer -> {
            video.start();
            totalSeconds = video.getDuration();//获取视频总毫秒数
            seekBarMaxSecond.setText(formatTime.SecondToTime(totalSeconds/1000));//设置进度条最大时间秒数
            seekBarMax = totalSeconds;
            seekBar.setMax(seekBarMax);
            //异步更新ui,利用消息机制处理子线程的需求
            Handler mhandler = new Handler(){
                @Override
                public void handleMessage(Message message){
                    if(message.what == 1){
                        updateProcess();//更新视频播放时间和进度条
                    }
                }
            };
            //子线程里执行循环发送消息
            new Thread(new Runnable() {
                @Override
                public void run() {
                    currentSeconds = video.getCurrentPosition();//获取视频播放进度,返回值为毫秒
                    while (true){
                        Message message = new Message();
                        message.what = 1;
                        mhandler.sendMessage(message);
                        try {
                            Thread.sleep(1);//设置一毫秒更新一次进度条ui,这里可以改成想要的值
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        });
    }

    /**
     * 更新视频播放时间和进度条
     */
    private void updateProcess(){
        currentSeconds = video.getCurrentPosition();
        seekBarStartSecond.setText(formatTime.SecondToTime(currentSeconds/1000));
        seekBar.setProgress(currentSeconds);
    }

    /**
     * 监听进度条滑块
     */
    public void updateSeekbar(){
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }
            //停止滑动的时候执行下面的方法
            @Override
            public void onStopTrackingTouch(SeekBar seekBar1) {
                int current = seekBar.getProgress();
                video.seekTo(current);
                seekBarStartSecond.setText(formatTime.SecondToTime(currentSeconds/1000));
            }
        });
    }

    /**
     * 打开视频文件并播放
     */
    private void toOpenVideo(){
        video.pause();
        Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("*/*");//设置类型,这是任意类型
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        //打开一个activity,并在onActivityResult方法里面接收回调数据,更新视频地址
        startActivityForResult(intent,1);
        //销毁上一个视频
        video.suspend();
    }

    private static final int FILE_SELECT_CODE = 1;

    /**
     * 处理打开文件之后的行为,如给video设置视频路径,该方法是activity在执行完startActivityForResult(intent,1)方法之后自动执行的,无需调用;
     * @param requestCode
     * @param resultCode
     * @param data
     */
    public void onActivityResult(int requestCode,int resultCode,Intent data){
        if(resultCode == Activity.RESULT_OK){
            Uri uri = data.getData();
            video.setVideoURI(uri);
            video.requestFocus();
            super.onActivityResult(requestCode,resultCode,data);
            return;
        }
        if (resultCode == FILE_SELECT_CODE){
            Uri uri = data.getData();
            Log.d("videoActivity",uri.getPath());
        }
        super.onActivityResult(requestCode,resultCode,data);
    }

    @Override
    public void onDestroy() {
        video.suspend();
        Log.i("视频播放器","释放资源");
        super.onDestroy();
    }
}

自定义一个进度条时间转换工具类,超简单

public class FormatTime {
    public String SecondToTime(int seconds){
        int second = seconds % 60;
        int m = seconds / 60;
        int h = seconds /60 /60;
        return h+":"+m+":"+second;
    }
}

最后,布局资源有一个icon,自己网上找一个,或者删掉那个图标的ImageButton,颜色值啥的自己设置叭

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

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

相关文章

Android安卓实战项目(3)—一个炫酷的健身APP界面

Android安卓实战项目&#xff08;3&#xff09;—一个炫酷的健身APP界面 一.项目运行介绍 1.大致浏览 2.功能介绍 &#xff08;1&#xff09;功能一 上导航条 &#xff08;2&#xff09;功能二 下导航条 二.具体实现 MainActivity.java package com.rckdeveloper.fitene…

python 面向对象编程的特点 - 封装 - 继承(经典类、新式类) - 多态 - 静态方法、类方法 - 下划线的使用 - 回合制攻击游戏实验

目录 面向对象编程的特点&#xff1a; 封装&#xff1a;封装是将数据和操作&#xff08;方法&#xff09;封装在一个对象中的能力 继承&#xff1a;继承是指一个类&#xff08;子类&#xff09;可以继承另一个类&#xff08;父类&#xff09;的属性和方法。 我们为什么需要继…

Python采集法外狂徒张三所有视频【含jS逆向解密】

传说中&#xff0c;有人因为只是远远的看了一眼法外狂徒张三就进去了&#x1f602; 我现在是获取他视频&#xff0c;岂不是直接终生了&#x1f929; 网友&#xff1a;赶紧跑路吧 &#x1f60f; 好了话不多说&#xff0c;我们直接开始今天的内容吧&#xff01; 你需要准备 …

详解STM32的GPIO八种输入输出模式,GPIO各种输入输出的区别、初始化的步骤详解,看这文章就行了(超详细)

在STM32微控制器中&#xff0c;常见的输入输出(GPIO)模式有八种&#xff0c;分别是推挽输出、开漏输出、复用推挽输出、复用开漏输出、浮空输入、上拉输入、下拉输入和模拟输入。下面我将为你解释每种模式的特点和区别&#xff0c;并提供相应的示例代码。 文章目录 介绍区别初…

组合模式-树形结构的处理

A公司需要筛选出年龄35岁及以上(如果是领导&#xff0c;年龄为45岁及以上)的人。其组织架构图如下。 图 A公司部分组织架构图 图 传统解决方案 public class Development {private String name;public Development(String name) {this.name name;}List<Employee> emplo…

uni-app优雅的实现时间戳转换日期格式

现在显示的格式如下图&#xff1a; 我期望统一格式&#xff0c;所以不妨前端处理一下&#xff0c;核心代码如下 filters: {// 时间戳处理formatDate: function(value, spe /) {value value * 1000let data new Date(value);let year data.getFullYear();let month data.…

【设计模式——学习笔记】23种设计模式——适配器模式Adapter(原理讲解+应用场景介绍+案例介绍+Java代码实现)

介绍 生活中的案例 不同国家的插座不同&#xff0c;出国旅游充电器不能直接使用&#xff0c;可以通过使用多功能转换插头来辅助使用 基础介绍 适配器模式将某个类的接口转换成客户端期望的另一个接口表示&#xff0c;主的目的是兼容性&#xff0c;让原本因接口不匹配不能一起…

github gitlab 多用户多平台切换

一、背景 我需要用账号1 来登录并管理github 账号 我需要用账号2 来登录并管理gitlab 账号 二、设置账号 邮箱 设置账号1用户名与邮箱 git config --global user.name "miaojiang" git config --global user.email "187133163.com" 三、生成本地密钥…

LT6911C 是一款HDMI 1.4到双端口MIPIDSI/CSI或者LVDS加音频的一款高性能芯片

LT6911C 1.描述&#xff1a; LT6911C是一款高性能的HDMI1.4到MIPIDSI/CSI/LVDS芯片&#xff0c;用于VR/智能手机/显示器应用程序。对于MIPIDSI/CSI输出&#xff0c;LT6911C具有可配置的单端口或双端口MIPIDSI/CSI&#xff0c;具有1个高速时钟通道和1个~4个高速数据通道&#…

ChatGLM-RM(Reward Model)实现代码逐行讲解

这里我们尝试通过RM训练让模型学会从给定上下文中提取信息&#xff0c;来进行RM模型的实践。你可以从下面链接获取代码 GitHub - Pillars-Creation/ChatGLM-RLHF-LoRA-RM: ChatGLM-6B添加了RLHF的实现&#xff0c;以及部分核心代码的逐行讲解 ,实例部分是做了个新闻短标题的生成…

入行软件测试7年,才知道原来字节跳动这么容易进

当前就业环境&#xff0c;裁员、失业消息满天飞&#xff0c;好像有一份工作就不错了&#xff0c;更别说高薪了。其实这只是一方面&#xff0c;而另一方面&#xff0c;各大企业依然求贤若渴&#xff0c;高技术人才依然紧缺&#xff0c;只要你技术过硬&#xff0c;拿个年薪50w不是…

FUNBOX_1靶场详解

FUNBOX_1靶场复盘 这个系列的靶场给出的干扰因素都挺多的&#xff0c;必须从中找到有用的线索才可以。 这个靶场你扫描到ip地址后打开网页会发现&#xff0c;ip自动转换成域名了&#xff0c;所以我们需要添加一条hosts解析才可以。 192.168.102.190 funbox.fritz.box从目录…

4EVERLAND 托管让 Permaweb 变得更容易!

在互联网托管领域&#xff0c;我们通常将其与存储和管理网站的服务联系起来。传统的 Web2 托管服务在集中式服务器模型上运行&#xff0c;其中网站文件和数据库存储在集中管理的服务器上。用户通过互联网访问网站。这种托管模式应用广泛&#xff0c;相对简单&#xff0c;适合很…

计算机存储结构、执行速度及对应用的影响

万丈高楼&#xff0c;平地起。 计算机世界的信息化软件工程&#xff0c;是构筑于计算机硬件之上的。 由于信息的流转依托于计算机不同的部件&#xff0c;所以计算机系统的内部设计、各类应用架构无不受部件之间速度差异的影响。 本文&#xff0c;主要先介绍存储体系&#xff0c…

Spring Security 授权体系结构

目录 1、Authorities 授权&#xff08;AuthorizationFilter 过滤器&#xff09; 2、AuthorizationManager 授权管理器 3、角色的层次化&#xff08;Roles&#xff09; 1、Authorities 授权&#xff08;AuthorizationFilter 过滤器&#xff09; 通过 Authentication.getAutho…

Spring基础知识讲解

文章目录 Spring是什么&#xff1f;IoC容器与DIDI与IoC的区别 Spring项目的创建配置maven国内源创建Spring项目有关Bean的操作存储Bean使用Bean ApplicationContext和BeanFactory的区别getBean()的三种使用方法 更简单的存储和获取对象类注解方法注解获取Bean对象的简单方法——…

Ubuntu18.04下安装ROS

安装相关依赖 sudo apt install ninja-build exiftool ninja-build protobuf-compiler libeigen3-dev genromfs xmlstarlet libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev python-pip python3-pip ----------------------------------------------------------------…

TCP/IP 介绍

一、TCP/IP 是什么 TCP /IP 是因特网通信协议 &#xff08;注释&#xff1a;通信协议是对计算机必须遵守的规则的描述&#xff0c;只有遵守这些规则&#xff0c;计算机之间才能进行通信。&#xff09; 因特网浏览器和因特网服务器均使用 TCP/IP 来连接因特网。浏览器使用 TCP…

Idea 避免import *

File -> setting -> Editor -> Code Style -> Java -> Imports

详解Mybatis查询之resultType返回值类型问题【4种情况】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 Mybatis版本&#xff1a;3.5.6 文章目录 引言一、查询单行数据返回单个对象二、查询多行数据返回对象的集合三、 查询单行数据返回Map[Key,…