> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coze.cn/llms.txt
> Use this file to discover all available pages before exploring further.

本文介绍扣子音视频相关的常见问题和处理方法。
## 如何申请设备权限？ {#0e326826}
使用 Realtime Web SDK 时，需要申请麦克风和摄像头权限，你可以在页面初始化后申请，也可以在需要使用 Realtime SDK 进行音视频通话时申请。Realtime SDK 提供了一个函数用于检查以及申请设备权限，示例代码如下：
```TypeScript
import { RealtimeUtils } from '@coze/realtime-api';

const checkVideo = false; // 如需申请摄像头权限，请设置为 true
const result = RealtimeUtils.checkDevicePermission(checkVideo);
```

:::tip 说明
麦克风权限为必选，摄像头权限仅在视频通话时才需要。
:::
这个函数本质上就是调用了浏览器的 [navigator.mediaDevices.getUserMedia](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia) 接口，你也可以使用该 API 来申请设备权限，这样后续就无需调用 `RealtimeUtils.checkDevicePermission` 方法了，示例代码如下：
```JavaScript
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('设备权限获取失败');
}
```

## 为什么每次打开应用都会收到设备权限申请提示？ {#7bccd89a}
使用 Realtime  Web SDK 时，需要申请麦克风和摄像头权限，若通过 webview 嵌入 H5 页面的方式使用 Realtime SDK，在 Android手机、iOS 手机或小程序里打开应用时，每次都得重新申请设备权限。这是因为移动端原生的安全限制造成的。建议使用火山引擎原生的 veRTC 客户端 SDK，你可以参考扣子提供的 Demo 来快速集成，具体方法请参见[集成音视频 Realtime Android SDK](/dev_how_to_guides/realtime_android)和[集成音视频 Realtime iOS SDK](/dev_how_to_guides/realtime_iOS)。
如果你还是想要通过 **webview 嵌入 H5 页面**的方式使用 Realtime SDK ，你可以参考如下实现思路：

1. 提前检测所需要的设备权限。
2. **原生应用做好配置**：如果 webview 需要设备权限，需要在原生应用中提前进行配置，否则可能导致权限申请失败。
3. **引导用户开启权限**：当用户没有开启设备权限或者拒绝了权限申请时，要提醒用户手动去开启。这样当打开 webview 时，用户只需要再点击确认一下就能获取权限了 。

