1+ package com.tencent.iot.video.link.service
2+
3+ import android.text.TextUtils
4+ import android.util.Log
5+ import com.alibaba.fastjson.JSON
6+ import com.alibaba.fastjson.JSONObject
7+ import com.tencent.iot.video.link.callback.DetectMesssageCallback
8+ import com.tencent.iot.video.link.consts.VideoConst
9+ import com.tencent.iot.video.link.entity.VideoMessageType
10+ import com.tencent.iot.video.link.entity.WlanDetectBody
11+ import com.tencent.iot.video.link.entity.WlanRespBody
12+ import com.tencent.xnet.XP2P
13+ import kotlinx.coroutines.*
14+ import java.net.DatagramPacket
15+ import java.net.DatagramSocket
16+ import java.net.InetAddress
17+ import java.net.SocketException
18+
19+ /* *
20+ * 设备探测服务
21+ */
22+ class DetectService private constructor(): CoroutineScope by MainScope() {
23+
24+ companion object {
25+ private var instance: DetectService ? = null
26+
27+ @Synchronized
28+ fun getInstance (): DetectService {
29+ instance?.let {
30+ return it
31+ }
32+ instance = DetectService ()
33+ return instance!!
34+ }
35+ }
36+
37+ private var TAG = DetectService ::class .java.simpleName
38+ @Volatile
39+ private var socket: DatagramSocket ? = null
40+ var port = 3072
41+ var overTime = 5000
42+ @Volatile
43+ var groupAddress = " 255.255.255.255"
44+ private val BUFFER_SIZE = 2048
45+ private val sdkVarsion = XP2P .getVersion()
46+ var detectMesssageCallback: DetectMesssageCallback ? = null
47+ var adapterCallback: DetectMesssageCallback = object : DetectMesssageCallback {
48+ override fun onMessage (version : String , message : String ): Boolean {
49+ Log .e(TAG , " version $version , message $message " )
50+ if (detectMesssageCallback?.onMessage(version, message) == true ) { // 使用集成 sdk 方的处理逻辑
51+
52+ } else { // 使用内部处理逻辑
53+ var resp = JSONObject .parseObject(message, WlanRespBody ::class .java)
54+ resp.let {
55+ if (it.method == " probeMatch" && it.params != null && it.params.isReady()) {
56+ // cancel() // 查询到设备的 IP 地址信息,停止广播发送和接收内容
57+ }
58+ }
59+ }
60+ return true
61+ }
62+ }
63+
64+ // 尝试发一次广播
65+ fun startSendBroadcast (body : WlanDetectBody ) {
66+ startSendBroadcast(body, 1 )
67+ }
68+
69+ fun clear () {
70+ cancel() // 关闭所有的协程
71+ resetSocket()
72+ }
73+
74+ fun resetSocket () {
75+ socket?.let { // 尝试关闭 socket
76+ it.close()
77+ }
78+ socket = null
79+ }
80+
81+ // 尝试指定次数的广播
82+ fun startSendBroadcast (body : WlanDetectBody , times : Int ) {
83+ Log .e(TAG , " startSendBroadcast times $times " )
84+ resetSocket()
85+
86+ socket = DatagramSocket (port)
87+ openReceiver()
88+ launch(Dispatchers .IO ) {
89+ for (i in 0 until times) { // 尝试在协程中发送指定次数的广播
90+ sendBroadcast(body)
91+ delay(1000 ) // 每秒发一次广播
92+ }
93+ }
94+ }
95+
96+ private fun sendBroadcast (body : WlanDetectBody ) {
97+ Log .e(TAG , " sendBroadcast body ${JSON .toJSONString(body)} " )
98+ var json = JSONObject ()
99+ json[VideoConst .VIDEO_WLAN_METHOD ] = " probe"
100+ json[VideoConst .VIDEO_WLAN_CLIENT_TOKEN ] = body.clientToken
101+ json[VideoConst .VIDEO_WLAN_TIME_STAMP ] = System .currentTimeMillis() / 1000
102+ json[VideoConst .VIDEO_WLAN_TIMEOUT_MS ] = overTime
103+
104+ var paramsJson = JSONObject ()
105+ paramsJson[VideoConst .MULTI_VIDEO_PROD_ID ] = body.productId
106+ var devs = " "
107+ if (body != null && body.deviceNames.isNotEmpty()) {
108+ for (i in 0 until body.deviceNames.size) {
109+ devs = " $devs ,${body.deviceNames.get(i)} "
110+ }
111+ }
112+ if (devs.endsWith(" ," )) { // 清理多余的分割逗号
113+ devs = devs.substring(0 , devs.length - 1 )
114+ }
115+ if (! TextUtils .isEmpty(devs)) { paramsJson[VideoConst .VIDEO_WLAN_DEV_NAMES ] = devs } // 存在数据的时候发送该内容
116+ json[VideoConst .VIDEO_WLAN_PARAMS ] = paramsJson
117+ var dataPayload = json.toJSONString().toByteArray()
118+ var headerData = buildHeader(VideoMessageType .DETECT_BODY , sdkVarsion, dataPayload.size)
119+ var data2Send = ByteArray (headerData.size + dataPayload.size)
120+ System .arraycopy(headerData, 0 , data2Send, 0 , headerData.size)
121+ System .arraycopy(dataPayload, 0 , data2Send, headerData.size, dataPayload.size)
122+ sendBroadcast(data2Send)
123+ }
124+
125+ private fun buildHeader (type : VideoMessageType , version : String , len : Int ): ByteArray {
126+ var headerBytes = ByteArray (4 )
127+ headerBytes[0 ] = type.getValue().toByte()
128+ var versionParts = version.split(" ." )
129+ versionParts?.let { oriParts ->
130+ if (oriParts.size < 2 ) return @let
131+ oriParts[0 ].toBigIntegerOrNull()?.let { versionPrefix ->
132+ oriParts[1 ].toBigIntegerOrNull()?.let { versionsuffix ->
133+ headerBytes[1 ] = ((versionPrefix.toInt() shl 4 ) or versionsuffix.toInt()).toByte()
134+ }
135+ }
136+ }
137+ len?.let {
138+ headerBytes[3 ] = (len / Math .pow(2.0 , 8.0 ).toInt()).toByte()
139+ headerBytes[2 ] = (len % Math .pow(2.0 , 8.0 ).toInt()).toByte()
140+ }
141+ return headerBytes
142+ }
143+
144+ private fun sendBroadcast (dataStr : String ) {
145+ sendBroadcast(dataStr.toByteArray())
146+ }
147+
148+ private fun sendBroadcast (data : ByteArray ) {
149+ socket?.let {
150+ val packet = DatagramPacket (data, data.size, InetAddress .getByName(groupAddress), port)
151+ it.send(packet)
152+ }
153+ }
154+
155+ // 开启广播接收器
156+ fun openReceiver () {
157+ // 在子线程中循环接收数据
158+ Thread {
159+ launch (Dispatchers .Default ) {
160+ val recvbuffer = ByteArray (BUFFER_SIZE )
161+ val dataPacket = DatagramPacket (recvbuffer, BUFFER_SIZE )
162+ socket?.let {
163+ while (! it.isClosed) {
164+ try {
165+ it.receive(dataPacket)
166+ var headerBytes = ByteArray (4 )
167+ System .arraycopy(recvbuffer, 0 , headerBytes, 0 , headerBytes.size)
168+
169+ // 非标准响应 body 不做处理
170+ if (headerBytes[0 ] != VideoMessageType .DETECT_RESP_BODY .getValue().toByte()) continue
171+ var version = (headerBytes[1 ].toInt() shr 4 ).toString()
172+ version = " $version .${(headerBytes[1 ].toInt() and 0x0F )} "
173+ adapterCallback?.onMessage(version, String (recvbuffer, 4 , dataPacket.length - 4 ))
174+ } catch (e: SocketException ) {
175+ e.printStackTrace()
176+ }
177+ }
178+ }
179+ }
180+ }.start()
181+ }
182+ }
0 commit comments