即时通信 IM 会中呼叫(Web)

会中呼叫功能允许会议内的用户(邀请者)向房间外的其他用户(被邀请者)发送实时入会邀请。该功能基于 Atomicx State Hooks 实现,能够帮助开发者快速构建具有主动邀约能力的互动场景。

相关产品
即时通信 IM 产品页,含版本规格、价格与购买入口。

一、适用场景

·协同办公:在进行远程会议时,若涉及跨部门决策,可一键呼叫外部专家快速入会,缩短沟通链路。

·直播教学:老师在授课过程中,若发现部分学生缺席,可直接邀请其加入课堂。

·互联网医疗会诊:首诊医生在病历分析过程中,通过呼叫功能邀请上级医生进入虚拟诊室进行多方会诊,或者医生呼叫在等候室的患者进行问诊。

二、前提条件

在接入功能前,请确保满足以下条件:

·邀请者 (User A):用户已经通过 useLoginState 完成登录鉴权,参考 接入概览,并已作为一个房间的房主或成员在房间内,参考 房间周期。

·被邀请者 (User B):用户已经通过 useLoginState 完成登录鉴权,参考 接入概览,以便接收信令事件。

·环境依赖:项目已正确安装并引入 tuikit-atomicx-vue3。

三、实现会中呼叫功能

实现会中呼叫的核心流程如下:

会中呼叫时序图

四、步骤1:发起呼叫(邀请者)

邀请者调用 callUserToRoom 接口发起邀请。请确保邀请者当前已在房间内(即 currentRoom 不为空),否则无法发起呼叫。

