diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6bc384b..66e4918 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/liveVideo/LiveVideoActivity.kt b/app/src/main/java/com/example/defenseapplication/liveVideo/LiveVideoActivity.kt index aa67c87..bbbf1d4 100644 --- a/app/src/main/java/com/example/defenseapplication/liveVideo/LiveVideoActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/LiveVideoActivity.kt @@ -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) + } } diff --git a/app/src/main/res/layout/live_video_activity.xml b/app/src/main/res/layout/live_video_activity.xml index 0ef3109..be6562e 100644 --- a/app/src/main/res/layout/live_video_activity.xml +++ b/app/src/main/res/layout/live_video_activity.xml @@ -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 @@ - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="250dp" + android:background="@android:color/transparent"/>