## 为什么连接成功，但无法对话？ {#64a6dc29}
由于浏览器安全限制，Realtime  Web SDK 需要用户**点击某个按钮**来触发调用 `connect `方法，不能在初始化时就调用`connect `方法来自动触发连接。如果无法对话，请检查调用 `connect `方法的实现方式是否正确。
## 如何处理网络断线重连？ {#82ddf25a}
音视频通话对稳定的网络环境有较高依赖，当网络异常、网络抖动或应用切换到后台而被回收设备权限时，会导致无法对话。本文详细介绍网络异常的几种典型场景以及优化方案。
### 典型场景 {#375a73fb}
**场景一：网络抖动**
网络在短时间内发生抖动时，若对网络变化进行监控，其状态可能会先变为离线（offline），随后又恢复为在线（online）。Realtime SDK 具备处理短时间网络波动的能力，这种波动对用户无感知，也不会影响实时对话。
Realtime SDK 提供了 `client.network.quality` 事件，你可以监听该事件，实时获取网络情况。示例代码如下：
```JavaScript
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`。网络状态的具体说明如下表所示。
<!-- @cols-width: 105,105,386 -->
| | | | \
|**属性** |**值** |**描述** |
|---|---|---|
| | | | \
|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` 事件。监听浏览器事件的示例代码如下：
```JavaScript
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`。示例代码如下：
```JavaScript
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 切换至后台**
当应用切换至后台时，系统可能会收回设备的权限（如语音权限）。当应用再次切换到前台时，可能需要重新申请设备权限；也有可能页面会重新加载；还有一种极端情况是，没有重新申请设备权限，但网络正常却无法通话，此时需要手动重新申请设备权限才能恢复使用。
### 优化方案 {#c2fa2ba9}
扣子提供[主动断开连接](/dev_how_to_guides/realtime_faq#aeb1a189)和[自动重连](/dev_how_to_guides/realtime_faq#f500ec99)两种优化方案思路。
<!-- @cols-width: 100,100,601 -->
| | | | \
|**方案** |**实现难易** |**使用场景** |
|---|---|---|
| | | | \
|[主动断开连接](/dev_how_to_guides/realtime_faq#aeb1a189) |简单 |适用于对实时性要求不高的场景。 |
| | | | \
|[自动重连](/dev_how_to_guides/realtime_faq#f500ec99) |复杂 |* 适用于对实时性要求较高的场景，例如音视频通话。 |\
| | |* 网络环境不稳定的场景，自动重连方案可以提升用户体验。 |

#### 主动断开连接 {#aeb1a189}
若网络出现异常，通常会触发 **offline** 事件。此事件可通过浏览器的 API 进行监控，原生应用也能监控到该事件并将其发送至 webview。当监控到 **offline** 事件后，若短时间内（可设为 10 秒）网络恢复正常，Realtime SDK 能够自动重连。若 10 秒后网络依旧异常，则主动断开连接，让用户手动重连。
另外如果长时间切换至后台后再切换至前台时，可能页面会重新加载，请根据业务实际情况进行处理。
```JavaScript
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);
};
```

:::tip 说明
通过原生应用监控网络状态变化比通过 webview 嵌入 H5 页面的方式更可靠、时效性更高，你可以监听网络情况并实时将信息发送给 webview 进行处理。
:::
#### 自动重连 {#f500ec99}
扣子提供了基于 React Hooks 和 Vue Class 的自动重连及网络异常处理机制。你可以根据实际技术栈选择合适的实现方式。

* **React Hooks**
   扣子基于 React Hooks 提供了一个 [useNetworkError.ts](https://github.com/coze-dev/coze-js/blob/main/examples/realtime-console/src/hooks/use-network-error.tsx) Hook，用于处理网络异常情况。你可以监听 `connectStatus `来获取网络连接状态，并将网络情况及时反馈给用户。使用方法如下：
   ```TypeScript
   // clientRef 是 RealtimeClient 实例
   // connectStatus 是连接状态，监听这个状态，让用户及时了解网络情况
   const { connectStatus } = useNetworkError({ clientRef });  
   ```

* **Vue Class**
   扣子还提供了一个基于 Vue Class 的 [NetworkErrorManager.ts](https://github.com/coze-dev/coze-js/blob/main/examples/realtime-quickstart-vue/src/network-error-manager.ts)  类，用于处理网络异常情况。你可以监听 `connectStatus `来获取网络连接状态，并将网络情况及时反馈给用户。使用方法如下：
   ```XML
   <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>
   ```


:::tip 说明
[useNetworkError.ts](https://github.com/coze-dev/coze-js/blob/main/examples/realtime-console/src/hooks/use-network-error.tsx) Hook 和 [NetworkErrorManager.ts](https://github.com/coze-dev/coze-js/blob/main/examples/realtime-quickstart-vue/src/network-error-manager.ts)  类内部通过监听以下事件实现网络状态的实时更新：

* 浏览器 `offline` 事件：当网络异常时，触发 offline 事件，提示用户网络中断。
* Realtime SDK 的 `uplinkNetworkQuality `事件：当事件值为 6（即网络状态为 DOWN）时，判定 SDK 无法正常使用。
*  WebRTC 的 `iceState` 事件：当事件状态为 `disconnect` 时，判定 SDK 无法正常使用。
* 本地语音设备流检测：每隔 5 秒检查本地语音设备流是否正常。如果设备流为undefined，则判定语音设备流异常，SDK 无法正常使用。
:::
## 如何设置扣子的语音检测模式？ {#044b8bcc}
### **RTC 音视频通话场景** {#ae923e7e}
在**更新房间配置**上行事件的 `data.turn_detection.type` 参数中设置语音检测模式，支持如下三种模式。具体参数说明请参见[Realtime 上行事件](/developer_guides/signaling_uplink_event)。详细的配置教程请参见[基于 RTC 实现按键说话/语义判停](/tutorial/rtc_client_interrupt)。

* 普通自由对话模式（`server_vad`）：语音活动检测由扣子服务端完成，客户端将音频流持续发送到服务端，服务端在接收到音频后，通过服务端 VAD 检测语音的开始和结束。
* 按键说话模式（`client_vad`）：客户端使用自己的 VAD 检测语音的开始和结束，并将检测到的语音片段发送到服务器进行识别。
* 语义判停自由对话模式（`semantic_vad`）：由服务端识别语义来判断是否停止说话。

具体的检测逻辑如下：

1. 当 VAD 检测到人声时，系统将自动启动语音识别服务。
2. 启动后，系统会自动获取服务启动前的一段音频，确保语音识别的完整性和准确性。
   时长由 `prefix_padding_ms` 参数决定，默认值为 600 毫秒，支持修改。
3. 当 VAD 识别不到人声且持续 15 秒后，系统自动关闭语音识别服务，结束时长统计。

### WebSocket 语音通话场景 {#20aa2a74}
在**更新对话配置**上行事件的 `data.turn_detection.type` 参数中设置语音检测模式，支持如下三种模式。具体参数说明请参见 [双向流式对话上行事件](/developer_guides/streaming_chat_event)。详细的配置教程请参见[硬件设备基于 WebSocket 实现语音交互](/tutorial/websocket_voice_best_practices)。

* 普通自由对话模式（`server_vad`）：语音活动检测由扣子服务端完成，客户端将音频流持续发送到服务端，服务端在接收到音频后，通过服务端 VAD 检测语音的开始和结束。
* 按键说话模式（`client_interrupt`）：在按键说话模式下，客户端实时分析语音数据，并检测用户是否已停止说话，语音识别服务时长等于用户说话的时长。
* 语义判停自由对话模式（`semantic_vad`）：由服务端识别语义来判断是否停止说话。

## 如何实现在 AI 回答时关闭语音打断？ {#e535e053}
在 AI 回答时如果想要实现关闭麦克风以避免语音中断，你可以将 Realtime 上行事件中的 `data.chat_config.allow_voice_interrupt` 参数设置为 `false`，以关闭语音打断功能。详细参数说明请参见[Realtime 上行事件](/developer_guides/signaling_uplink_event)。
你也可以通过设置打断对话的关键词和打断模式来实现精准打断，只有当用户语音中包含预设的关键词时，智能体的输出才会被中断，从而有效减少误中断。具体请参见[通过关键词打断语音对话](/tutorial/keywords_interruption)。例如，在智能家居场景中，你可以通过设置关键词“你好扣子”来控制智能设备。当智能体正在播报天气信息时，用户说“你好扣子，打开客厅灯”，智能体会立即停止播报天气，转而执行打开客厅灯的操作。
## 如何调整语音输出的语速 {#cbf552dd}

* **RTC 音视频通话场景**
   在**更新房间配置**（`session.update`）上行事件的 `data.speech_rate` 参数中设置模型回复的语速，取值范围 [-50, 100]，默认为 0。-50 表示 0.5 倍速，100 表示 2 倍速。具体参数说明请参见[Realtime 上行事件](/developer_guides/signaling_uplink_event)。
* **WebSocket 语音通话场景**
   在**更新对话配置**（`chat.update`）上行事件的 `data.output_audio.speech_rate` 参数中设置模型回复的语速，取值范围 [-50, 100]，默认为 0。-50 表示 0.5 倍速，100 表示 2 倍速。具体参数说明请参见 [双向流式对话上行事件](/developer_guides/streaming_chat_event)。

## 如何获取与智能体对话过程中用户语音输入的音频内容？ {#c8273e58}

* **WebSocket 语音通话场景**
   你可以通过扣子返回的增量语音`conversation.audio.delta` 下行事件中，通过 `data.content` 字段，进行 Base64 解码后获取二进制音频数据。事件的详细参数说明请参见[增量语音 ](/developer_guides/streaming_chat_downlink_event#d0850e28)。通过 WebSocket 实现语音交互的完整流程请参考[硬件设备基于 WebSocket 实现语音交互](/tutorial/websocket_voice_best_practices)。
* **RTC 音视频通话场景**
   你可以监听 RTC 的获取原始音频数据回调，获取音频流。具体回调事件请参考[Native 端获取原始音频数据](https://www.volcengine.com/docs/6348/1178324#api-%E5%8F%8A%E5%9B%9E%E8%B0%83)。

## 语音交互时智能体回复的语音如何避免被设备录音后再发给智能体？ {#022101de}
如果出现设备扬声器播放的智能体语音被麦克风再次拾取，出现回声时，你可以通过 3A 音频处理算法中的 **AEC（Acoustic Echo Cancellation，回声消除）** 技术来解决该问题。
## 如何查看语音通话的聊天记录？ {#a7bfe3e3}
通过 RTC 或 WebSocket 进行语音通话时，你可以通过智能体的**消息日志**查看语音通话的聊天记录，具体请参见[消息日志](/guides/queries)。
## 如何限制企业成员使用扣子语音 API {#b804f6aa}
目前暂不支持限制企业成员使用扣子语音相关 API。
## 语音和文字聊天时，如何筛选文字聊天记录？ {#20c73fad}
在同一个会话 ID 下进行语音和文字聊天，通过[查看消息列表](/developer_guides/list_message) API 能查看对应会话中的语音和文字聊天记录。`content_type` 为 `text` 的消息，即为文字聊天记录。