Vue3Reactimport { useRoomState } from 'tuikit-atomicx-vue3/room';const { currentRoom, callUserToRoom } = useRoomState();const inviteUser = async () => { // 会中呼叫必须关联一个已存在的 roomId,因此需先校验当前用户的进房状态 if (!currentRoom.value) { console.warn('请先进入房间再发起邀请'); return; } try { const resultMap = await callUserToRoom({ roomId: currentRoom.value.roomId, // 被邀请者将要加入的房间 ID userIdList: ['user1', 'user2'], // 目标用户 ID 数组,支持单人或多人 timeout: 60, // 邀请有效时长(秒),超时后双方会触发 onCallTimeout // extensionInfo 可用于传递业务自定义数据,例如邀请类型、附加消息等 extensionInfo: JSON.stringify({ type: 'emergency', priority: 'high' }) }); // resultMap 返回每个 userId 对应的状态,例如 Success、AlreadyInRoom (已在房) 或 AlreadyInCalling (通话中) console.log('邀请发送完成,详情状态映射:', resultMap); } catch (error) { console.error('呼叫请求发送失败,请检查网络或登录状态:', error); }};import { useRoomState } from 'tuikit-atomicx-react/room';const { currentRoom, callUserToRoom } = useRoomState();const inviteUser = async () => { // 会中呼叫必须关联一个已存在的 roomId,因此需先校验当前用户的进房状态 if (!currentRoom) { console.warn('请先进入房间再发起邀请'); return; } try { const resultMap = await callUserToRoom({ roomId: currentRoom.roomId, // 被邀请者将要加入的房间 ID userIdList: ['user1', 'user2'], // 目标用户 ID 数组,支持单人或多人 timeout: 60, // 邀请有效时长(秒),超时后双方会触发 onCallTimeout // extensionInfo 可用于传递业务自定义数据,例如邀请类型、附加消息等 extensionInfo: JSON.stringify({ type: 'emergency', priority: 'high' }) }); // resultMap 返回每个 userId 对应的状态,例如 Success、AlreadyInRoom (已在房) 或 AlreadyInCalling (通话中) console.log('邀请发送完成,详情状态映射:', resultMap); } catch (error) { console.error('呼叫请求发送失败,请检查网络或登录状态:', error); }};

五、步骤2:监听呼叫事件(被邀请者)

被邀请者需要监听 onCallReceived 事件。建议在例如 App.vue等长驻组件中实现,以确保无论用户在哪个页面都能响应。

Vue3Reactimport { ref, onMounted, onUnmounted } from 'vue';import { useRoomState, RoomEvent } from 'tuikit-atomicx-vue3/room';const { subscribeEvent, unsubscribeEvent, acceptCall, rejectCall, joinRoom, currentRoom } = useRoomState();const showInviteDialog = ref(false); // 控制 UI 弹窗显隐const currentCallInfo = ref(null); // 缓存收到的邀请上下文const handleCallReceived = async ({ roomInfo, call, extensionInfo }) => { // 冲突处理:如果用户当前已经在会议中,根据业务策略通常应直接拒绝新邀请,防止干扰 if (currentRoom.value?.roomId) { await rejectCall({ roomId: roomInfo.roomId }); return; } // 记录邀请详情,用于在 UI 上展示邀请者名称或业务透传数据 currentCallInfo.value = { roomInfo, call }; showInviteDialog.value = true;};// 接受邀请逻辑const onUserAccept = async () => { if (!currentCallInfo.value) return; const { roomId } = currentCallInfo.value.roomInfo; try { // 关键流程 A:响应信令。告知邀请者已接受,使其能更新 UI(例如显示“对方已接听”) await acceptCall({ roomId }); // 关键流程 B:执行进房。只有调用 joinRoom 才会真正开启音视频推拉流链路 await joinRoom({ roomId }); } catch (error) { console.error('接受呼叫后的进房操作失败:', error); } finally { closeDialog(); }};// 拒绝邀请逻辑const onUserReject = async () => { if (currentCallInfo.value) { // 告知邀请者已拒绝,邀请者会收到 onCallRejected 事件 await rejectCall({ roomId: currentCallInfo.value.roomInfo.roomId }); } closeDialog();};const closeDialog = () => { showInviteDialog.value = false; currentCallInfo.value = null;};// 事件全生命周期管理:确保在组件挂载时监听,销毁时移除,防止事件溢出onMounted(() => { subscribeEvent(RoomEvent.onCallReceived, handleCallReceived); // 监听导致邀请结束的各种异常/同步事件,确保 UI 弹窗能及时闭合 subscribeEvent(RoomEvent.onCallCancelled, closeDialog); // 邀请者取消了呼叫 subscribeEvent(RoomEvent.onCallTimeout, closeDialog); // 呼叫超时未响应 subscribeEvent(RoomEvent.onCallHandledByOtherDevice, closeDialog); // 用户在其他设备已处理该呼叫});onUnmounted(() => { // 移除所有监听器,避免在组件销毁后继续执行 handleCallReceived 导致内存泄漏或报错 unsubscribeEvent(RoomEvent.onCallReceived, handleCallReceived); unsubscribeEvent(RoomEvent.onCallCancelled, closeDialog); unsubscribeEvent(RoomEvent.onCallTimeout, closeDialog); unsubscribeEvent(RoomEvent.onCallHandledByOtherDevice, closeDialog);});import { useState, useEffect } from 'react';import { useRoomState, RoomEvent } from 'tuikit-atomicx-react/room';const { subscribeEvent, unsubscribeEvent, acceptCall, rejectCall, joinRoom, currentRoom } = useRoomState();const [showInviteDialog, setShowInviteDialog] = useState(false); // 控制 UI 弹窗显隐const [currentCallInfo, setCurrentCallInfo] = useState(null); // 缓存收到的邀请上下文const handleCallReceived = async ({ roomInfo, call, extensionInfo }) => { // 冲突处理:如果用户当前已经在会议中,根据业务策略通常应直接拒绝新邀请,防止干扰 if (currentRoom?.roomId) { await rejectCall({ roomId: roomInfo.roomId }); return; } // 记录邀请详情,用于在 UI 上展示邀请者名称或业务透传数据 setCurrentCallInfo({ roomInfo, call }); setShowInviteDialog(true);};// 接受邀请逻辑const onUserAccept = async () => { if (!currentCallInfo) return; const { roomId } = currentCallInfo.roomInfo; try { // 关键流程 A:响应信令。告知邀请者已接受,使其能更新 UI(例如显示"对方已接听") await acceptCall({ roomId }); // 关键流程 B:执行进房。只有调用 joinRoom 才会真正开启音视频推拉流链路 await joinRoom({ roomId }); } catch (error) { console.error('接受呼叫后的进房操作失败:', error); } finally { closeDialog(); }};// 拒绝邀请逻辑const onUserReject = async () => { if (currentCallInfo) { // 告知邀请者已拒绝,邀请者会收到 onCallRejected 事件 await rejectCall({ roomId: currentCallInfo.roomInfo.roomId }); } closeDialog();};const closeDialog = () => { setShowInviteDialog(false); setCurrentCallInfo(null);};// 事件全生命周期管理:确保在组件挂载时监听,销毁时移除,防止事件溢出useEffect(() => { subscribeEvent(RoomEvent.onCallReceived, handleCallReceived); // 监听导致邀请结束的各种异常/同步事件,确保 UI 弹窗能及时闭合 subscribeEvent(RoomEvent.onCallCancelled, closeDialog); // 邀请者取消了呼叫 subscribeEvent(RoomEvent.onCallTimeout, closeDialog); // 呼叫超时未响应 subscribeEvent(RoomEvent.onCallHandledByOtherDevice, closeDialog); // 用户在其他设备已处理该呼叫 return () => { // 移除所有监听器,避免在组件销毁后继续执行 handleCallReceived 导致内存泄漏或报错 unsubscribeEvent(RoomEvent.onCallReceived, handleCallReceived); unsubscribeEvent(RoomEvent.onCallCancelled, closeDialog); unsubscribeEvent(RoomEvent.onCallTimeout, closeDialog); unsubscribeEvent(RoomEvent.onCallHandledByOtherDevice, closeDialog); };}, []);

六、实践教程:提升呼叫接通率

为了在业务中获得更高的呼叫接通率,除了基础功能的实现外,建议参考以下策略优化用户体验:

七、增强呼叫感知(避免用户遗漏)

由于 Web 端特性,浏览器静音或页面处于后台运行状态可能导致用户遗漏呼叫。

·视觉提醒:利用 Page Visibility API 检测页面可见性状态。若页面处于后台,可通过循环修改 document.title 实现标题栏闪烁提示(例如:“【新邀请】UserA 邀请你入会…”),以吸引用户注意。

·听觉提醒:在 onCallReceived 事件触发时播放高优先级的提示铃声。注意:需遵循浏览器的 自动播放策略,大部分浏览器在缺乏用户交互(例如点击、触摸或按键)的情况下会限制音频自动播放。

八、丰富邀请上下文(提升接听意愿)

缺乏明确意图的邀请容易被用户拒绝。建议充分利用 extensionInfo 字段传递关键业务信息,以提升用户的接听意愿。

·传递业务上下文:利用 extensionInfo 透传如 reason(例如:“项目紧急评审”)、level(例如:“P0故障排查”)等业务字段,告知用户呼叫的紧迫性和重要性。

九、多通道触达兜底(确保消息可达)

受限于 Web 端机制,浏览器进程结束将无法接收实时信令。建议采取以下替代方案以确保触达:

·浏览器系统通知:引导用户授权 Web Notification API。只要浏览器进程未关闭(包括最小化或在后台标签页),即可触发操作系统级别的弹窗通知,有效提醒用户。

·多通道触达:建议结合业务后端逻辑,在检测到用户不在线或超时未响应时,通过短信、邮件或企业微信等外部渠道发送入会链接,实现全方位触达。

十、智能重试机制(解决“接受邀请后进房失败”)

在弱网或其他不稳定环境下,用户点击接受邀请后调用 joinRoom 可能会出现偶发性失败。

·自动重试:建议在 onUserAccept 的异常处理逻辑(catch)中引入 2-3 次自动重试机制(推荐结合指数退避策略),以最大程度确保用户点击“接受”后能成功加入会议。

十一、开发注意事项

1. 接受邀请不等于进房

acceptCall 仅仅是信令层面的回复(告知对方已接听)。用户必须在调用 acceptCall 后紧接着手动调用 await joinRoom({ roomId }) 才能真正进入音视频房间。

2. 生命周期管理

请务必在组件销毁周期(onUnmounted)调用 unsubscribeEvent。如果不清理监听器,当用户切换页面时,旧页面的监听器会残留在内存中,导致一次邀请弹出多个窗口或逻辑重复执行。

3. 超时设置

如果不设置 timeout 参数,在某些网络异常或程序崩溃情况下,邀请可能长期处于挂起状态,导致无法再次对该用户发起呼叫。建议设置为 30-60 秒。

十二、常见问题

1. 如何在呼叫时带上自定义参数(如“来自XX的紧急会议”)?

使用 callUserToRoom 的 extensionInfo 参数传递 JSON 字符串。被邀请者可以在 onCallReceived 事件的 extensionInfo 字段中解析该字符串获取业务信息。

2. 被邀请者如果不在线,上线后能收到邀请吗?

不可以。邀请事件是一个瞬时信令动作,SDK 目前不会补发离线期间收到的呼叫通知。

3. 可以取消已发出的邀请吗?

可以。调用 cancelCall({ roomId, userIdList }) 即可撤销。此时被邀请者会收到 onCallCancelled 事件,你需要监听此事件来关闭被邀请端的 UI 弹窗。

4. 接受后进房失败怎么办?

检查是否已登录 TUIRoomEngine,重试 joinRoom({ roomId }) 并捕获错误提示用户;必要时在 UI 上提供“重试”按钮。

5. 对方忙线或已在其他房间如何提示?

callUserToRoom 的返回值 resultMap 中若出现 AlreadyInRoom / AlreadyInCalling,应在邀请者 UI 提示“对方忙线/已在会中”,并决定是否稍后重试。

6. 超时/取消后弹窗不关闭?

确保监听 onCallTimeout、onCallCancelled、onCallHandledByOtherDevice,统一在回调里关闭弹窗、清理当前邀请数据。

7. 多设备同时响铃如何处理?

在任意设备接受/拒绝后,其余设备会收到 onCallHandledByOtherDevice,需关闭其他端弹窗避免重复处理。

8. extensionInfo 该怎么约定?

建议使用 JSON 字符串并约定字段(例如 type、priority、from),在 handleCallReceived 时解析后展示友好文案(例如“来自XX的紧急会议”)。

十三、示例项目

腾讯云在 GitHub 上提供了示例项目 vite-vue3-ts ,你可以参考以实现完整的 RoomKit 功能。

十四、API 文档

State/Component 功能描述 API 文档
useRoomState 房间状态管理(创建、加入、预约等) API 文档
赞 (0)
上一篇 4天前
下一篇 4天前