视频播放1

This commit is contained in:
2026-05-27 11:58:26 +08:00
parent ac906c901b
commit 97a0cdc14f
4 changed files with 75 additions and 288 deletions

View File

@@ -81,12 +81,6 @@ dependencies {
implementation("com.github.bumptech.glide:glide:4.16.0")
// ZXing 二维码扫描库
implementation("com.google.zxing:core:3.5.2")
// ExoPlayer 核心库
implementation("androidx.media3:media3-exoplayer:1.9.0")
// ExoPlayer UI组件库提供PlayerView等
implementation("androidx.media3:media3-ui:1.9.0")
// ExoPlayer 通用库
implementation("androidx.media3:media3-common:1.9.0")
// 基础依赖包,必须要依赖
implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
// kotlin扩展可选
@@ -107,5 +101,7 @@ dependencies {
implementation("com.yanzhenjie.recyclerview:x:1.3.2")
//Android-PickerView-时间弹窗
implementation("com.contrarywind:Android-PickerView:4.1.9")
//基于exoPlayer 自定义播放器 JPlayer支持功能
implementation("com.ycjiang:VideoPlayModule-Lite:2.3.61")
}

View File

@@ -2,55 +2,40 @@ package com.example.defenseapplication.liveVideo
import android.Manifest
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.media.AudioManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.view.SurfaceHolder
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.view.animation.ScaleAnimation
import android.widget.Toast
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import chuangyuan.ycj.videolibrary.video.ExoUserPlayer
import com.example.defenseapplication.R
import com.example.defenseapplication.base.BaseActivity
import com.example.defenseapplication.databinding.LiveVideoActivityBinding
import com.example.defenseapplication.utils.ActivityManager
import com.example.defenseapplication.utils.DeviceIdEvent
import com.gyf.immersionbar.ImmersionBar
import com.gyf.immersionbar.ktx.destroyImmersionBar
import java.io.File
import java.io.IOException
import android.content.res.Configuration
import androidx.activity.OnBackPressedCallback
import chuangyuan.ycj.videolibrary.listener.VideoInfoListener
import chuangyuan.ycj.videolibrary.video.VideoPlayerManager
private const val TAG = "LiveVideoActivity"
//智能防御机A1
class LiveVideoActivity : BaseActivity(), SurfaceHolder.Callback {
class LiveVideoActivity : BaseActivity() {
private lateinit var binding: LiveVideoActivityBinding
private var mediaPlayer: MediaPlayer? = null
private var isFullscreen = false
private var isPlaying = false
private lateinit var exoPlayerManager: ExoUserPlayer
private var isVoiceEnabled = false
private var isLightOn = false
private var isRecording = false
private var mediaRecorder: MediaRecorder? = null
private var micPulseAnimation: ScaleAnimation? = null
private var videoWidth = 0
private var videoHeight = 0
private val testVideoUrl = "https://vjs.zencdn.net/v/oceans.mp4"
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
@@ -78,93 +63,55 @@ class LiveVideoActivity : BaseActivity(), SurfaceHolder.Callback {
binding.tvTitle.text = deviceName
}
initSurfaceView()
// 初始化播放器
initVideoPlayer()
initListener()
}
private fun initSurfaceView() {
binding.surfaceView.holder.addCallback(this)
}
override fun surfaceCreated(holder: SurfaceHolder) {
initMediaPlayer(holder)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
adjustSurfaceViewSize()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
releaseMediaPlayer()
}
private fun initMediaPlayer(holder: SurfaceHolder) {
mediaPlayer = MediaPlayer().apply {
setDisplay(holder)
setAudioStreamType(AudioManager.STREAM_MUSIC)
try {
setDataSource(this@LiveVideoActivity, Uri.parse(testVideoUrl))
prepareAsync()
setOnPreparedListener { mp ->
this@LiveVideoActivity.videoWidth = mp.videoWidth
this@LiveVideoActivity.videoHeight = mp.videoHeight
adjustSurfaceViewSize()
mp.isLooping = true
mp.start()
this@LiveVideoActivity.isPlaying = true
Toast.makeText(this@LiveVideoActivity, R.string.video_ready, Toast.LENGTH_SHORT).show()
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (exoPlayerManager.onBackPressed()) {
return
}
setOnCompletionListener {
this@LiveVideoActivity.isPlaying = false
Toast.makeText(this@LiveVideoActivity, R.string.playback_complete, Toast.LENGTH_SHORT).show()
}
setOnErrorListener { _, what, extra ->
Toast.makeText(this@LiveVideoActivity, getString(R.string.video_error_format, what, extra), Toast.LENGTH_SHORT).show()
releaseMediaPlayer()
true
}
setOnBufferingUpdateListener { _, percent ->
if (percent < 100) {
Toast.makeText(this@LiveVideoActivity, getString(R.string.buffering_format, percent), Toast.LENGTH_SHORT).show()
}
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this@LiveVideoActivity, R.string.cannot_load_video, Toast.LENGTH_SHORT).show()
// 无全屏时执行默认返回逻辑(如 finish
finish()
}
}
})
}
private fun initVideoPlayer() {
val videoPlayerView = binding.exoPlayContextId
private fun adjustSurfaceViewSize() {
if (videoWidth == 0 || videoHeight == 0) return
val screenWidth = resources.displayMetrics.widthPixels
val screenHeight = resources.displayMetrics.heightPixels
val aspectRatio = videoWidth.toFloat() / videoHeight.toFloat()
val params = binding.surfaceView.layoutParams
if (isFullscreen) {
if (screenWidth.toFloat() / screenHeight.toFloat() > aspectRatio) {
params.width = (screenHeight * aspectRatio).toInt()
params.height = screenHeight
} else {
params.width = screenWidth
params.height = (screenWidth / aspectRatio).toInt()
}
} else {
val containerHeight = binding.videoContainer.height
params.width = (containerHeight * aspectRatio).toInt()
params.height = containerHeight
}
binding.surfaceView.layoutParams = params
exoPlayerManager = VideoPlayerManager.Builder(VideoPlayerManager.TYPE_PLAY_GESTURE, videoPlayerView)
.setPlayUri("https://media.w3.org/2010/05/sintel/trailer.mp4")//真实地址
.setPosition(0)
.addVideoInfoListener(object : VideoInfoListener {
// 注意:此处的 p0: Long 是视频开始播放时的位置信息,可以按需使用
override fun onPlayStart(p0: Long) {
Log.d(TAG, "视频开始播放")
}
override fun onLoadingChanged() {
// 视频加载状态变化
}
override fun onPlayerError(p0: com.google.android.exoplayer2.ExoPlaybackException?) {
Toast.makeText(this@LiveVideoActivity, "播放失败", Toast.LENGTH_SHORT).show()
}
override fun onPlayEnd() {
Log.d(TAG, "播放结束")
}
// 修正点:库的接口要求必须实现 isPlaying 方法
override fun isPlaying(p0: Boolean) {
// p0 表示当前是否正在播放
}
fun onRepeatModeChanged(p0: Int) {
// 循环模式改变时的回调
}
})
.create()
exoPlayerManager.startPlayer()
}
private fun initListener() {
@@ -180,15 +127,6 @@ class LiveVideoActivity : BaseActivity(), SurfaceHolder.Callback {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
binding.ivFullscreen.setOnClickListener {
toggleFullscreen()
}
binding.surfaceView.setOnClickListener {
togglePlayPause()
}
binding.llVoice.setOnClickListener {
toggleVoice()
}
@@ -212,90 +150,14 @@ class LiveVideoActivity : BaseActivity(), SurfaceHolder.Callback {
}
}
private fun togglePlayPause() {
mediaPlayer?.let { mp ->
if (mp.isPlaying) {
mp.pause()
isPlaying = false
Toast.makeText(this, R.string.paused, Toast.LENGTH_SHORT).show()
} else {
mp.start()
isPlaying = true
Toast.makeText(this, R.string.resume_playback, Toast.LENGTH_SHORT).show()
}
}
}
private fun toggleFullscreen() {
isFullscreen = !isFullscreen
if (isFullscreen) {
enterFullscreen()
} else {
exitFullscreen()
}
}
private fun enterFullscreen() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
binding.topBar.visibility = View.GONE
binding.bitRateBar.visibility = View.GONE
binding.functionBar.visibility = View.GONE
val alarmLogParent = binding.llAlarmLog.parent.parent as View
alarmLogParent.visibility = View.GONE
val containerParams = binding.videoContainer.layoutParams
containerParams.width = ViewGroup.LayoutParams.MATCH_PARENT
containerParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.videoContainer.layoutParams = containerParams
val surfaceParams = binding.surfaceView.layoutParams
surfaceParams.width = resources.displayMetrics.widthPixels
surfaceParams.height = resources.displayMetrics.heightPixels
binding.surfaceView.layoutParams = surfaceParams
binding.ivFullscreen.setImageResource(com.example.defenseapplication.R.drawable.back)
Toast.makeText(this, R.string.entered_fullscreen, Toast.LENGTH_SHORT).show()
}
private fun exitFullscreen() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
binding.topBar.visibility = View.VISIBLE
binding.bitRateBar.visibility = View.VISIBLE
binding.functionBar.visibility = View.VISIBLE
val alarmLogParent = binding.llAlarmLog.parent.parent as View
alarmLogParent.visibility = View.VISIBLE
val containerParams = binding.videoContainer.layoutParams
containerParams.width = ViewGroup.LayoutParams.MATCH_PARENT
containerParams.height = (220 * resources.displayMetrics.density).toInt()
binding.videoContainer.layoutParams = containerParams
adjustSurfaceViewSize()
binding.ivFullscreen.setImageResource(com.example.defenseapplication.R.drawable.full_screen)
Toast.makeText(this, R.string.exited_fullscreen, Toast.LENGTH_SHORT).show()
}
private fun toggleVoice() {
isVoiceEnabled = !isVoiceEnabled
if (isVoiceEnabled) {
mediaPlayer?.setVolume(1.0f, 1.0f)
binding.ivVoice.setImageResource(com.example.defenseapplication.R.drawable.voice_on_open)
Toast.makeText(this, R.string.sound_on, Toast.LENGTH_SHORT).show()
} else {
mediaPlayer?.setVolume(0.0f, 0.0f)
binding.ivVoice.setImageResource(com.example.defenseapplication.R.drawable.voice)
Toast.makeText(this, R.string.sound_off, Toast.LENGTH_SHORT).show()
}
@@ -378,20 +240,6 @@ class LiveVideoActivity : BaseActivity(), SurfaceHolder.Callback {
Toast.makeText(this, R.string.stop_control_mode, Toast.LENGTH_SHORT).show()
}
// private fun startMicPulseAnimation() {
// micPulseAnimation = ScaleAnimation(
// 1.0f, 1.3f,
// 1.0f, 1.3f,
// Animation.RELATIVE_TO_SELF, 0.5f,
// Animation.RELATIVE_TO_SELF, 0.5f
// ).apply {
// duration = 500
// repeatCount = Animation.INFINITE
// repeatMode = Animation.REVERSE
// interpolator = LinearInterpolator()
// }
// binding.ivVoice.startAnimation(micPulseAnimation)
// }
private fun stopMicPulseAnimation() {
micPulseAnimation?.let {
@@ -434,32 +282,26 @@ class LiveVideoActivity : BaseActivity(), SurfaceHolder.Callback {
mediaRecorder = null
}
}
private fun releaseMediaPlayer() {
mediaPlayer?.let {
if (it.isPlaying) {
it.stop()
}
it.release()
mediaPlayer = null
}
override fun onResume() {
super.onResume()
exoPlayerManager.onResume()
}
override fun onPause() {
super.onPause()
mediaPlayer?.pause()
}
override fun onResume() {
super.onResume()
mediaPlayer?.start()
exoPlayerManager.onPause()
}
override fun onDestroy() {
releaseMediaPlayer()
super.onDestroy()
exoPlayerManager.onDestroy()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 横竖屏切换时通知播放器
exoPlayerManager.onConfigurationChanged(newConfig)
}
}

View File

@@ -4,6 +4,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F6F6"
android:name=".liveVideo.LiveVideoActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:screenOrientation="portrait"
android:orientation="vertical">
<!-- 顶部导航栏 -->
@@ -41,68 +44,12 @@
</LinearLayout>
<!-- 视频流信息 -->
<LinearLayout
android:id="@+id/bitRateBar"
android:layout_width="match_parent"
android:layout_height="32dp"
android:background="#FFFFFF"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:id="@+id/tvBitRate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="120 B/S"
android:textColor="@color/black"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 视频播放区域 -->
<FrameLayout
android:id="@+id/videoContainer"
<chuangyuan.ycj.videolibrary.widget.VideoPlayerView
android:id="@+id/exoPlayContextId"
android:layout_width="match_parent"
android:layout_height="220dp"
android:background="#000000">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<!-- 全屏按钮 -->
<ImageView
android:id="@+id/ivFullscreen"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/full_screen"/>
<!-- 播放控制按钮 -->
<LinearLayout
android:id="@+id/playControl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ImageView
android:id="@+id/ivPlay"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/full_screen" />
</LinearLayout>
</FrameLayout>
android:layout_height="250dp"
android:background="@android:color/transparent"/>
<!-- 功能按钮区域 -->
<LinearLayout