Files
SecurityDefenseDevice/app/src/main/java/com/example/defenseapplication/liveVideo/LiveVideoActivity.kt
2026-05-08 09:59:54 +08:00

456 lines
15 KiB
Kotlin

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.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.appcompat.app.AppCompatActivity
import androidx.compose.ui.test.isEnabled
import androidx.core.content.ContextCompat
import com.example.defenseapplication.R
import com.example.defenseapplication.databinding.LiveVideoActivityBinding
import com.gyf.immersionbar.ImmersionBar
import com.gyf.immersionbar.ktx.destroyImmersionBar
import java.io.File
import java.io.IOException
private const val TAG = "LiveVideoActivity"
class LiveVideoActivity : AppCompatActivity(), SurfaceHolder.Callback {
private lateinit var binding: LiveVideoActivityBinding
private var mediaPlayer: MediaPlayer? = null
private var isFullscreen = false
private var isPlaying = false
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 ->
if (isGranted) {
startRecordingMode()
} else {
Toast.makeText(this, "需要录音权限才能使用控制功能", Toast.LENGTH_SHORT).show()
isRecording = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LiveVideoActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
// ImmersionBar核心调用,沉浸式
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
initSurfaceView()
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, "视频准备完成", Toast.LENGTH_SHORT).show()
}
setOnCompletionListener {
this@LiveVideoActivity.isPlaying = false
Toast.makeText(this@LiveVideoActivity, "播放完成", Toast.LENGTH_SHORT).show()
}
setOnErrorListener { _, what, extra ->
Toast.makeText(this@LiveVideoActivity, "播放错误: $what, $extra", Toast.LENGTH_SHORT).show()
releaseMediaPlayer()
true
}
setOnBufferingUpdateListener { _, percent ->
if (percent < 100) {
Toast.makeText(this@LiveVideoActivity, "缓冲中: $percent%", Toast.LENGTH_SHORT).show()
}
}
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this@LiveVideoActivity, "无法加载视频", Toast.LENGTH_SHORT).show()
}
}
}
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
}
private fun initListener() {
binding.btnBack.setOnClickListener {
finish()
}
binding.ivMenu.setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
binding.ivFullscreen.setOnClickListener {
toggleFullscreen()
}
binding.surfaceView.setOnClickListener {
togglePlayPause()
}
binding.llVoice.setOnClickListener {
toggleVoice()
}
binding.llLight.setOnClickListener {
toggleLight()
}
binding.llRecord.setOnClickListener {
toggleRecording()
}
binding.llAlarmLog.setOnClickListener {
Toast.makeText(this, "查看告警日志", Toast.LENGTH_SHORT).show()
}
// 点击xq箭头跳转到AlarmLogActivity
binding.xq.setOnClickListener {
val intent = Intent(this, AlarmLogActivity::class.java)
startActivity(intent)
}
}
private fun togglePlayPause() {
mediaPlayer?.let { mp ->
if (mp.isPlaying) {
mp.pause()
isPlaying = false
Toast.makeText(this, "已暂停", Toast.LENGTH_SHORT).show()
} else {
mp.start()
isPlaying = true
Toast.makeText(this, "继续播放", 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, "已进入全屏", 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, "已退出全屏", 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, "声音已开启", Toast.LENGTH_SHORT).show()
} else {
mediaPlayer?.setVolume(0.0f, 0.0f)
binding.ivVoice.setImageResource(com.example.defenseapplication.R.drawable.voice)
Toast.makeText(this, "声音已关闭", Toast.LENGTH_SHORT).show()
}
}
private fun toggleLight() {
isLightOn = !isLightOn
if (isLightOn) {
binding.ivLight.setImageResource(com.example.defenseapplication.R.drawable.light_on_open)
Toast.makeText(this, "灯光已开启", Toast.LENGTH_SHORT).show()
} else {
binding.ivLight.setImageResource(com.example.defenseapplication.R.drawable.light_on)
Toast.makeText(this, "灯光已关闭", Toast.LENGTH_SHORT).show()
}
}
private fun toggleRecording() {
if (!isRecording) {
if (checkRecordAudioPermission()) {
startRecordingMode()
} else {
requestRecordAudioPermission()
}
} else {
stopRecordingMode()
}
}
private fun checkRecordAudioPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
}
private fun requestRecordAudioPermission() {
requestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
private fun startRecordingMode() {
isRecording = true
binding.ivRecord.setImageResource(com.example.defenseapplication.R.drawable.copy_open)
// 隐藏告警日志区域,显示控制台区域
binding.llAlarmLog.visibility = View.GONE
binding.consoleBar.visibility = View.VISIBLE
// 麦克风切换成开启状态
binding.ivVoice.setImageResource(com.example.defenseapplication.R.drawable.voice_on_open)
isVoiceEnabled = true
// 启动语音录制
startVoiceRecording()
// 启动麦克风脉冲动画
//startMicPulseAnimation()
Toast.makeText(this, "开始控制模式", Toast.LENGTH_SHORT).show()
}
private fun stopRecordingMode() {
isRecording = false
binding.ivRecord.setImageResource(com.example.defenseapplication.R.drawable.copy)
// 显示告警日志区域,隐藏控制台区域
binding.llAlarmLog.visibility = View.VISIBLE
binding.consoleBar.visibility = View.GONE
// 麦克风切换成关闭状态
binding.ivVoice.setImageResource(com.example.defenseapplication.R.drawable.voice)
isVoiceEnabled = false
// 停止语音录制
stopVoiceRecording()
// 停止麦克风脉冲动画
stopMicPulseAnimation()
Toast.makeText(this, "停止控制模式", 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 {
binding.ivVoice.clearAnimation()
micPulseAnimation = null
}
}
private fun startVoiceRecording() {
mediaRecorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
val outputDir = getExternalFilesDir(null) ?: filesDir
val audioDir = File(outputDir, "audio")
if (!audioDir.exists()) {
audioDir.mkdirs()
}
val outputFile = File(audioDir, "voice_record_${System.currentTimeMillis()}.3gp")
setOutputFile(outputFile.absolutePath)
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
try {
prepare()
start()
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(this@LiveVideoActivity, "录音启动失败", Toast.LENGTH_SHORT).show()
}
}
}
private fun stopVoiceRecording() {
mediaRecorder?.let {
try {
it.stop()
it.release()
} catch (e: Exception) {
e.printStackTrace()
}
mediaRecorder = null
}
}
private fun releaseMediaPlayer() {
mediaPlayer?.let {
if (it.isPlaying) {
it.stop()
}
it.release()
mediaPlayer = null
}
}
override fun onPause() {
super.onPause()
mediaPlayer?.pause()
}
override fun onResume() {
super.onResume()
mediaPlayer?.start()
}
override fun onDestroy() {
releaseMediaPlayer()
super.onDestroy()
}
}