From 1790758b08f2c5840cc3666c2ce031b5e2a53616 Mon Sep 17 00:00:00 2001 From: renwj Date: Thu, 28 Nov 2024 11:46:13 +0800 Subject: [PATCH] =?UTF-8?q?[6.8.0][=E9=A9=BE=E9=A9=B6=E4=BD=8D=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=B5=81]=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../autopilot/telematic/TeleMsgHandler.kt | 116 ++++++++++++++---- .../video/DriveSeatVideoProviderImpl.kt | 57 +++++++-- .../hmi/ui/operate/OperatePanelLayout.kt | 48 ++++++-- .../driver/video/IDriveSeatVideoProvider.kt | 11 ++ 4 files changed, 194 insertions(+), 38 deletions(-) diff --git a/core/function-impl/mogo-core-function-datacenter/src/main/java/com/mogo/eagle/core/function/datacenter/autopilot/telematic/TeleMsgHandler.kt b/core/function-impl/mogo-core-function-datacenter/src/main/java/com/mogo/eagle/core/function/datacenter/autopilot/telematic/TeleMsgHandler.kt index d4a3c8f395..0eca37e334 100644 --- a/core/function-impl/mogo-core-function-datacenter/src/main/java/com/mogo/eagle/core/function/datacenter/autopilot/telematic/TeleMsgHandler.kt +++ b/core/function-impl/mogo-core-function-datacenter/src/main/java/com/mogo/eagle/core/function/datacenter/autopilot/telematic/TeleMsgHandler.kt @@ -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().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().also { + it["step"] = 3 + it["open"] = true + it["playUrl"] = playUrl + it["event"] = event + }) + } + if (target != null) { + MogoAnalyticUtils.track("DriveSeatVideoStream", HashMap().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().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().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().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().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().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().also { + it["step"] = 7 + it["error"] = t.message ?: "未知异常" + }) } return } diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/driver/video/DriveSeatVideoProviderImpl.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/driver/video/DriveSeatVideoProviderImpl.kt index c66f53c011..af4af7a3a6 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/driver/video/DriveSeatVideoProviderImpl.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/driver/video/DriveSeatVideoProviderImpl.kt @@ -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? = null + @Volatile private var target: WeakReference? = 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(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 = 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(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) } diff --git a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/operate/OperatePanelLayout.kt b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/operate/OperatePanelLayout.kt index acd053b9e8..54cb70b3ce 100644 --- a/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/operate/OperatePanelLayout.kt +++ b/core/function-impl/mogo-core-function-hmi/src/main/java/com/mogo/eagle/core/function/hmi/ui/operate/OperatePanelLayout.kt @@ -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(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { + val p = preferenceScreen.findPreferenceReal(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { it.isEnabled = CallerDevaToolsManager.driveSeatVideoProvider()?.requestVideoInfo()?.livePlayUrl?.isNotEmpty() ?: false } + + if (FunctionBuildConfig.isDriveSeatVideoStream && p?.isEnabled == true) { + val map = HashMap() + 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(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { changeValue(it, false) } } "11" -> { //打开成功 + Log.d(TAG, "-- 驾驶位视频流打开成功 --") FunctionBuildConfig.isDriveSeatVideoStream = true preferenceScreen.findPreferenceReal(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { changeValue(it, true) } } else -> { - //关闭失败或打开失败 + if ("0" == s) { + Log.d(TAG, "-- 设备下线 --") + FunctionBuildConfig.isDriveSeatVideoStream = false + // 设备下线 + preferenceScreen.findPreferenceReal(KEY_DRIVE_SEAT_VIDEO_STREAM)?.also { + it.isChecked = false + it.isEnabled = false + } + } else if ("1" == s) { + // 设备在线 + Log.d(TAG, "-- 设备上线 --") + preferenceScreen.findPreferenceReal(KEY_DRIVE_SEAT_VIDEO_STREAM)?.isEnabled = true + } else if ("000" == s) { + // 查询乘客屏播放状态-未播放 + Log.d(TAG, "-- 乘客屏当前处于未播放状态 --") + preferenceScreen.findPreferenceReal(KEY_DRIVE_SEAT_VIDEO_STREAM)?.isChecked = false + } else if ("111" == s) { + // 查询乘客屏播放状态-正在播放 + Log.d(TAG, "-- 乘客屏当前处于播放状态 --") + preferenceScreen.findPreferenceReal(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() @@ -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 } } diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/driver/video/IDriveSeatVideoProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/driver/video/IDriveSeatVideoProvider.kt index ac4bf34c29..9adf3511e5 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/driver/video/IDriveSeatVideoProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/driver/video/IDriveSeatVideoProvider.kt @@ -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 + + /** + * 视频是否正在播放 + */ + fun isVideoPlaying(): Boolean + sealed class Event { object Loading: Event()