456 lines
15 KiB
Kotlin
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()
|
|
}
|
|
|
|
|
|
}
|
|
|