视频播放 (二) 自定义 MediaPlayer

news2024/11/26 8:19:51

1. 说明

  1.1 使用Mediaplayer和surfaceView进行视频播放,并实现:感应生命周期、支持无缝续播、宽高比适配以及全屏模式

  1.2 创建一个播放控制View,并以ViewModel驱动

2. 配置信息

  2.1 AndroidManifest.xml 添加网络权限

 <uses-permission android:name="android.permission.INTERNET" />

  2.2 http 明文请求设置

 android:usesCleartextTraffic="true"

  2.3 引用 lifecycle 库

    def lifecycle_version = "2.6.0-alpha03"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // ViewModel utilities for Compose
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    // Lifecycles only (without ViewModel or LiveData)
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
    // Saved state module for ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

2.4 矢量图标,添加系统自带矢量图

    ic_baseline_play_arrow_24.xml,
    ic_baseline_replay_24.xml,
    ic_baseline_pause_24.xml

3. 布局文件

  3.1 控制View,controller_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/controllerFrame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#55000000">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_gravity="bottom"
        android:layout_margin="4dp"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/buttonControl"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"
            app:srcCompat="@drawable/ic_baseline_play_arrow_24" />

        <SeekBar
            android:id="@+id/seekBar"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="12"
            android:progressBackgroundTint="#FFFFFF" />
    </LinearLayout>
</FrameLayout>

  3.2 竖屏布局,activity_main.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"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/playerFrame"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#000000"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

    <include
        layout="@layout/controller_layout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

  3.3 横屏布局, activity_main.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"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/playerFrame"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#000000"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />

        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </FrameLayout>

    <include
        layout="@layout/controller_layout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

4. VM 层实现

  4.1 自定义 MediaPlayer, MyMediaPlayer.kt

//LifecycleObserver
class MyMediaPlayer:MediaPlayer(), DefaultLifecycleObserver{
    
//   @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
//    fun pausePlayer(){
//        pause()
//    }
    
    override fun onPause(owner: LifecycleOwner) {
        super.onPause(owner)
        Log.e("MyTag","onPause");
        pause()
    }

    override fun onResume(owner: LifecycleOwner) {
        super.onResume(owner)
        Log.e("MyTag","onResume");
        start()
    }
}

  4.2 实现 ViewModel 控制,PlayerViewModel.kt

//播放状态
enum class PlayerStatus{
    Playing,Paused,Completed,NotReady
}


class PlayerViewModel(application: Application) : AndroidViewModel(application) {
    private var controllerShowTime = 0L
    val mediaPlayer = MyMediaPlayer()
    private val _playerStatus = MutableLiveData(PlayerStatus.NotReady)
    val playerStatus:LiveData<PlayerStatus> = _playerStatus
    private var _bufferPercent = MutableLiveData(0)
    val bufferPercent: LiveData<Int> = _bufferPercent
    private val _controllerFrameVisibility = MutableLiveData(View.INVISIBLE)
    val controllerFrameVisibility: LiveData<Int> = _controllerFrameVisibility;
    private val _progressBarVisibility = MutableLiveData(View.VISIBLE)
    val progressBarVisibility:LiveData<Int> = _progressBarVisibility
    private val _videoResolution = MutableLiveData(Pair(0,0))
    val videoResolution: LiveData<Pair<Int,Int>> = _videoResolution

    init {
        loadVideo()
    }

    private fun loadVideo(){
        mediaPlayer.apply {
            //https://media.w3.org/2010/05/sintel/trailer.mp4
            //http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4
            //$packageName
            //val videoPath = "android.resource://com.example.myplayer/${R.raw.redes}"
            //android.resource://com.example.myplayer/2131623936
            val videoPath = "https://media.w3.org/2010/05/sintel/trailer.mp4"
            reset()
            _progressBarVisibility.value = View.VISIBLE
            _playerStatus.value = PlayerStatus.NotReady
            setDataSource(videoPath)
            //val fd = getApplication<Application>().getAssets().openFd("red.mp4");
            //setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
            setOnPreparedListener {
                _progressBarVisibility.value = View.INVISIBLE;
                //isLooping = true
                it.start()
                _playerStatus.value = PlayerStatus.Playing
                Log.e("MyTag", "setOnPreparedListener")
            }
            //宽高
            setOnVideoSizeChangedListener { _, width, height ->
                _videoResolution.value = Pair(width, height)
            }
            //缓冲
            setOnBufferingUpdateListener { _, percent ->
                _bufferPercent.value = percent
            }
            //播放完成
            setOnCompletionListener {
                _playerStatus.value = PlayerStatus.Completed
            }
            //进度完成
            setOnSeekCompleteListener {
                mediaPlayer.start()
                _playerStatus.value = PlayerStatus.Playing
                _progressBarVisibility.value = View.INVISIBLE
            }
            prepareAsync()
        }
    }

