[6.8.0][驾驶位视频流] 优化代码逻辑

This commit is contained in:
renwj
2024-11-28 11:46:13 +08:00
parent c50b69f14d
commit 1790758b08
4 changed files with 194 additions and 38 deletions

View File

@@ -1,10 +1,13 @@
package com.mogo.eagle.core.function.datacenter.autopilot.telematic
import android.os.SystemClock
import android.util.Log
import androidx.core.view.ViewCompat
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.protobuf.TextFormat
import com.mogo.commons.storage.SharedPrefsMgr
import com.mogo.commons.utils.MogoAnalyticUtils
import com.mogo.eagle.core.data.app.AppConfigInfo
import com.mogo.eagle.core.data.autopilot.AutopilotControlParameters
import com.mogo.eagle.core.data.autopilot.AutopilotControlParameters.AutoPilotLine
@@ -28,8 +31,11 @@ import com.mogo.eagle.core.function.call.och.CallerOchBizFunctionCall4EagleManag
import com.mogo.eagle.core.function.call.telematic.CallerTelematicListenerManager
import com.mogo.eagle.core.function.call.telematic.CallerTelematicManager
import com.mogo.eagle.core.function.call.v2x.CallerTrafficLightListenerManager
import com.mogo.eagle.core.utilcode.kotlin.lifeCycleScope
import com.mogo.eagle.core.utilcode.kotlin.scope
import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger
import com.mogo.eagle.core.utilcode.mogo.logger.scene.SceneConstant
import com.mogo.eagle.core.utilcode.util.AppStateManager
import com.mogo.eagle.core.utilcode.util.GsonUtils
import com.mogo.eagle.core.utilcode.util.ThreadUtils
import com.mogo.eagle.core.utilcode.util.ToastUtils
@@ -41,11 +47,14 @@ import com.zhidao.support.adas.high.AdasManager
import com.zhjt.service.chain.ChainLog
import io.netty.channel.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import mogo.telematics.pad.MessagePad
import mogo.telematics.pad.MessagePad.TrackedObject
import java.nio.charset.Charset
import java.util.Collections
import java.util.concurrent.TimeUnit
class TeleMsgHandler : IMsgHandler {
@@ -70,6 +79,8 @@ class TeleMsgHandler : IMsgHandler {
private var listener: EventListener? = null
private var lastTimeOnReceiveDriveVideo = 0L
override fun handleMsgFromServer(msg: MogoProtocolMsg?, channel: Channel?) {
msg?.let { it ->
if (it.protocolType == TelematicConstant.V2N_AI_ROAD_DATA_TO_PASSENGER) {
@@ -163,35 +174,96 @@ class TeleMsgHandler : IMsgHandler {
val content = String(it.body, Charset.defaultCharset())
Log.d(TAG, "乘客屏收到司机屏转发的驾驶位视频流开关 --- 2 ---:$content")
val data = GsonUtils.fromJson(content, Map::class.java)
val open = data["open"].toString().toInt() == 1
val playUrl = data["playUrl"]?.toString()
//TODO yangyakun
if (open) {
if (playUrl != null) {
// 1. 获取视频播放控件
val target = CallerDevaToolsManager.driveSeatVideoProvider()?.getDriveVideoView(playUrl) { event ->
if (data.containsKey("open")) {
val open = data["open"].toString().toInt() == 1
val playUrl = data["playUrl"]?.toString()
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 0
it["open"] = open
it["playUrl"] = playUrl ?: ""
})
AppStateManager.currentActivity()?.lifeCycleScope?.launchWhenResumed {
if (open) {
if (playUrl != null) {
// 1. 获取视频播放控件
val target = CallerDevaToolsManager.driveSeatVideoProvider()?.getDriveVideoView(playUrl) { event ->
Log.d(TAG, "event -> $event")
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 3
it["open"] = true
it["playUrl"] = playUrl
it["event"] = event
})
}
if (target != null) {
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 1
it["open"] = true
it["playUrl"] = playUrl
})
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, "11".toByteArray())
} else {
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 2
it["open"] = true
it["playUrl"] = playUrl
})
}
if (target != null && !ViewCompat.isAttachedToWindow(target) && (lastTimeOnReceiveDriveVideo == 0L || SystemClock.elapsedRealtime() - lastTimeOnReceiveDriveVideo > TimeUnit.SECONDS.toMillis(10))) {
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 4
it["open"] = true
it["playUrl"] = playUrl
})
CallerDevaToolsManager.driveSeatVideoProvider()?.poller()?.onEach {
Log.d(TAG, "--- poller -> $it")
if (!it) {
// 设备离线了,乘客屏的驾驶位视频控件要移除
CallerOchBizFunctionCall4EagleManager.setVideoView(null)
}
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, if (it) "1".toByteArray() else "0".toByteArray())
}?.launchIn(target.scope)
CallerOchBizFunctionCall4EagleManager.setVideoView(target)
lastTimeOnReceiveDriveVideo = SystemClock.elapsedRealtime()
}
} else {
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 5
it["open"] = true
it["playUrl"] = ""
})
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, "10".toByteArray())
}
} else {
//第1个0代表运营面板开关关闭第2个1代表关闭成功告之司机端; 相应的还有状态00表示关闭失败
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, "01".toByteArray())
CallerOchBizFunctionCall4EagleManager.setVideoView(null)
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 6
it["open"] = false
it["playUrl"] = ""
})
}
if (target != null) {
// 2. 添加到一个ViewGroup上
// 11: 第1个1代表运营面板开关打开第2个1代表打开成功告之司机端
// 10: 第1个1代表运营面板开关打开第2个0代表打开成功告之司机端
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, "11".toByteArray())
}
CallerOchBizFunctionCall4EagleManager.setVideoView(target)
} else {
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, "10".toByteArray())
}
} else {
//第1个0代表运营面板开关关闭第2个1代表关闭成功告之司机端; 相应的还有状态00表示关闭失败
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, "01".toByteArray())
CallerOchBizFunctionCall4EagleManager.setVideoView(null)
} else if (data.containsKey("query")) {
val isPlaying = CallerDevaToolsManager.driveSeatVideoProvider()?.isVideoPlaying()
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 8
it["isPlaying"] = isPlaying ?: false
})
Log.e(TAG, "乘客屏收到司机屏转发的驾驶位视频流开关--- 4 ---: isPlaying -> $isPlaying")
CallerTelematicManager.sendMsgToServer(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_RSP, if (isPlaying == true) "111".toByteArray() else "000".toByteArray())
}
} catch (t: Throwable) {
t.printStackTrace()
Log.e(TAG, "乘客屏收到司机屏转发的驾驶位视频流开关--- 3 ---", t)
MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap<String, Any>().also {
it["step"] = 7
it["error"] = t.message ?: "未知异常"
})
}
return
}

