物联网开发平台 Android 智能设备(视频通话)

这篇将介绍在 Android 平台实现设备响应来自 App 或微信小程序的呼叫(接听/拒绝),以及通话建立后的音视频采集推流、对端画面渲染与挂断。

相关产品
物联网开发平台 产品页,含版本规格、价格与购买入口。

一、前提条件

在接入通话功能前,请你先完成 登录与注册 文档中跑通 login 登录功能,如果你已经完成,可以跳过该操作。

注意:说明:

注意:音视频通话依赖摄像头与麦克风采集,请确保已在 AndroidManifest.xml 中声明 android.permission.CAMERA、android.permission.RECORD_AUDIO 权限,并在运行时向用户申请授权。

二、步骤 1:注册通话事件回调

登录成功后,通过 TXIoTCallSession.getInstance().addListener 注册 TXIoTCallSession.Listener,通话相关的回调如下:

回调 说明
onCallRequested 收到对端来电时触发,携带 contact(对端信息)与 option(通话媒体类型等)。
onCallAccepted 通话正式建立时触发。被叫方调用 accept 成功、SDK 完成通话通道建立后也会回调,此时才是开启摄像头/麦克风采集推流的时机。
onCallRejected 本次呼叫被拒绝时触发。被叫方调用 reject 成功后也会回调,需结束本次呼叫并释放资源。
onCallTimeout 来电未在指定时间内被接听或拒绝时触发,需释放资源。
onCallHangup 对端挂断通话时触发。