    //播放状态
    fun togglePlayerStatus(){
        when(_playerStatus.value){
           PlayerStatus.Playing ->{
               mediaPlayer.pause()
               _playerStatus.value = PlayerStatus.Paused
           }
            PlayerStatus.Paused ->{
                mediaPlayer.start()
                _playerStatus.value = PlayerStatus.Playing
            }
            PlayerStatus.Completed ->{
                mediaPlayer.start()
                _playerStatus.value = PlayerStatus.Playing
            }
            else -> return
        }
    }

    // 显示/隐藏 控制条
    fun toggleControllerFrame(){
       if(_controllerFrameVisibility.value == View.INVISIBLE){
           _controllerFrameVisibility.value = View.VISIBLE
           controllerShowTime = System.currentTimeMillis()
          viewModelScope.launch {
              delay(3000)
              if(System.currentTimeMillis() - controllerShowTime > 3000){
                  _controllerFrameVisibility.value = View.INVISIBLE
              }
          }
       }else{
           _controllerFrameVisibility.value = View.INVISIBLE
       }
    }

    //重新赋值
    fun emmitVideoResolution(){
        _videoResolution.value = _videoResolution.value
    }

    //设置 MediaPlayer 进度
    fun playerSeekToProgress(progress: Int){
        _progressBarVisibility.value = View.VISIBLE
        mediaPlayer.seekTo(progress)
    }

    override fun onCleared() {
        super.onCleared()
        mediaPlayer.release()
        Log.e("MyTag","mediaPlayer release");
    }
}

  4.3 调用view层, 使用ViewModel,MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var playerViewModel: PlayerViewModel
    private lateinit var surfaceView: SurfaceView
    private lateinit var playerFrameLayout: FrameLayout
    private lateinit var seekBar: SeekBar

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        object :OrientationEventListener(this){
//            override fun onOrientationChanged(orientation: Int) {
//            }
//        }
        setContentView(R.layout.activity_main)
        val progressBar: ProgressBar = findViewById(R.id.progressBar)
        seekBar = findViewById(R.id.seekBar)
        val controllerFrameLayout: FrameLayout = findViewById(R.id.controllerFrame)
        val buttonControl: ImageView = findViewById(R.id.buttonControl)
        playerFrameLayout = findViewById(R.id.playerFrame)
        updatePlayerProgress()
        playerViewModel = ViewModelProvider(this)[PlayerViewModel::class.java].apply {
            progressBarVisibility.observe(this@MainActivity) {
                progressBar.visibility = it
            }
            videoResolution.observe(this@MainActivity) {
                seekBar.max = mediaPlayer.duration
                //Log.e("MyTag","---- ${mediaPlayer.duration}");
                playerFrameLayout.post {
                    reSizePlayer(it.first, it.second)
                }
            }
            controllerFrameVisibility.observe(this@MainActivity) {
                controllerFrameLayout.visibility = it
            }

            bufferPercent.observe(this@MainActivity, Observer {
                //Log.e("MyTag","---- $it");
                seekBar.secondaryProgress = seekBar.max * it / 100;
            })

            playerStatus.observe(this@MainActivity) {
                buttonControl.isClickable = true
                when (it) {
                    PlayerStatus.Paused -> buttonControl.setImageResource(R.drawable.ic_baseline_play_arrow_24)
                    PlayerStatus.Completed -> buttonControl.setImageResource(R.drawable.ic_baseline_replay_24)
                    PlayerStatus.NotReady -> buttonControl.isClickable = false
                    else -> buttonControl.setImageResource(R.drawable.ic_baseline_pause_24)
                }
            }
        }

        lifecycle.addObserver(playerViewModel.mediaPlayer)

        buttonControl.setOnClickListener {
            playerViewModel.togglePlayerStatus()
        }

        playerFrameLayout.setOnClickListener {
            playerViewModel.toggleControllerFrame()
        }

        seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                if (fromUser) {
                    playerViewModel.playerSeekToProgress(progress)
                }
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {}

            override fun onStopTrackingTouch(seekBar: SeekBar?) {}
        })
        surfaceView = findViewById(R.id.surfaceView)
        surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {}

            override fun surfaceChanged(
                holder: SurfaceHolder, format: Int, width: Int, height: Int
            ) {
                playerViewModel.mediaPlayer.setDisplay(holder)
                playerViewModel.mediaPlayer.setScreenOnWhilePlaying(true)
            }

            override fun surfaceDestroyed(holder: SurfaceHolder) {}
        })
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            hideSystemUI()
            playerViewModel.emmitVideoResolution()
        }
    }

    private fun reSizePlayer(width: Int, height: Int) {
        if (width == 0 || height == 0) return
        surfaceView.layoutParams = FrameLayout.LayoutParams(
            playerFrameLayout.height * width / height,
            FrameLayout.LayoutParams.MATCH_PARENT,
            Gravity.CENTER
        )
        //1674 1908
        //Log.e("MyTag","Size width:  ${playerFrameLayout.height * width / height}")
    }

    private fun updatePlayerProgress() {
        lifecycleScope.launch {
            while (true) {
                delay(500)
                seekBar.progress = playerViewModel.mediaPlayer.currentPosition
            }
        }
    }

    private fun hideSystemUI() {
        val decorView: View = window.decorView
        // Set the content to appear under the system bars so that the
        decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                // content doesn't resize when the system bars hide and show.
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN //Hide the nav bar and status bar
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN)
    }
}

