视频播放 (二) 自定义 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. 效果图

 

相关内容

热门资讯

秋季开学典礼颁奖主持词 秋季开学典礼颁奖主持词  活动对象的不同,主持词的写作风格也会大不一样。在人们积极参与各种活动的今天...
老人寿宴致辞 老人寿宴致辞(精选7篇)  在我们平凡的日常里,许多人都写过致辞吧,致辞具有“礼仪性”或“仪式化”的...
经典高考升学宴主持词   尊敬的各位领导、各位嘉宾、各位亲朋好友:  大家好!8月,理想赤诚、热爱挚烈,8月,阳光灿烂、收...
中秋晚会主持稿 中秋晚会主持稿(精选5篇)  又到了一个激动人心的好日子!中秋合家团圆,是中华民族的传统习俗。下面是...
男孩满月酒主持词 男孩满月酒主持词  主持词要注意活动对象,针对活动对象写相应的主持词。在各种集会、活动不断增多的社会...
婚礼司仪主持词简短版 婚礼司仪主持词简短版  借鉴诗词和散文诗是主持词的一种写作手法。在人们积极参与各种活动的今天,各种集...
培训主持词 【精华】培训主持词八篇  借鉴诗词和散文诗是主持词的一种写作手法。在当今不断发展的世界,很多晚会、集...
婚礼主持词完整版 2017婚礼主持词(完整版)  无论新人举行什么样形式的婚礼,婚礼主持人是必不能少的。那么婚礼司仪全...
《哈利波特》的经典语录台词 《哈利波特》的经典语录台词  “就看你的了,哈利,要使他们看到,作为一名找球手,单靠一个有钱的爸爸是...
前任2备胎反击战经典台词 前任2备胎反击战经典台词  1、一见钟情太肤浅,日久生情才是真。  2、再深的感情也敌不过缘分的交错...
生日宴会主持词开场白 生日宴会主持词开场白(精选19篇)  【导语】一个好的活动开展,主持人的开场一定要和活动的主题相契合...
大学军训汇报表演主持词 大学军训汇报表演主持词  军训汇演是必不可少的,下面unjs小编整理了大学军训汇报表演主持词,欢迎阅...
闭幕词 闭幕词(通用10篇)  闭幕词,是会议的主要领导人代表会议举办单位,在会议闭幕时的讲话。其内容一般是...
班歌串词 班歌串词尊敬的领导、亲爱的同学们:大家上午好!(合)请全体起来,齐唱《美佛儿校歌》请坐!今天我们隆重...
幼儿园元旦活动主持词开场白   一、主持人开场白:  (亲爱的爸爸妈妈,小朋友们,大家新年好!因为您的孩子,我们走到了一起,形成...
生日主持主持词 精选生日主持主持词4篇  主持词要尽量增加文化内涵、寓教于乐,不断提高观众的文化知识和素养。在如今这...
开业庆典主持词 开业庆典主持词  什么是主持词?  主持词是主持人对各种晚会背诵已经准备好的稿子,或眼看提示器说出,...
新职工欢迎会主持词 新职工欢迎会主持词  主持词已成为各种演出活动和集会中不可或缺的一部分。在当下的中国社会,主持人的需...
颁奖晚会主持词 颁奖晚会主持词集合7篇  主持词可以采用和历史文化有关的表述方法去写作以提升活动的文化内涵。随着社会...
最新员工激励大会主持词 最新员工激励大会主持词  根据活动对象的不同,需要设置不同的主持词。在现今人们越来越重视活动氛围的社...