private final TXIoTCallSession.Listener mCallListener = new TXIoTCallSession.Listener() { @Override public void onCallRequested(Contact contact, Option option) { // 收到来电,根据业务决定接听或拒绝(见步骤 2) } @Override public void onCallAccepted(Contact contact) { // 通话正式建立,此时开启采集推流与渲染对端画面(见步骤 3) } @Override public void onCallRejected(Contact contact) { // 本次呼叫被拒绝,结束本次呼叫并释放资源 } @Override public void onCallTimeout(Contact contact) { // 来电未及时处理超时,释放资源 } @Override public void onCallHangup(Contact contact) { // 对端挂断,停止采集与渲染并释放资源(见步骤 4) }};// 登录成功后注册监听TXIoTCallSession.getInstance().addListener(mCallListener);

三、步骤 2:响应来电

设备作为被叫,收到 onCallRequested 后,根据业务逻辑调用 accept 接听,或调用 reject 拒绝:

注意:accept 回调 onSuccess 仅代表接听请求已发出,通话尚未建立。请勿在此时立即开启采集推流,务必等到 onCallAccepted 回调触发后(通话正式建立)再开启摄像头/麦克风。

@Overridepublic void onCallRequested(Contact contact, Option option) { // 示例:直接接听,实际业务可结合按键、界面弹窗等方式决定接听或拒绝 TXIoTCallSession.getInstance().accept(contact.userId, new TXIoTCallback() { @Override public void onSuccess() { // 接听请求已发出,等待 onCallAccepted 回调触发后再开启采集推流 } @Override public void onError(int code, String desc) { Log.e(TAG, "accept failed: " + code + " " + desc); } }); // 拒绝来电: // TXIoTCallSession.getInstance().reject(contact.userId, callback);}

四、步骤 3:开启采集推流与渲染对端画面

通话正式建立(onCallAccepted 回调触发)后,根据 onCallRequested 携带的 option.mediaContent 开启本地采集推流,直接调用如下接口即可:

接口 说明
openCamera(frontCamera, view) 开启摄像头采集并推流,frontCamera 为是否使用前置摄像头,view 为本地画面预览的 TXCloudVideoView(可为 null),视频通话时调用。
openMicrophone() 开启麦克风采集并推流,音频/视频通话均需调用。
startRemoteView(userId, view) 拉取并渲染对端 userId 的视频画面到指定 TXCloudVideoView。

// mLocalView / mRemoteView 为布局中的 TXCloudVideoView,分别用于本地预览与对端画面String userId = "peer_user_id";if (media == MediaContent.VIDEO || media == MediaContent.AUDIO_VIDEO) { TXIoTCallSession.getInstance().openCamera(true, mLocalView); // 使用前置摄像头,本地预览到 mLocalView TXIoTCallSession.getInstance().startRemoteView(userId, mRemoteView);// 渲染对端画面}if (media == MediaContent.AUDIO || media == MediaContent.AUDIO_VIDEO) { TXIoTCallSession.getInstance().openMicrophone();}

五、步骤 4:挂断通话

通话中主动挂断,调用 hangup;对端挂断时会触发 onCallHangup,此时应关闭本地采集、停止对端画面渲染:

// 主动挂断TXIoTCallSession.getInstance().hangup(userId, new TXIoTCallback() { @Override public void onSuccess() { // 停止采集与渲染,释放资源(onCallHangup / onCallTimeout / onCallRejected 均调用) TXIoTCallSession.getInstance().stopRemoteView(userId); TXIoTCallSession.getInstance().closeCamera(); TXIoTCallSession.getInstance().closeMicrophone(); } @Override public void onError(int code, String desc) { Log.e(TAG, "hangup failed: " + code + " " + desc); }});

注意:说明:

注意:来电未及时处理时会触发 onCallTimeout,处理方式与 onCallHangup 一致:关闭采集、停止渲染并释放资源。

六、完整示例代码

以下 CallActivity 示例整合了通话事件响应、接听来电、开启采集推流与渲染对端画面、以及挂断的完整流程,实际接入时,请将布局中的 TXCloudVideoView 与业务界面对接。

import android.os.Bundle;import android.util.Log;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;import com.tencent.liteav.iot.TXIoTCallback;import com.tencent.liteav.iot.TXIoTCallSession;import com.tencent.liteav.iot.TXIoTCallSession.Contact;import com.tencent.liteav.iot.TXIoTCallSession.MediaContent;import com.tencent.liteav.iot.TXIoTCallSession.Option;import com.tencent.liteav.iot.TXIoTError;import com.tencent.rtmp.ui.TXCloudVideoView;public class CallActivity extends AppCompatActivity { private static final String TAG = "CallActivity"; private TXCloudVideoView mLocalView; private TXCloudVideoView mRemoteView; // 记住当前通话的媒体类型与对端账号,供接听/挂断复用 private MediaContent mCurrentMedia = MediaContent.AUDIO_VIDEO; private String mPeerUserId; private final TXIoTCallSession.Listener mCallListener = new TXIoTCallSession.Listener() { @Override public void onCallRequested(Contact contact, Option option) { Log.i(TAG, "onCallRequested userId=" + contact.userId + " media=" + option.mediaContent); mPeerUserId = contact.userId; mCurrentMedia = option.mediaContent; // 示例:自动接听。实际可弹出接听/拒绝界面 acceptCall(contact.userId); } @Override public void onCallAccepted(Contact contact) { Log.i(TAG, "onCallAccepted userId=" + contact.userId); // 通话正式建立,此时才开启采集推流与渲染对端画面 startCallStreams(contact, mCurrentMedia); } @Override public void onCallRejected(Contact contact) { Log.i(TAG, "onCallRejected userId=" + contact.userId); stopCallStreams(contact.userId); } @Override public void onCallTimeout(Contact contact) { Log.i(TAG, "onCallTimeout userId=" + contact.userId); stopCallStreams(contact.userId); } @Override public void onCallHangup(Contact contact) { Log.i(TAG, "onCallHangup userId=" + contact.userId); stopCallStreams(contact.userId); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_call); // mLocalView = findViewById(R.id.local_view); // mRemoteView = findViewById(R.id.remote_view); // 请确保已完成 login 登录,并已申请摄像头/麦克风运行时权限 TXIoTCallSession.getInstance().addListener(mCallListener); } // 接听来电(采集推流放到 onCallAccepted 回调中执行) private void acceptCall(String userId) { TXIoTCallSession.getInstance().accept(userId, new TXIoTCallback() { @Override public void onSuccess() { Log.i(TAG, "accept succeed, waiting for onCallAccepted"); } @Override public void onError(int code, String desc) { Log.e(TAG, "accept failed: " + code + " " + desc); } }); } // 通话建立后:根据媒体类型开启采集推流并渲染对端画面 private void startCallStreams(Contact contact, MediaContent media) { TXIoTCallSession session = TXIoTCallSession.getInstance(); if (media == MediaContent.VIDEO || media == MediaContent.AUDIO_VIDEO) { int rc = session.openCamera(true, mLocalView); if (rc != TXIoTError.SUCCESS) { Log.e(TAG, "openCamera failed: " + rc); } session.startRemoteView(contact.userId, mRemoteView); } if (media == MediaContent.AUDIO || media == MediaContent.AUDIO_VIDEO) { int rc = session.openMicrophone(); if (rc != TXIoTError.SUCCESS) { Log.e(TAG, "openMicrophone failed: " + rc); } } } // 主动挂断 private void hangupCall(String userId) { TXIoTCallSession.getInstance().hangup(userId, new TXIoTCallback() { @Override public void onSuccess() { stopCallStreams(userId); } @Override public void onError(int code, String desc) { Log.e(TAG, "hangup failed: " + code + " " + desc); } }); } // 停止采集与渲染,释放资源 private void stopCallStreams(String userId) { TXIoTCallSession session = TXIoTCallSession.getInstance(); if (userId != null) { session.stopRemoteView(userId); } session.closeCamera(); session.closeMicrophone(); } @Override protected void onDestroy() { super.onDestroy(); // 退出通话界面时,务必挂断并释放资源,移除监听 if (mPeerUserId != null) { hangupCall(mPeerUserId); } else { stopCallStreams(null); } TXIoTCallSession.getInstance().removeListener(mCallListener); }}

七、常见问题

现象 排查建议
App 发起呼叫后,设备端未收到 onCallRequested。 确认设备已成功登录;确认已调用 addListener 注册监听且未被提前 removeListener。
accept 回调 onError 返回失败。 确认设备已成功登录;确认 userId 有效;确认当前没有其他通话正在进行。
本地或对端画面不显示。 确认已声明并授予 CAMERA、RECORD_AUDIO 权限;确认 openCamera 传入的本地 TXCloudVideoView 与 startRemoteView 传入的对端 TXCloudVideoView 均已正确加入布局且可见。
多次通话后设备资源占用持续增长。 检查 onCallHangup / onCallTimeout / onCallRejected 是否都正确调用了 closeCamera / closeMicrophone / stopRemoteView,并在退出界面时 removeListener,避免资源泄漏。

八、联系我们

如果你在接入或使用过程中有任何疑问或者建议,欢迎 联系我们 提交反馈。

赞 (0)
上一篇 4天前
下一篇 4天前