1+ package com.example.ivdemo
2+
3+ import android.graphics.SurfaceTexture
4+ import android.os.Build
5+ import android.os.Bundle
6+ import android.os.Handler
7+ import android.os.Looper
8+ import android.text.TextUtils
9+ import android.util.Log
10+ import android.view.Surface
11+ import android.view.TextureView
12+ import android.view.View
13+ import android.widget.Toast
14+ import androidx.annotation.RequiresApi
15+ import androidx.core.view.isVisible
16+ import androidx.lifecycle.lifecycleScope
17+ import com.example.ivdemo.popup.QualitySettingDialog
18+ import com.tencent.iot.twcall.R
19+ import com.tencent.iot.twcall.databinding.ActivityCustomDuplexVideoBinding
20+ import com.tencent.iot.video.device.VideoNativeInterface
21+ import com.tencent.iotvideo.link.CameraRecorder
22+ import com.tencent.iotvideo.link.SimplePlayer
23+ import kotlinx.coroutines.Dispatchers
24+ import kotlinx.coroutines.launch
25+ import org.json.JSONException
26+ import org.json.JSONObject
27+
28+ private const val COMMAND_WX_CALL_START = " wx_call_start" // 小程序发起请求
29+ private const val COMMAND_WX_CALL_CANCEL = " wx_call_cancel" // 小程序取消呼叫
30+ private const val COMMAND_WX_CALL_HANGUP = " wx_call_hangup" // 小程序挂断
31+ private val TAG : String = CustomDuplexVideoActivity ::class .java.simpleName
32+
33+ class CustomDuplexVideoActivity : BaseIPCActivity <ActivityCustomDuplexVideoBinding >() {
34+
35+ @Volatile
36+ private var isCalling = false
37+
38+ private var condition1 = false
39+ private var condition2 = false
40+ private val lock = Any ()
41+
42+ private var type = 0
43+ private var height = 0
44+ private var width = 0
45+
46+ private var surface: Surface ? = null
47+
48+ private var localPreviewSurface: SurfaceTexture ? = null
49+ private var remotePreviewSurface: SurfaceTexture ? = null
50+ private var player = SimplePlayer ()
51+ private val cameraRecorder = CameraRecorder ()
52+ private val handler = Handler (Looper .getMainLooper())
53+ private val UPDATE_P2P_INFO_TOKEN = " update_p2p_info_token"
54+
55+
56+ private val listener = object : TextureView .SurfaceTextureListener {
57+ override fun onSurfaceTextureAvailable (surface : SurfaceTexture , width : Int , height : Int ) {
58+ if (surface == binding.textureViewDuplex.surfaceTexture) {
59+ // Initialize the SurfaceTexture object
60+ localPreviewSurface = surface
61+
62+ // Start the camera encoder
63+ cameraRecorder.openCamera(localPreviewSurface, this @CustomDuplexVideoActivity)
64+ } else if (surface == binding.surfaceViewDuplex.surfaceTexture) {
65+ remotePreviewSurface = surface
66+ synchronized(lock) {
67+ condition1 = true
68+ checkConditions()
69+ }
70+ }
71+ }
72+
73+ override fun onSurfaceTextureSizeChanged (surface : SurfaceTexture , width : Int , height : Int ) {
74+ // Not used in this example
75+ }
76+
77+ override fun onSurfaceTextureDestroyed (surface : SurfaceTexture ): Boolean {
78+ if (surface == binding.textureViewDuplex.surfaceTexture) {
79+ // Stop the camera encoder
80+ cameraRecorder.closeCamera()
81+ }
82+ return true
83+ }
84+
85+ override fun onSurfaceTextureUpdated (surface : SurfaceTexture ) {
86+ // Not used in this example
87+ }
88+ }
89+
90+ override fun onCreate (savedInstanceState : Bundle ? ) {
91+ Log .d(TAG , " start create" )
92+ super .onCreate(savedInstanceState)
93+ }
94+
95+ override fun getViewBinding (): ActivityCustomDuplexVideoBinding =
96+ ActivityCustomDuplexVideoBinding .inflate(layoutInflater)
97+
98+ override fun initView () {
99+ with (binding) {
100+ // Set the SurfaceTextureListener on the TextureView
101+ textureViewDuplex.surfaceTextureListener = listener
102+ surfaceViewDuplex.surfaceTextureListener = listener
103+ titleLayout.tvTitle.text = getString(R .string.title_custom_audio_video_call)
104+ titleLayout.ivBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
105+ titleLayout.ivRightBtn.isVisible = true
106+ titleLayout.ivRightBtn.setOnClickListener {
107+ val dialog = QualitySettingDialog (this @CustomDuplexVideoActivity)
108+ dialog.setOnDismissListener {
109+ cameraRecorder.closeCamera()
110+ cameraRecorder.openCamera(localPreviewSurface, this @CustomDuplexVideoActivity)
111+ }
112+ dialog.show(supportFragmentManager)
113+ }
114+ textDevInfo.text =
115+ String .format((getString(R .string.text_device_info)), " ${productId} _$deviceName " )
116+
117+
118+ btnRejectListen.setOnClickListener {
119+ sendCommand(" call_reject" )
120+ finishStream()
121+ updateCancelCallUI()
122+ }
123+ btnHangUp.setOnClickListener {
124+ sendCommand(" call_hang_up" )
125+ finishStream()
126+ isCalling = false
127+ updateCancelCallUI()
128+ }
129+
130+ btnAnswer.setOnClickListener {
131+ sendCommand(" call_answer" )
132+ isCalling = true
133+ btnRejectListen.visibility = View .GONE
134+ btnAnswer.visibility = View .GONE
135+ btnHangUp.visibility = View .VISIBLE
136+ tipText.visibility = View .VISIBLE
137+ updateAnswerUI()
138+ }
139+ }
140+ }
141+
142+ @RequiresApi(Build .VERSION_CODES .P )
143+ private fun updateP2pInfo () {
144+ val p2pInfo = VideoNativeInterface .getInstance().p2pInfo
145+ if (! binding.tvP2pInfo.text.toString().contains(p2pInfo)) {
146+ showToast(" P2PInfo 已更新" )
147+ }
148+ binding.tvP2pInfo.text = String .format(getString(R .string.text_p2p_info), p2pInfo)
149+ handler.postDelayed(taskRunnable, UPDATE_P2P_INFO_TOKEN , 10000 )
150+ }
151+
152+ @RequiresApi(Build .VERSION_CODES .P )
153+ private val taskRunnable = Runnable {
154+ updateP2pInfo()
155+ }
156+
157+ override fun onDestroy () {
158+ super .onDestroy()
159+ handler.removeCallbacksAndMessages(UPDATE_P2P_INFO_TOKEN )
160+ }
161+
162+ @RequiresApi(Build .VERSION_CODES .P )
163+ override fun onOnline (netDateTime : Long ) {
164+ super .onOnline(netDateTime)
165+ lifecycleScope.launch(Dispatchers .Main ) {
166+ updateP2pInfo()
167+ handler.postDelayed(taskRunnable, UPDATE_P2P_INFO_TOKEN , 60000 )
168+ }
169+ }
170+
171+ override fun onStartRecvVideoStream (
172+ visitor : Int ,
173+ channel : Int ,
174+ type : Int ,
175+ height : Int ,
176+ width : Int ,
177+ frameRate : Int
178+ ): Int {
179+ Log .d(TAG , " start video visitor $visitor h: $height w: $width " )
180+ this .type = type
181+ this .height = height
182+ this .width = width
183+ synchronized(lock) {
184+ condition2 = true
185+ checkConditions()
186+ }
187+ if (remotePreviewSurface != null ) {
188+ return 0
189+ } else {
190+ Log .d(
191+ TAG ,
192+ " IvStartRecvVideoStream mRemotePreviewSurface is null visitor $visitor "
193+ )
194+ return - 1
195+ }
196+ }
197+
198+ private fun checkConditions () {
199+ if (condition1 && condition2 && remotePreviewSurface != null && surface == null ) {
200+ surface = Surface (remotePreviewSurface)
201+ player.startVideoPlay(surface, visitor, type, height, width)
202+ }
203+ }
204+
205+ override fun onStartRealPlay (visitor : Int , channel : Int , videoResType : Int ) {
206+ super .onStartRealPlay(visitor, channel, videoResType)
207+ cameraRecorder.startRecording(visitor, videoResType)
208+ }
209+
210+ override fun onStopRealPlay (visitor : Int , channel : Int , videoResType : Int ) {
211+ cameraRecorder.stopRecording(visitor, videoResType)
212+ }
213+
214+ override fun onRecvStream (
215+ visitor : Int ,
216+ streamType : Int ,
217+ data : ByteArray? ,
218+ len : Int ,
219+ pts : Long ,
220+ seq : Long
221+ ): Int {
222+ if (! isCalling) return 0
223+ Log .d(
224+ TAG ,
225+ " onRecvStream visitor $visitor stream_type $streamType data$data len$len pts$pts seq$seq "
226+ )
227+ if (streamType == 1 ) {
228+ return player.playVideoStream(visitor, data, len, pts, seq)
229+ } else if (streamType == 0 ) {
230+ return player.playAudioStream(visitor, data, len, pts, seq)
231+ }
232+ return 0
233+ }
234+
235+ override fun onRecvCommand (
236+ command : Int ,
237+ visitor : Int ,
238+ channel : Int ,
239+ videoResType : Int ,
240+ args : String?
241+ ): String {
242+ runOnUiThread {
243+ when (args) {
244+ COMMAND_WX_CALL_START -> updateCallUI()
245+ COMMAND_WX_CALL_CANCEL -> updateCancelCallUI()
246+ COMMAND_WX_CALL_HANGUP -> {
247+ isCalling = false
248+ updateCancelCallUI()
249+ }
250+
251+ " wx_call_timeout" -> {}
252+ }
253+ }
254+ val resJson = JSONObject ()
255+ try {
256+ resJson.put(" code" , 0 )
257+ resJson.put(" errMsg" , " " )
258+ } catch (e: JSONException ) {
259+ e.printStackTrace()
260+ }
261+ return resJson.toString()
262+ }
263+
264+ private fun finishStream () {
265+ VideoNativeInterface .getInstance().sendFinishStreamMsg(visitor, channel, videoResType)
266+ VideoNativeInterface .getInstance().sendFinishStream(visitor, channel, videoResType)
267+ }
268+
269+ private fun sendCommand (order : String ): Boolean {
270+ val jsonObject = JSONObject ()
271+ try {
272+ jsonObject.put(" iv_private_cmd" , order)
273+ } catch (e: Exception ) {
274+ e.printStackTrace()
275+ }
276+ val res =
277+ VideoNativeInterface .getInstance().sendCommand(visitor, jsonObject.toString(), 1 * 1000 )
278+ Toast .makeText(this , " 发送发送:$jsonObject 信令发送结果:$res " , Toast .LENGTH_SHORT ).show()
279+ return ! TextUtils .isEmpty(res)
280+ }
281+
282+ private fun updateCallUI () {
283+ with (binding) {
284+ btnRejectListen.visibility = View .VISIBLE
285+ btnAnswer.visibility = View .VISIBLE
286+ btnHangUp.visibility = View .GONE
287+ clCall.visibility = View .VISIBLE
288+ tipText.visibility = View .VISIBLE
289+ tipText.bringToFront()
290+ textureViewDuplex.visibility = View .VISIBLE
291+ }
292+ }
293+
294+ private fun updateCancelCallUI () {
295+ with (binding) {
296+ clCall.visibility = View .GONE
297+ surfaceViewDuplex.visibility = View .INVISIBLE
298+ textureViewDuplex.visibility = View .INVISIBLE
299+ }
300+ }
301+
302+ private fun updateAnswerUI () {
303+ with (binding) {
304+ surfaceViewDuplex.visibility = View .VISIBLE
305+ surfaceViewDuplex.bringToFront()
306+ textureViewDuplex.visibility = View .VISIBLE
307+ textureViewDuplex.bringToFront()
308+ }
309+ }
310+ }
0 commit comments