> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/react/audio-visualizer-optional.md).

# 在 React 中添加音频可视化器

在 React 应用中使用会话音轨上的 Web Audio API，根据角色的声音绘制实时电平表和频率条。

在 React 组件中，将角色的语音绘制为频率条和电平表。当你构建自定义 UI 并希望在角色说话时获得可视化反馈时，请使用此页面。此示例是一个可根据你的设计进行调整的起点。

### 前提条件

* 一个已连接到 `useConvaiClient`的 React 应用，使用默认的 LiveKit 传输。该示例从 `room.remoteParticipants`中读取角色的音频轨道，而 WebSocket 传输不会填充该字段。
* 角色的音频通过 `AudioRenderer` 或 `ConvaiWidget`播放。可视化器读取该轨道；它不会播放它。请参见 [在 React 中构建自定义 UI](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/react/custom-ui-and-advanced-usage.md).

### 添加可视化器组件

该组件会等待会话连接，获取第一个远程音频轨道，并将其输入到 Web Audio `AnalyserNode`中。每一帧动画它都会在画布上绘制频率数据并存储平均电平。

这里使用的 `AudioContext` 是浏览器的 Web Audio 类。请不要从 `AudioContext` 导入 `@convai/web-widget/react` ，因为该导出是 LiveKit 房间上下文。

{% code title="src/AudioVisualizer.tsx" %}

```tsx
import { useEffect, useRef, useState } from 'react';
import type { useConvaiClient } from '@convai/web-sdk/react';

type Client = ReturnType<typeof useConvaiClient>;

export function AudioVisualizer({ convaiClient }: { convaiClient: Client }) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const [audioLevel, setAudioLevel] = useState(0);

  useEffect(() => {
    if (!convaiClient.room || !convaiClient.state.isConnected) return;

    let animationId = 0;
    const audioContext = new AudioContext();

    const participant = Array.from(convaiClient.room.remoteParticipants.values())[0];
    const publication = participant
      ? Array.from(participant.audioTrackPublications.values())[0]
      : undefined;
    const track = publication?.track;

    if (track) {
      const source = audioContext.createMediaStreamSource(
        new MediaStream([track.mediaStreamTrack]),
      );
      const analyser = audioContext.createAnalyser();
      analyser.fftSize = 256;
      source.connect(analyser);
      const data = new Uint8Array(analyser.frequencyBinCount);

      const render = () => {
        analyser.getByteFrequencyData(data);
        const average = data.reduce((sum, v) => sum + v, 0) / data.length;
        setAudioLevel(average / 255);

        const ctx = canvasRef.current?.getContext('2d');
        if (ctx && canvasRef.current) {
          const { width, height } = canvasRef.current;
          ctx.clearRect(0, 0, width, height);
          const barWidth = (width / data.length) * 2.5;
          let x = 0;
          for (let i = 0; i < data.length; i++) {
            const barHeight = (data[i] / 255) * height;
            ctx.fillRect(x, height - barHeight, barWidth, barHeight);
            x += barWidth + 1;
          }
        }

        animationId = requestAnimationFrame(render);
      };

      render();
    }

    return () => {
      cancelAnimationFrame(animationId);
      audioContext.close().catch(() => {});
    };
  }, [convaiClient.room, convaiClient.state.isConnected]);

  return (
    <div>
      <canvas ref={canvasRef} width={800} height={200} />
      <div>音频电平：{(audioLevel * 100).toFixed(0)}%</div>
    </div>
  );
}
```

{% endcode %}

当 `state.isConnected` 发生变化时，该 effect 会再次运行。如果会话连接时角色的轨道尚未发布，那么该组件在该会话中不会绘制任何内容；如果你的应用需要，在轨道到达时重新运行设置。

一个调用 `setAudioLevel` 的组件在每一帧动画都会以显示器刷新率重新渲染。对于较重的页面，改为将电平写入 ref 或直接写入 DOM。

### 测量你自己的麦克风

`audioControls` 有一个 `audioLevel` 字段，以及 `startAudioLevelMonitoring()` 和 `stopAudioLevelMonitoring()` 方法。在此版本中 `startAudioLevelMonitoring()` 不会更新 `audioLevel`，其保持为 `0`。要显示你自己的麦克风电平，请将相同的分析器方法应用于来自 `room.localParticipant.audioTrackPublications`.

### 验证可视化器

渲染 `<AudioVisualizer convaiClient={convaiClient} />`，连接并发送一条消息。

{% hint style="success" %}
当角色说话时，条形会移动，电平会升至 0% 以上，然后在其停止时回落。
{% endhint %}

### 下一步

{% content-ref url="/pages/a3978c3ff1ab0d126b04a15a7cc212776a8745d3" %}
[在 React 中构建自定义 UI](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/react/custom-ui-and-advanced-usage.md)
{% endcontent-ref %}

{% content-ref url="/pages/68959b8b127b7dad992c41de6f7e87cd945ce3e3" %}
[useConvaiClient Hook 参考](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/react/useconvaiclient-hook.md)
{% endcontent-ref %}

{% content-ref url="/pages/37f8f6ed66558d3e353b5ef0cbc277c4034fc718" %}
[使用 WebSocket 传输层](/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/websocket-transport-layer.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/web-plugins/convai-web-sdk/react/audio-visualizer-optional.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