5. 效果图

 

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

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

相关文章

Docker入门-上篇

Docker容器技术的使用&#xff0c;现在它已经不仅仅只是运维人员的专属技能了&#xff0c;对于我们开发人员同样需要具备&#xff0c;在很多中小公司中云环境的项目搭建和项目部署依然还是我们开发人员干的事&#xff0c;所以多学一门技术总是没错的。 1.Docker介绍 Docker最…

Ubuntu下解压文件(提取文件总是报错)文件是zip 格式

删除非空文件夹&#xff1a;在该目录下打开终端&#xff1a;sudo rm -r 文件夹名 回到不能解压问题&#xff08;unzip总是容易出问题&#xff09; 安装7zip&#xff1a;sudo apt-get install p7zip-full 解决办法1&#xff08;解压出来好的数据&#xff09;: 使用7z解压文件&…

附录6-JS中的一些概念

1 深拷贝与浅拷贝 在这几个地方涉及到了拷贝 23. 节点_Suyuoa的博客-CSDN博客 复制节点 node.cloneNode() 深拷贝实质上是拷贝要拷贝的对象自身&#xff0c;浅拷贝实质上是对要拷贝对象的引用。 当你浅拷贝复制A为B的时候&#xff0c;改动B会给A造成影响 当你深拷贝复制A…

极速Go语言入门(超全超详细)-进阶篇

基础篇可访问此链接: 基础篇1:https://blog.csdn.net/asd1358355022/article/details/127905011?spm1001.2014.3001.5501 基础篇2:https://blog.csdn.net/asd1358355022/article/details/128039005?spm1001.2014.3001.5501 文章目录GO语言类型断言文件操作打开、关闭、读取文…

第二证券|券商12月金股出炉!多只地产股成热门,科创仍是中长期主线

跟着券商12月金股战略陈述连续出炉&#xff0c;主流组织的配备风向也浮出水面。 到券商我国记者发稿时&#xff0c;已有10多家券商发布12月金股战略陈述&#xff0c;从职业散布来看&#xff0c;信息技术、工业范畴的金股数量最多&#xff0c;其次是材料、可选消费、医疗。值得一…

前端二倍图

物理像素&物理像素比&#xff1a; 物理像素点指的是屏幕显示的最小颗粒&#xff0c;是物理真实存在的&#xff0c;这是厂商在出厂时设置好了我们开发时候1px不是一定等于1个物理像素的Pc端页面&#xff0c;1px等于1个物理像素点&#xff0c;但是移动端就不尽相同一个px能显…

打包发布自己的app

创建自己的app 一、 安装HBuilderX 二、 引入代码&#xff0c;引入组件 三、 配置app信息 四、 云打包 1、第一步 2、第二步 3、证书创建是用的jdk8创建的&#xff0c;软件里带教程&#xff0c;也可以用公共测试证书。 五、打出的包是apk文件&#xff0c;配合我的搭建网…

ROG幻15电脑开机自动安装软件怎么U盘重装系统