View File

@@ -8,7 +8,7 @@ import androidx.core.view.doOnAttach
import androidx.core.view.doOnDetach
import com.mogo.commons.constants.HostConst
import com.mogo.eagle.core.data.BaseResponse
import com.mogo.eagle.core.data.app.AppConfigInfo
import com.mogo.eagle.core.data.config.FunctionBuildConfig
import com.mogo.eagle.core.function.api.driver.video.IDriveSeatVideoProvider
import com.mogo.eagle.core.function.api.driver.video.IDriveSeatVideoProvider.Event
import com.mogo.eagle.core.function.api.driver.video.IDriveSeatVideoProvider.Event.Failed
@@ -16,6 +16,7 @@ import com.mogo.eagle.core.function.api.driver.video.IDriveSeatVideoProvider.Eve
import com.mogo.eagle.core.function.api.driver.video.IDriveSeatVideoProvider.Event.Playing
import com.mogo.eagle.core.function.call.datacenter.CallerDataCenterBizListener
import com.mogo.eagle.core.network.MoGoRetrofitFactory
import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils
import com.mogo.eagle.core.utilcode.util.AppStateManager
import com.tencent.liteav.basic.log.TXCLog
import com.tencent.rtmp.ITXLivePlayListener
@@ -26,31 +27,39 @@ import com.tencent.rtmp.ui.TXCloudVideoView
import com.zhjt.mogo_core_function_devatools.driver.video.vo.VideoUrlData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
class DriveSeatVideoProviderImpl : IDriveSeatVideoProvider {
companion object {
private const val TAG = "DriveSeatVideoProvider"
}
@Volatile
private var target: WeakReference<TXCloudVideoView>? = null
@Volatile private var target: WeakReference<TXCloudVideoView>? = null
@Volatile
private var timer: Job? = null
@Volatile private var timer: Job? = null
private val scope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
private val chl by lazy { AtomicReference(Channel<Boolean>(Channel.CONFLATED)) }
private var data: VideoUrlData? = null
private val isPlaying by lazy { AtomicBoolean(false) }
internal interface IVideoLiveUrlApi {
@Headers("Content-Type:application/json;charset=UTF-8")
@GET("/eagleEye-mis/camera/monitor/watch/status")
@@ -82,6 +91,7 @@ class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
Log.d(TAG, "getDriveVideoView --> $playUrl")
x.doOnAttach {
Log.d(TAG, "onAttachToWindow --> $playUrl")
start()
val player = TXLivePlayer(activity)
player.setPlayerView(x)
player.setMute(true)
@@ -100,12 +110,13 @@ class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
playCallback?.invoke(Loading)
} else if (event == TXLiveConstants.PLAY_EVT_PLAY_BEGIN) {
Log.d(TAG, "play begin...")
start()
isPlaying.set(true)
playCallback?.invoke(Playing)
} else {
if (event < 0) {
Log.d(TAG, "play failed...$event, bundle: $bundle")
stop()
isPlaying.set(false)
playCallback?.invoke(Failed(bundle?.toString() ?: "播放失败"))
}
}
@@ -122,6 +133,7 @@ class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
player.stopPlay(true)
stop()
} finally {
isPlaying.set(false)
target?.clear()
}
}
@@ -135,6 +147,27 @@ class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
return data
}
@OptIn(ExperimentalCoroutinesApi::class) override fun poller(): Flow<Boolean> = channelFlow {
if (AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode)) {
throw AssertionError("此方法不支持司机屏使用")
}
val ch = chl.get()?.let {
if (it.isClosedForSend || it.isClosedForReceive) {
Log.d(TAG, "isClosed -> ${it.isClosedForReceive} -> ${it.isClosedForSend}")
val n = Channel<Boolean>(Channel.CONFLATED)
chl.set(n)
n
} else {
it
}
} ?: return@channelFlow
val iterator = ch.iterator()
while (iterator.hasNext()) {
send(iterator.next())
}
}
private fun start() {
timer?.cancel()
scope.launch {
@@ -144,6 +177,9 @@ class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
val result = requestVideoInfo()
if (result != null) {
data = result
chl.get()?.trySend(true)
} else {
chl.get()?.trySend(false)
}
Log.d(TAG, "当次请求结束...")
delay(5000)
@@ -157,6 +193,13 @@ class DriveSeatVideoProviderImpl: IDriveSeatVideoProvider {
timer?.cancel()
}
override fun isVideoPlaying(): Boolean {
if (AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode)) {
throw AssertionError("此方法不支持司机屏使用")
}
return isPlaying.get()
}
private fun getApi(): IVideoLiveUrlApi? {
return MoGoRetrofitFactory.getInstanceNoCallAdapter(HostConst.HOST_RELEASE).create(IVideoLiveUrlApi::class.java)
}

View File

@@ -2,6 +2,7 @@ package com.mogo.eagle.core.function.hmi.ui.operate
import android.content.Context
import android.os.Bundle
import android.os.SystemClock
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
@@ -63,17 +64,16 @@ import com.mogo.eagle.core.utilcode.mogo.logger.Logger
import com.mogo.eagle.core.utilcode.mogo.vehicle.SweeperVehicleConfigUtils
import com.mogo.eagle.core.utilcode.rv.divider.CommonDividerItemDecoration
import com.mogo.eagle.core.utilcode.util.AppStateManager
import com.mogo.eagle.core.utilcode.util.GsonUtils
import com.mogo.eagle.core.utilcode.util.GsonUtils.*
import com.mogo.eagle.core.utilcode.util.ToastUtils
import com.zhjt.mogo.adas.data.AdasConstants
import com.zhjt.mogo.adas.data.bean.AdasParam
import kotlinx.android.synthetic.main.layout_operate_panel.view.iv_operate_panel_close
import kotlinx.android.synthetic.main.layout_operate_panel_preference_widget_switch_compat.switchWidget
import kotlinx.coroutines.launch
import me.jessyan.autosize.utils.AutoSizeUtils
import mogo.telematics.pad.MessagePad
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import kotlin.math.min
class OperatePanelLayout : LinearLayout {
@@ -690,13 +690,21 @@ class OperatePanelLayout : LinearLayout {
private const val KEY_DRIVE_SEAT_VIDEO_STREAM = "drive_seat_video_stream"
}
private var lastTimeOnSendDriveVideo = 0L
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
CallerTelematicListenerManager.addListener(TAG, this)
lifecycleScope.launchWhenResumed {
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also {
val p = preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also {
it.isEnabled = CallerDevaToolsManager.driveSeatVideoProvider()?.requestVideoInfo()?.livePlayUrl?.isNotEmpty() ?: false
}
if (FunctionBuildConfig.isDriveSeatVideoStream && p?.isEnabled == true) {
val map = HashMap<String, String>()
map["query"] = "1"
CallerTelematicManager.sendMsgToAllClients(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_REQ, toJson(map).toByteArray())
}
}
}
@@ -789,16 +797,38 @@ class OperatePanelLayout : LinearLayout {
when(s) {
"01" -> {
//关闭成功
Log.d(TAG, "-- 驾驶位视频流关闭成功 --")
FunctionBuildConfig.isDriveSeatVideoStream = false
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { changeValue(it, false) }
}
"11" -> {
//打开成功
Log.d(TAG, "-- 驾驶位视频流打开成功 --")
FunctionBuildConfig.isDriveSeatVideoStream = true
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { changeValue(it, true) }
}
else -> {
//关闭失败或打开失败
if ("0" == s) {
Log.d(TAG, "-- 设备下线 --")
FunctionBuildConfig.isDriveSeatVideoStream = false
// 设备下线
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also {
it.isChecked = false
it.isEnabled = false
}
} else if ("1" == s) {
// 设备在线
Log.d(TAG, "-- 设备上线 --")
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.isEnabled = true
} else if ("000" == s) {
// 查询乘客屏播放状态-未播放
Log.d(TAG, "-- 乘客屏当前处于未播放状态 --")
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.isChecked = false
} else if ("111" == s) {
// 查询乘客屏播放状态-正在播放
Log.d(TAG, "-- 乘客屏当前处于播放状态 --")
preferenceScreen.findPreferenceReal<SwitchPreferenceCompat>(KEY_DRIVE_SEAT_VIDEO_STREAM)?.isChecked = true
}
}
}
}
@@ -896,6 +926,11 @@ class OperatePanelLayout : LinearLayout {
return true
}
KEY_DRIVE_SEAT_VIDEO_STREAM -> {
if (lastTimeOnSendDriveVideo != 0L && SystemClock.elapsedRealtime() - lastTimeOnSendDriveVideo <= TimeUnit.SECONDS.toMillis(10)) {
ToastUtils.showShort("未收到乘客屏响应,一会儿再试~")
return false
}
lastTimeOnSendDriveVideo = SystemClock.elapsedRealtime()
val isChecked = newValue as? Boolean ?: false
clickEventAnalytics("视频流驾驶位开关", isChecked)
val map = HashMap<String, String>()
@@ -903,11 +938,6 @@ class OperatePanelLayout : LinearLayout {
val playUrl = CallerDevaToolsManager.driveSeatVideoProvider()?.getLastData()?.livePlayUrl ?: ""
map["playUrl"] = playUrl
CallerTelematicManager.sendMsgToAllClients(TelematicConstant.DRIVE_SEAT_VIDEO_STREAM_REQ, toJson(map).toByteArray())
// CallerDevaToolsManager.driveSeatVideoProvider()?.getDriveVideoView(playUrl) { event ->
// Log.d(TAG, "event -> $event")
// }?.also {
// showVideoView(it)
// }
return false
}
}

View File

@@ -2,6 +2,7 @@ package com.mogo.eagle.core.function.api.driver.video
import android.view.View
import com.zhjt.mogo_core_function_devatools.driver.video.vo.VideoUrlData
import kotlinx.coroutines.flow.Flow
interface IDriveSeatVideoProvider {
@@ -23,6 +24,16 @@ interface IDriveSeatVideoProvider {
fun getLastData(): VideoUrlData?
/**
* 是否在线
*/
fun poller(): Flow<Boolean>
/**
* 视频是否正在播放
*/
fun isVideoPlaying(): Boolean
sealed class Event {
object Loading: Event()