Skip to content

Commit e42170a

Browse files
committed
增加呼叫接听自定义音视频双向功能
1 parent a4d9bfd commit e42170a

15 files changed

+645
-51
lines changed

app/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ dependencies {
5757
implementation libs.crashreport
5858
implementation libs.androidx.core.ktx
5959
implementation libs.kotlinx.coroutines.android
60-
implementation libs.mmkv
6160
implementation libs.gson
6261
implementation 'com.tencent.iot.video:video-device-android:1.0.6.03-SNAPSHOT'
6362
}

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,10 @@
5555
android:configChanges="orientation|keyboardHidden|screenSize"
5656
android:exported="false"
5757
android:theme="@style/Theme.TWAppTheme" />
58+
<activity
59+
android:name="com.example.ivdemo.CustomDuplexVideoActivity"
60+
android:configChanges="orientation|keyboardHidden|screenSize"
61+
android:exported="false"
62+
android:theme="@style/Theme.TWAppTheme" />
5863
</application>
5964
</manifest>
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package com
22

33
import android.app.Application
4-
import com.tencent.mmkv.MMKV
54

65
class IvDemoApplication : Application() {
76
override fun onCreate() {
87
super.onCreate()
9-
MMKV.initialize(this);
108
}
119
}

app/src/main/java/com/example/ivdemo/BaseIPCActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import kotlinx.coroutines.cancel
2121
import kotlinx.coroutines.launch
2222
import org.json.JSONObject
2323

24-
private val TAG = IPCActivity::class.java.simpleName
24+
private val TAG = BaseIPCActivity::class.java.simpleName
2525

2626
abstract class BaseIPCActivity<VB : ViewBinding> : AppCompatActivity(), IvDeviceCallback,
2727
IvAvtCallback {
@@ -341,6 +341,7 @@ abstract class BaseIPCActivity<VB : ViewBinding> : AppCompatActivity(), IvDevice
341341
}
342342

343343
fun showToast(msg: String) {
344+
Log.d(TAG, "msg:$msg")
344345
lifecycleScope.launch {
345346
Toast.makeText(this@BaseIPCActivity.applicationContext, msg, Toast.LENGTH_SHORT).show();
346347
}
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
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

Comments
 (0)