ROG幻15电脑开机自动安装软件怎么U盘重装系统。今天和大家一起来分享如何解决ROG幻15电脑开机的时候会自动安装软件的问题。用户反馈开机之后自动后台安装很多软件&#xff0c;导致无法操作卡死。这个情况我们可以使用U盘来重装一些系统&#xff0c;这样就可以解决问题&#xf…

(二)正则表达式——捕获

&#xff08;二&#xff09;正则表达式——捕获 正则捕获的懒惰性 实现正则捕获的方法&#xff1a;exec exec返回的结果&#xff1a; 懒惰性 这就是正则捕获的懒惰性&#xff1a;默认只捕获第1个 lastIndex&#xff1a;下次匹配的开始位置 懒惰的原因&#xff1a;默认lastIndex…

视频播放 (一) VideoView的使用

1. 配置参数 1.1 AndroidManifest.xml 文件添加网络权限 <uses-permission android:name"android.permission.INTERNET" /> 1.2 http 明文请求设置 android:usesCleartextTraffic"true" 1.3 activity 配置屏幕变化&#xff0c;不重新加载 Activity …

多数据中心多活相关知识

Cell&#xff1a;业务可封闭收敛最小执行分片&#xff1b;业务对请求空间按一定维度&#xff08;比如会员、门店等&#xff09;划分分片。 LDC&#xff1a;逻辑数据中心&#xff0c;是由多个业务可封闭 cell 组成的集合单元&#xff0c;拥有独立的基础中间件系统&#xff08;包…

树莓派4b通过docker安装部署jenkins

借鉴&#xff1a;https://blog.csdn.net/wz_coming/article/details/113523610 树莓派的docker安装及其他操作请看&#xff1a;https://blog.csdn.net/weixin_44578029/article/details/127987795 前言 我的环境是树莓派4b&#xff0c;安装的官方64 debian11系统 arm架构 4h…

[附源码]SSM计算机毕业设计疫情状态下病房管理平台JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Kafka第一讲:应用场景及架构设计详解

本节是Kafka专题第一篇&#xff0c;主要介绍Kafka的发展历史、应用场景以及Kafka的基本架构&#xff0c;后续还会对Kafka的生产者、Broker、消费者、集群做详细讲解&#xff0c;敬请期待。 1.kafka的发展历史及应用场景 1.1kafka的定位 可以实现如下功能&#xff1a; 1.2为什…

『航班乘客满意度』场景数据分析建模与业务归因解释 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 数据分析实战系列&#xff1a;https://www.showmeai.tech/tutorials/40 &#x1f4d8; 机器学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/41 &#x1f4d8; 本文地址&#xff1a;https://www.sho…

vue+elementUI实现级联表格el-table级联多选

vueelementUI实现级联表格el-table级联多选 <template><div id"app"><el-button type"primary" click"getAllSelect()">获取选中集合</el-button><el-table:data"renderDynamic"ref"lendontable"…

DataFun: 微信NLP算法微服务治理

管理问题 性能问题 PyInter&#xff1a;暂未开源&#xff0c;有开源打算 调度问题 P50&#xff1a; 响应的中位数P999&#xff1a;耗时最慢的千分之一 让p999下降为p50的1.5倍

DIY正则图片下载工具

一、初心&#xff1a;如果您擅长正则表达式&#xff0c;可以自定义抓取自定义网页的图片。 二、效果&#xff1a; 目前支持 <img>标签抓取图片正则。更多正则欢迎分项。支持base64图片预览。 三、使用方法&#xff1a; 修改正则表达式&#xff1a;选中即可。同时工具几…

Springboot毕业设计毕设作品,纯净水销售配送管理系统设计与实现

功能清单 在系统里面我们将纯净水的产品统称为商品 【后台管理员功能】 广告管理&#xff1a;设置小程序首页轮播图广告和链接 留言列表&#xff1a;所有用户留言信息列表&#xff0c;支持删除 会员列表&#xff1a;查看所有注册会员信息&#xff0c;支持删除 资讯分类&#…

基于Abaqus-Simpack联合仿真车辆-浮置板轨道耦合动力学仿真

作者&#xff1a; CAE兮枫如秋 仿真秀专栏作者 一、城市轨道交通中钢弹簧浮置板高级减振轨道 城市轨道交通不仅作为城市亮丽的名片&#xff0c;还在解决城市交通问题中具有特殊的地位和作用。城市轨道交通也是一种安全、快捷、准时、方便、舒适的理想交通工具。伴随着全世界各…