视频播放 (二) 自定义 MediaPlayer
创始人
2024-03-07 22:35:51
0

1. 说明

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

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

2. 配置信息

  2.1 AndroidManifest.xml 添加网络权限

 

  2.2 http 明文请求设置

 android:usesCleartextTraffic="true"

  2.3 引用 lifecycle 库

    def lifecycle_version = "2.6.0-alpha03"// ViewModelimplementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"// ViewModel utilities for Composeimplementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"// LiveDataimplementation "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 ViewModelimplementation "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



  3.2 竖屏布局,activity_main.xml



  3.3 横屏布局, activity_main.xml



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 = 0Lval mediaPlayer = MyMediaPlayer()private val _playerStatus = MutableLiveData(PlayerStatus.NotReady)val playerStatus:LiveData = _playerStatusprivate var _bufferPercent = MutableLiveData(0)val bufferPercent: LiveData = _bufferPercentprivate val _controllerFrameVisibility = MutableLiveData(View.INVISIBLE)val controllerFrameVisibility: LiveData = _controllerFrameVisibility;private val _progressBarVisibility = MutableLiveData(View.VISIBLE)val progressBarVisibility:LiveData = _progressBarVisibilityprivate val _videoResolution = MutableLiveData(Pair(0,0))val videoResolution: LiveData> = _videoResolutioninit {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/2131623936val videoPath = "https://media.w3.org/2010/05/sintel/trailer.mp4"reset()_progressBarVisibility.value = View.VISIBLE_playerStatus.value = PlayerStatus.NotReadysetDataSource(videoPath)//val fd = getApplication().getAssets().openFd("red.mp4");//setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());setOnPreparedListener {_progressBarVisibility.value = View.INVISIBLE;//isLooping = trueit.start()_playerStatus.value = PlayerStatus.PlayingLog.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.VISIBLEcontrollerShowTime = 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.VISIBLEmediaPlayer.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: PlayerViewModelprivate lateinit var surfaceView: SurfaceViewprivate lateinit var playerFrameLayout: FrameLayoutprivate lateinit var seekBar: SeekBaroverride 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 = truewhen (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 = falseelse -> 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) returnsurfaceView.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 thedecorView.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 baror View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN)}
}

5. 效果图

 

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...