本文将介绍在 Android 平台实现设备与 App 之间的双向通话,既包括设备主动呼叫 App,也包括设备响应 App 来电(接听/拒绝),以及通话建立后的音视频采集推流、对端画面渲染与挂断。
前提条件
说明:
音视频通话依赖摄像头与麦克风采集,请确保已在
AndroidManifest.xml 中声明 android.permission.CAMERA、android.permission.RECORD_AUDIO 权限,并在运行时向用户申请授权。如果您需要呼叫小程序的功能,请您先完成 激活 TWeCall 的接入。
接入步骤
步骤 1:注册通话事件回调
private final TXIoTCallSession.Listener mCallListener = new TXIoTCallSession.Listener() {@Overridepublic void onCallRequested(Contact contact, Option option) {// 被叫:收到来电,根据业务决定接听或拒绝(见步骤 3)}@Overridepublic void onCallAccepted(Contact contact) {// 主叫:对端接听,通话建立,此时开启采集推流(见步骤 4)}@Overridepublic void onCallRejected(Contact contact) {// 主叫:对端拒绝,结束本次呼叫并释放资源}@Overridepublic void onCallTimeout(Contact contact) {// 无人应答超时,释放资源}@Overridepublic void onCallHangup(Contact contact) {// 对端挂断,停止采集与渲染并释放资源(见步骤 5)}};// 登录成功后注册监听TXIoTCallSession.getInstance().addListener(mCallListener);
步骤 2:主动发起呼叫(可选)
getContacts 分页拉取联系人:cursor 传 null 拉取首页,后续使用回调返回的 nextCursor 继续拉取,limit 为单页数量。// 拉取首页联系人(limit 为单页数量)TXIoTCallSession.getInstance().getContacts(null, 10, new TXIoTValueCallback<ContactsResult>() {@Overridepublic void onSuccess(ContactsResult result) {for (Contact c : result.contacts) {Log.i(TAG, "contact userId=" + c.userId + " userName=" + c.userName);}// 若 result.nextCursor 非空,可再次调用 getContacts(result.nextCursor, 10, ...) 拉取下一页// 从列表中选定目标联系人后,即可用其 userId 发起呼叫}@Overridepublic void onError(int code, String desc) {Log.e(TAG, "getContacts failed: " + code + " " + desc);}});
通话选项 Option 字段说明如下:
字段 | 说明 |
mediaContent | 通话媒体类型: MediaContent.AUDIO(纯语音)、MediaContent.VIDEO(纯视频)或 MediaContent.AUDIO_VIDEO(音视频)。 |
customData | 透传给对端的自定义数据(可选)。 |
注意:
call 的回调 onSuccess 仅代表呼叫已发出,通话尚未建立。请勿在此时立即开启采集推流,务必等到 onCallAccepted(对端接听)回调触发后再开启摄像头/麦克风。String userId = "peer_user_id";Option option = new Option();option.mediaContent = MediaContent.AUDIO_VIDEO; // 音视频通话TXIoTCallSession.getInstance().call(userId, option, new TXIoTCallback() {@Overridepublic void onSuccess() {// 呼叫已发出,等待 onCallAccepted / onCallRejected / onCallTimeoutLog.i(TAG, "calling " + userId + ", waiting for accept ...");}@Overridepublic void onError(int code, String desc) {Log.e(TAG, "call failed: " + code + " " + desc);}});
步骤 3:响应来电,接听或拒绝
设备作为被叫,收到
onCallRequested 后,根据业务逻辑调用 accept 接听,或调用 reject 拒绝:@Overridepublic void onCallRequested(Contact contact, Option option) {// 示例:直接接听,实际业务可结合按键、界面弹窗等方式决定接听或拒绝TXIoTCallSession.getInstance().accept(contact.userId, new TXIoTCallback() {@Overridepublic void onSuccess() {// 接听成功,通话建立,开启采集推流(见步骤 4)}@Overridepublic void onError(int code, String desc) {Log.e(TAG, "accept failed: " + code + " " + desc);}});// 拒绝来电:// TXIoTCallSession.getInstance().reject(contact.userId, callback);}
步骤 4:开启采集推流与渲染对端画面
通话建立后(被叫方
accept 成功,或主叫方收到 onCallAccepted),根据通话的 mediaContent 开启本地采集推流,直接调用如下接口即可:接口 | 说明 |
openCamera(frontCamera, view) | 开启摄像头采集并推流, frontCamera 为是否使用前置摄像头,view 为本地画面预览的 TXCloudVideoView(可为 null),视频通话时调用。 |
switchCamera(frontCamera) | 切换前/后置摄像头。 |
openMicrophone() | 开启麦克风采集并推流,音频/视频通话均需调用。 |
startRemoteView(userId, view) | 拉取并渲染对端 userId 的视频画面到指定 TXCloudVideoView。 |
muteRemoteAudio(userId, mute) | 静音/取消静音对端 userId 的音频。 |
上述接口返回
int 错误码,返回 TXIoTError.SUCCESS(0)代表调用成功。// mLocalView / mRemoteView 为布局中的 TXCloudVideoView,分别用于本地预览与对端画面String userId = "peer_user_id";if (media == MediaContent.VIDEO || media == MediaContent.AUDIO_VIDEO) {TXIoTCallSession.getInstance().openCamera(true, mLocalView); // 使用前置摄像头,本地预览到 mLocalViewTXIoTCallSession.getInstance().startRemoteView(userId, mRemoteView);// 渲染对端画面}if (media == MediaContent.AUDIO || media == MediaContent.AUDIO_VIDEO) {TXIoTCallSession.getInstance().openMicrophone();}
步骤 5:挂断通话
通话中主动挂断,调用
hangup;对端挂断时会触发 onCallHangup,此时应关闭本地采集、停止对端画面渲染:// 主动挂断TXIoTCallSession.getInstance().hangup(userId, new TXIoTCallback() {@Overridepublic void onSuccess() {// 停止采集与渲染,释放资源(onCallHangup / onCallTimeout / onCallRejected 均调用)TXIoTCallSession.getInstance().stopRemoteView(userId);TXIoTCallSession.getInstance().closeCamera();TXIoTCallSession.getInstance().closeMicrophone();}@Overridepublic 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() {@Overridepublic 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);}@Overridepublic void onCallAccepted(Contact contact) {Log.i(TAG, "onCallAccepted userId=" + contact.userId);mPeerUserId = contact.userId;// 主叫:对端接听,通话建立,开启采集推流(复用发起时的媒体类型)startCallStreams(contact, mCurrentMedia);}@Overridepublic void onCallRejected(Contact contact) {Log.i(TAG, "onCallRejected userId=" + contact.userId);stopCallStreams(contact.userId);}@Overridepublic void onCallTimeout(Contact contact) {Log.i(TAG, "onCallTimeout userId=" + contact.userId);stopCallStreams(contact.userId);}@Overridepublic void onCallHangup(Contact contact) {Log.i(TAG, "onCallHangup userId=" + contact.userId);stopCallStreams(contact.userId);}};@Overrideprotected 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 startOutgoingCall(String peerUserId) {mPeerUserId = peerUserId;Option option = new Option();option.mediaContent = MediaContent.AUDIO_VIDEO;mCurrentMedia = option.mediaContent;TXIoTCallSession.getInstance().call(peerUserId, option, new TXIoTCallback() {@Overridepublic void onSuccess() {Log.i(TAG, "calling " + peerUserId + ", waiting for accept ...");}@Overridepublic void onError(int code, String desc) {Log.e(TAG, "call failed: " + code + " " + desc);}});}// 被叫:接听来电private void acceptCall(String userId) {TXIoTCallSession.getInstance().accept(userId, new TXIoTCallback() {@Overridepublic void onSuccess() {Contact contact = new Contact();contact.userId = userId;startCallStreams(contact, mCurrentMedia);}@Overridepublic 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() {@Overridepublic void onSuccess() {stopCallStreams(userId);}@Overridepublic 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();}@Overrideprotected void onDestroy() {super.onDestroy();// 退出通话界面时,务必挂断并释放资源,移除监听if (mPeerUserId != null) {hangupCall(mPeerUserId);} else {stopCallStreams(null);}TXIoTCallSession.getInstance().removeListener(mCallListener);}}
常见问题
现象 | 排查建议 |
APP 发起呼叫后,设备端未收到 onCallRequested | 确认设备已成功登录;确认已调用 addListener 注册监听且未被提前 removeListener。 |
accept 回调 onError 返回失败 | 确认设备已成功登录;确认 userId 有效;确认当前没有其他通话正在进行。 |
调用 call 后对端 App 未收到呼叫 | 确认设备已成功登录;确认 userId 为有效的 App 端账号且对端在线;确认当前没有其他通话正在进行。 |
主叫已接通( onCallAccepted 已触发),但对端看不到画面或听不到声音 | 确认在 onCallAccepted(而非 call 回调 onSuccess)后才开启 openCamera / openMicrophone;确认沿用了发起呼叫时的 mediaContent。 |
本地或对端画面不显示 | 确认已声明并授予 CAMERA、RECORD_AUDIO 权限;确认 openCamera 传入的本地 TXCloudVideoView 与 startRemoteView 传入的对端 TXCloudVideoView 均已正确加入布局且可见。 |
多次通话后设备资源占用持续增长 | 检查 onCallHangup / onCallTimeout / onCallRejected 是否都正确调用了 closeCamera / closeMicrophone / stopRemoteView,并在退出界面时 removeListener,避免资源泄漏。 |
联系我们