本文介绍扣子音视频相关的常见问题和处理方法。
使用 Realtime Web SDK 时,需要申请麦克风和摄像头权限,你可以在页面初始化后申请,也可以在需要使用 Realtime SDK 进行音视频通话时申请。Realtime SDK 提供了一个函数用于检查以及申请设备权限,示例代码如下:
import { RealtimeUtils } from '@coze/realtime-api';
const checkVideo = false; // 如需申请摄像头权限,请设置为 true
const result = RealtimeUtils.checkDevicePermission(checkVideo);
说明
麦克风权限为必选,摄像头权限仅在视频通话时才需要。
这个函数本质上就是调用了浏览器的 navigator.mediaDevices.getUserMedia 接口,你也可以使用该 API 来申请设备权限,这样后续就无需调用 RealtimeUtils.checkDevicePermission 方法了,示例代码如下:
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: false, // 如需申请摄像头权限,请设置为 true
});
const tracks = stream.getTracks();
tracks.forEach((track) => track.stop());
} catch (e) {
console.log('设备权限获取失败');
}
使用 Realtime Web SDK 时,需要申请麦克风和摄像头权限,若通过 webview 嵌入 H5 页面的方式使用 Realtime SDK,在 Android手机、iOS 手机或小程序里打开应用时,每次都得重新申请设备权限。这是因为移动端原生的安全限制造成的。建议使用火山引擎原生的 veRTC 客户端 SDK,你可以参考扣子提供的 Demo 来快速集成,具体方法请参见集成音视频 Realtime Android SDK和集成音视频 Realtime iOS SDK。
如果你还是想要通过 webview 嵌入 H5 页面的方式使用 Realtime SDK ,你可以参考如下实现思路:
由于浏览器安全限制,Realtime Web SDK 需要用户点击某个按钮来触发调用 connect 方法,不能在初始化时就调用connect 方法来自动触发连接。如果无法对话,请检查调用 connect 方法的实现方式是否正确。
音视频通话对稳定的网络环境有较高依赖,当网络异常、网络抖动或应用切换到后台而被回收设备权限时,会导致无法对话。本文详细介绍网络异常的几种典型场景以及优化方案。
场景一:网络抖动
网络在短时间内发生抖动时,若对网络变化进行监控,其状态可能会先变为离线(offline),随后又恢复为在线(online)。Realtime SDK 具备处理短时间网络波动的能力,这种波动对用户无感知,也不会影响实时对话。
Realtime SDK 提供了 client.network.quality 事件,你可以监听该事件,实时获取网络情况。示例代码如下:
import { EventNames } from '@coze/realtime-api';
// client 为 RealtimeClient 实例
client.on(EventNames.ALL, (eventName, event) => {
if (eventName === EventNames.NETWORK_QUALITY) {
const uplinkNetworkQuality = event?.uplinkNetworkQuality;
console.log('uplinkNetworkQuality', uplinkNetworkQuality);
}
});
当监听 client.network.quality 事件时,若发生网络抖动,网络状态 uplinkNetworkQuality 的值会从 DOWN 迅速恢复为 EXCELLENT。网络状态的具体说明如下表所示。
|
属性 |
值 |
描述 |
|---|---|---|
|
UNKNOWN |
0 |
网络质量未知。 |
|
EXCELLENT |
1 |
网络质量极好。 |
|
GOOD |
2 |
主观感觉和 Excellent 差不多,但码率可能略低。 |
|
POOR |
3 |
主观感受有瑕疵但不影响沟通。 |
|
BAD |
4 |
勉强能沟通但不顺畅。建议降低采样率、码率或通过 UI 提示用户是否切换为纯语音通话。 |
|
VBAD |
5 |
网络质量非常差,基本不能沟通。建议通过 UI 给出弱网提示。 |
|
DOWN |
6 |
网络连接断开,无法通话。 |
此外,如果监听浏览器的 offline 和 online 事件,当网络抖动发生时,网络状态会先变为 offline。由于其时效性比 client.network.quality 更快,所以通常会先收到 offline 事件,之后才会收到 uplinkNetworkQuality = 6 事件。监听浏览器事件的示例代码如下:
function checkNetworkStatus() {
if (!navigator.onLine) {
console.log('network offline');
}
}
// Listen for online/offline events
window.addEventListener('online', checkNetworkStatus);
window.addEventListener('offline', checkNetworkStatus);
场景二:网络切换或短时间(一般< 10秒)网络异常
当进行网络切换,例如从 Wifi 切换到移动网络(反之亦然)时,由于网络切换过程涉及设备与网络之间的重新连接以及 IP 地址的重新分配等操作,会导致短暂的网络中断。具体表现为,尽管网络显示正常,但无法进行正常对话,需要等待几秒钟,直到 SDK 重新连接后才能恢复通话。
同样,当网络短时间(<10 秒)内断网,恢复正常后也可能会出现几秒钟的不可用状态。
若监听 client.network.quality 事件,在这种情况下网络状态 uplinkNetworkQuality 的值会一直保持为 DOWN,直至网络恢复正常状态。
若监听 Web RTC 的 iceState 状态,在短时间网络异常时,iceState 的取值先是 connected,经过一段时间后才会变为 disconnected。示例代码如下:
try {
// client 为 RealtimeClient 实例
const iceState = client.getRtcEngine()?.iceState;
console.log('get iceState', iceState);
} catch (e) {
console.error('get iceState failed', e);
}
场景三:长时间(一般>20秒)网络异常
当网络长时间断网时,智能体会在断网 22 秒后强制主动退出。当网络恢复正常后,通常会收到 server.bot.leave 事件,但也有可能收不到该事件。此时,Realtime SDK 无法自动重连,需要用户手动进行重连操作。
如果监听 client.network.quality 事件,当网络恢复正常时,其值会变为 1(即 EXCELLENT,表示网络正常)。
如果监听 Web RTC 的 iceState 状态,当网络恢复正常时,其取值可能为 undefined,也可能是 connected,这表明 WebRTC 状态正常,但实际上 SDK 已经断开连接,无法继续使用。
场景四:应用 APP 切换至后台
当应用切换至后台时,系统可能会收回设备的权限(如语音权限)。当应用再次切换到前台时,可能需要重新申请设备权限;也有可能页面会重新加载;还有一种极端情况是,没有重新申请设备权限,但网络正常却无法通话,此时需要手动重新申请设备权限才能恢复使用。
若网络出现异常,通常会触发 offline 事件。此事件可通过浏览器的 API 进行监控,原生应用也能监控到该事件并将其发送至 webview。当监控到 offline 事件后,若短时间内(可设为 10 秒)网络恢复正常,Realtime SDK 能够自动重连。若 10 秒后网络依旧异常,则主动断开连接,让用户手动重连。
另外如果长时间切换至后台后再切换至前台时,可能页面会重新加载,请根据业务实际情况进行处理。
const checkNetworkStatus = () => {
let timer: NodeJS.Timeout | null = null;
function handleNetworkStatus() {
if (!navigator.onLine) {
timer = setTimeout(() => {
if (!navigator.onLine) {
console.log('network offline 10s');
// client 为 RealtimeClient 实例
client.disconnect();
}
}, 10 * 1000);
} else {
timer && clearTimeout(timer);
}
}
// 监听 online/offline 事件
window.addEventListener('online', handleNetworkStatus);
window.addEventListener('offline', handleNetworkStatus);
};
说明
通过原生应用监控网络状态变化比通过 webview 嵌入 H5 页面的方式更可靠、时效性更高,你可以监听网络情况并实时将信息发送给 webview 进行处理。
扣子提供了基于 React Hooks 和 Vue Class 的自动重连及网络异常处理机制。你可以根据实际技术栈选择合适的实现方式。
React Hooks
扣子基于 React Hooks 提供了一个 useNetworkError.ts Hook,用于处理网络异常情况。你可以监听 connectStatus 来获取网络连接状态,并将网络情况及时反馈给用户。使用方法如下:
// clientRef 是 RealtimeClient 实例
// connectStatus 是连接状态,监听这个状态,让用户及时了解网络情况
const { connectStatus } = useNetworkError({ clientRef });
Vue Class
扣子还提供了一个基于 Vue Class 的 NetworkErrorManager.ts 类,用于处理网络异常情况。你可以监听 connectStatus 来获取网络连接状态,并将网络情况及时反馈给用户。使用方法如下:
<template>
<div>
<p>连接状态: {{ connectStatus }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import { NetworkErrorManager } from './NetworkErrorManager';
import type { RealtimeClient } from '@coze/realtime-api';
export default defineComponent({
name: 'YourComponent',
setup() {
const connectStatus = ref('disconnected');
let networkManager: NetworkErrorManager | null = null;
onMounted(() => {
// 假设你已经有了 RealtimeClient 的实例
const client: RealtimeClient = /* your client instance */;
// 创建 NetworkErrorManager 实例
networkManager = new NetworkErrorManager(client);
// 监听状态变化
networkManager.onStatusChange = (status) => {
connectStatus.value = status;
};
});
onUnmounted(() => {
// 清理资源
networkManager?.destroy();
networkManager = null;
});
return {
connectStatus,
};
},
});
</script>
说明
useNetworkError.ts Hook 和 NetworkErrorManager.ts 类内部通过监听以下事件实现网络状态的实时更新:
offline 事件:当网络异常时,触发 offline 事件,提示用户网络中断。uplinkNetworkQuality 事件:当事件值为 6(即网络状态为 DOWN)时,判定 SDK 无法正常使用。iceState 事件:当事件状态为 disconnect 时,判定 SDK 无法正常使用。在更新房间配置上行事件的 data.turn_detection.type 参数中设置语音检测模式,支持如下三种模式。具体参数说明请参见Realtime 上行事件。详细的配置教程请参见基于 RTC 实现按键说话/语义判停。
server_vad):语音活动检测由扣子服务端完成,客户端将音频流持续发送到服务端,服务端在接收到音频后,通过服务端 VAD 检测语音的开始和结束。client_vad):客户端使用自己的 VAD 检测语音的开始和结束,并将检测到的语音片段发送到服务器进行识别。semantic_vad):由服务端识别语义来判断是否停止说话。具体的检测逻辑如下:
prefix_padding_ms 参数决定,默认值为 600 毫秒,支持修改。在更新对话配置上行事件的 data.turn_detection.type 参数中设置语音检测模式,支持如下三种模式。具体参数说明请参见 双向流式对话上行事件。详细的配置教程请参见硬件设备基于 WebSocket 实现语音交互。
server_vad):语音活动检测由扣子服务端完成,客户端将音频流持续发送到服务端,服务端在接收到音频后,通过服务端 VAD 检测语音的开始和结束。client_interrupt):在按键说话模式下,客户端实时分析语音数据,并检测用户是否已停止说话,语音识别服务时长等于用户说话的时长。semantic_vad):由服务端识别语义来判断是否停止说话。在 AI 回答时如果想要实现关闭麦克风以避免语音中断,你可以将 Realtime 上行事件中的 data.chat_config.allow_voice_interrupt 参数设置为 false,以关闭语音打断功能。详细参数说明请参见Realtime 上行事件。
你也可以通过设置打断对话的关键词和打断模式来实现精准打断,只有当用户语音中包含预设的关键词时,智能体的输出才会被中断,从而有效减少误中断。具体请参见通过关键词打断语音对话。例如,在智能家居场景中,你可以通过设置关键词“你好扣子”来控制智能设备。当智能体正在播报天气信息时,用户说“你好扣子,打开客厅灯”,智能体会立即停止播报天气,转而执行打开客厅灯的操作。
session.update)上行事件的 data.speech_rate 参数中设置模型回复的语速,取值范围 [-50, 100],默认为 0。-50 表示 0.5 倍速,100 表示 2 倍速。具体参数说明请参见Realtime 上行事件。chat.update)上行事件的 data.output_audio.speech_rate 参数中设置模型回复的语速,取值范围 [-50, 100],默认为 0。-50 表示 0.5 倍速,100 表示 2 倍速。具体参数说明请参见 双向流式对话上行事件。conversation.audio.delta 下行事件中,通过 data.content 字段,进行 Base64 解码后获取二进制音频数据。事件的详细参数说明请参见增量语音 。通过 WebSocket 实现语音交互的完整流程请参考硬件设备基于 WebSocket 实现语音交互。如果出现设备扬声器播放的智能体语音被麦克风再次拾取,出现回声时,你可以通过 3A 音频处理算法中的 AEC(Acoustic Echo Cancellation,回声消除) 技术来解决该问题。
通过 RTC 或 WebSocket 进行语音通话时,你可以通过智能体的消息日志查看语音通话的聊天记录,具体请参见消息日志。
目前暂不支持限制企业成员使用扣子语音相关 API。
在同一个会话 ID 下进行语音和文字聊天,通过查看消息列表 API 能查看对应会话中的语音和文字聊天记录。content_type 为 text 的消息,即为文字聊天记录。