文章目录
- [Android VideoView总结](#Android VideoView总结)
Android VideoView总结
概述
VideoView 是 Android 原生轻量级视频播放控件,基于系统 MediaPlayer 封装,自动完成初始化、渲染、状态监听、资源回收,无需底层适配,是原生轻量视频播放最优方案。
实现

FullVideoView
VideoView 在 measure 阶段有个"自动等比修正"逻辑:例如:当宽高都是固定值(match_parent + 200dp)时,它会悄悄把自身尺寸改成和视频原始比例一致------比如 16:9 视频在 200dp 高的横条里,实际宽度会被算成 200dp × 16/9,导致左右多出一截黑底,看起来就是"视频没铺满"。
就算你设了裁剪模式,它也只是在缩放后的 view 尺寸内有效,view 本身先缩了,自然铺不满。
kotlin
class FillVideoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : VideoView(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
// 始终用布局给定的宽高,不做 VideoView 默认的等比缩放修正
setMeasuredDimension(width, height)
}
}
代码
dart
class MainActivity : AppCompatActivity() {
private lateinit var videoView: FillVideoView
private lateinit var tvProgress: TextView
private lateinit var loadingView: ProgressBar
private lateinit var tvBuffering: TextView
private lateinit var btnStop: Button
private lateinit var btnStartPause: Button
private lateinit var btnVolumeUp: Button
private lateinit var btnVolumeDown: Button
private lateinit var btnVolumeMax: Button
private lateinit var btnVolumeMute: Button
private val audioManager by lazy { getSystemService(AUDIO_SERVICE) as android.media.AudioManager }
private val handler = Handler(Looper.getMainLooper())
private lateinit var progressRunnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
initVideoView()
initProgressListener()
}
private fun initView() {
loadingView = findViewById(R.id.loading_view)
videoView = findViewById(R.id.video_view)
tvProgress = findViewById(R.id.tv_progress)
tvBuffering = findViewById(R.id.tv_buffering)
btnStartPause = findViewById(R.id.btn_start_pause)
btnStop = findViewById(R.id.btn_stop)
btnVolumeUp = findViewById(R.id.btn_volume_up)
btnVolumeDown = findViewById(R.id.btn_volume_down)
btnVolumeMax = findViewById(R.id.btn_volume_max)
btnVolumeMute = findViewById(R.id.btn_volume_mute)
btnStartPause.setOnClickListener {
if (videoView.isPlaying) {
// 暂停操作
pauseVideo()
} else {
// 播放操作
playVideo()
}
}
btnStop.setOnClickListener {
stopVideo()
}
btnVolumeUp.setOnClickListener { volumeUp() }
btnVolumeDown.setOnClickListener { volumeDown() }
btnVolumeMax.setOnClickListener { volumeMax() }
btnVolumeMute.setOnClickListener { volumeMute() }
}
/**
* 设置url
*/
private fun setVideoUrl() {
val videoUrl =
"https://stream7.iqilu.com/10339/upload_transcode/202002/09/20200209104902N3v5Vpxuvb.mp4"
videoView.setVideoURI(videoUrl.toUri())
}
/**
* 播放视频
*/
private fun playVideo() {
btnStartPause.text = "暂停"
videoView.start()
}
/**
* 暂停
*/
private fun pauseVideo() {
btnStartPause.text = "播放"
videoView.pause()
}
/**
* 停止
*/
private fun stopVideo() {
videoView.stopPlayback()
btnStartPause.text = "播放"
tvProgress.text = null
tvBuffering.text = null
setVideoUrl()
}
/**
* 增加音量
*/
private fun volumeUp() {
audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI
)
}
/**
* 减少音量
*/
private fun volumeDown() {
audioManager.adjustStreamVolume(
AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_LOWER,
AudioManager.FLAG_SHOW_UI
)
}
/**
* 最大音量
*/
private fun volumeMax() {
val maxVol = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVol, AudioManager.FLAG_SHOW_UI)
}
/**
* 静音
*/
private fun volumeMute() {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, AudioManager.FLAG_SHOW_UI)
}
private fun showLoading() {
loadingView.visibility = View.VISIBLE
}
private fun hideLoading() {
loadingView.visibility = View.GONE
}
private fun initVideoView() {
videoView.setOnInfoListener { _, what, _ ->
when (what) {
MediaPlayer.MEDIA_INFO_BUFFERING_START -> {
// 播放中缓冲不足(重新缓冲)
tvBuffering.text = "缓冲中"
showLoading()
}
MediaPlayer.MEDIA_INFO_BUFFERING_END -> {
tvBuffering.text = "缓冲完成"
hideLoading()
}
}
true
}
// 首次加载(prepare 阶段)遮罩默认可见,黑屏期间显示 loading
videoView.setOnPreparedListener { mp ->
// 保持屏幕常量
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
// 设置视频画面在渲染区域里的缩放方式
mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
hideLoading()
btnStartPause.text = "暂停"
}
videoView.setOnErrorListener { _, what, extra ->
hideLoading()
tvBuffering.text = "播放出错(what=$what, extra=$extra)"
true
}
// 循环播放
videoView.setOnCompletionListener {
Log.e("TAG", "播放完成")
}
setVideoUrl()
playVideo()
}
private fun initProgressListener() {
progressRunnable = Runnable {
if (videoView.isPlaying) {
val current = videoView.currentPosition
val total = videoView.duration
if (total > 0) {
val progress = (current.toFloat() / total * 100).toInt()
tvProgress.text = "进度:${progress}"
if (progress >= 100) {
handler.removeCallbacksAndMessages(null)
}
}
}
handler.postDelayed(progressRunnable, 1000L)
}
handler.post(progressRunnable)
}
override fun onResume() {
super.onResume()
videoView.resume()
}
override fun onPause() {
super.onPause()
videoView.pause()
}
override fun onDestroy() {
super.onDestroy()
videoView.stopPlayback()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}