实现视频通话
实时视频通话能够拉近人与人之间的距离,为用户提供沉浸式的交流体验,帮助你的 App 提高用户黏性。
本文介绍如何通过声网视频 React SDK 提供的组件和 Hooks,在你的 App 里实现高质量、低延迟的视频通话功能。
前提条件
开始之前你需要满足以下前提条件:
- 创建一个声网项目,并获取 App ID、频道名和临时 Token,详见开通服务。
- 了解 React 基础知识。
立即体验
在 CodeSandbox 上,只需简单几步就能立即体验完整代码的运行效果。
具体步骤:
- 填入你获取的 App ID、频道名、临时 Token。
- 点击 Join Channel 按钮。你可以在视频画面中看到自己。
- 请一位朋友在浏览器中打开相同的页面链接,重复步骤 1 和 2 并确保填入和你相同的 App ID、频道名和临时 Token。成功加入频道后,你们可以看到和听到对方。
- 点击左下角的麦克风和摄像头按钮即可关闭或打开对应设备,点击右下角的挂断按钮即可结束通话。
你还可以直接修改左侧编辑器的代码,在右侧实时预览运行效果。
如果 CodeSandbox 访问速度较慢,请尝试更改你的网络配置。
实现视频通话
本节将分步骤介绍如何实现一个简单的视频通话 App 。
安装依赖
npm install agora-rtc-react agora-rtc-sdk-ng
- 使用 npm 安装依赖时,声网推荐你同时安装
agora-rtc-react
和agora-rtc-sdk-ng
依赖项(如上所示)。agora-rtc-sdk-ng
是agora-rtc-react
的peerDependencies
,这样安装可以确保项目中的agora-rtc-sdk-ng
符合版本要求,从而保证agora-rtc-react
的最佳使用体验。 agora-rtc-react
对应 React SDK,agora-rtc-sdk-ng
对应 RTC Web SDK 4.x,二者的关系详见 React SDK 和 Web SDK 的关系。
初始化 SDK
首先调用 Web SDK 提供的 createClient 方法创建 client
对象,用于 <AgoraRTCProvider />
组件。
然后使用 <AgoraRTCProvider />
组件包裹<App />
组件,方便后续使用状态和操作相关的 Hooks。
import React, { StrictMode } from 'react';
import { createRoot } from "react-dom/client";
import AgoraRTC from "agora-rtc-sdk-ng";
import { AgoraRTCProvider } from "agora-rtc-react";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
root.render(
<StrictMode>
<AgoraRTCProvider client={client}>
<App />
</AgoraRTCProvider>
</StrictMode>
);
加入频道
在 <App />
组件中,我们再创建一个 <Basics />
组件,实现音视频通话的逻辑。
首先使用 useJoin
这个 Hook 来加入频道,其中传入的 joinOptions
包含以下字段:
appid
:标识你在声网控制台创建的项目。channel
:标识用户所在的频道。频道是由开发者调用声网提供的 API 创建的、用于传输实时数据的通道。token
:在加入频道时用于校验用户权限的一组字符串。
这些字段将通过表单的形式向用户收集。收集到字段后,还需要提供一个按钮给用户点击,表示准备好加入频道。
import {
LocalUser,
RemoteUser,
useIsConnected,
useJoin,
useLocalMicrophoneTrack,
useLocalCameraTrack,
usePublish,
useRemoteUsers,
} from "agora-rtc-react";
import React, { useState } from "react";
import "./styles.css";
import logo from "./logo.png";
export const Basics = () => {
// 定义状态变量
const [calling, setCalling] = useState(false); // 是否正在呼叫
const [appId, setAppId] = useState(""); // 存储 App ID 的状态
const [channel, setChannel] = useState(""); // 存储频道名的状态
const [token, setToken] = useState(""); // 存储 Token 的状态
// 使用 App ID、频道名和 Token 加入频道,是否加入频道取决于 calling 的状态
useJoin({appid: appId, channel: channel, token: token ? token : null}, calling);
return (
<>
<div className="room">
<div className="join-room">
<img alt="agora-logo" className="logo" src={logo} />
<input
onChange={e => setAppId(e.target.value)}
placeholder="<Your App ID>"
value={appId}
/>
<input
onChange={e => setChannel(e.target.value)}
placeholder="<Your channel Name>"
value={channel}
/>
<input
onChange={e => setToken(e.target.value)}
placeholder="<Your token>"
value={token}
/>
<button
className={`join-channel ${!appId || !channel ? "disabled" : ""}`}
disabled={!appId || !channel}
onClick={() => setCalling(true)}
>
<span>Join Channel</span>
</button>
</div>
</div>
</>
);
};
export default Basics;
接下来可以给 <Basics />
组件添加 CSS 样式。示例项目中使用 styles.css,加入频道界面的效果如下:

现在 App 已经实现了用户加入频道的功能和界面,但用户无法听到和看到对方,还不能进行通话。
渲染本地和远端的音视频
为了让用户能互相看到和听到对方,App 需要渲染本地用户和远端用户的音视频。
渲染本地用户音视频的步骤如下:
- 使用
useLocalMicrophoneTrack
和useLocalCameraTrack
创建麦克风音频轨道和摄像头视频轨道。 - 使用
usePublish
发布本地用户的音频和视频轨道。 - 使用
<LocalUser />
组件渲染本地用户的音频和视频。
渲染远端用户音视频的步骤如下:
- 使用
useRemoteUsers
获取所有远端用户。 - 使用
<RemoteUser />
组件渲染每一位远端用户的音频和视频。
此外,你还可以在界面上提供以下按钮:
- 打开/关闭麦克风。
- 打开/关闭摄像头。
- 结束通话。
export const Basics = () => {
const [calling, setCalling] = useState(false);
const [appId, setAppId] = useState("");
const [channel, setChannel] = useState("");
const [token, setToken] = useState("");
useJoin({appid: appId, channel: channel, token: token ? token : null}, calling);
// 本地用户
const [micOn, setMic] = useState(true);
const [cameraOn, setCamera] = useState(true);
const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
const { localCameraTrack } = useLocalCameraTrack(cameraOn);
usePublish([localMicrophoneTrack, localCameraTrack]);
// 远端用户
const remoteUsers = useRemoteUsers();
return (
<>
<div className="room">
<div className="user-list">
<div className="user">
<LocalUser
audioTrack={localMicrophoneTrack}
cameraOn={cameraOn}
micOn={micOn}
videoTrack={localCameraTrack}
cover="https://www.agora.io/en/wp-content/uploads/2022/10/3d-spatial-audio-icon.svg"
>
<samp className="user-name">You</samp>
</LocalUser>
</div>
{remoteUsers.map((user) => (
<div className="user" key={user.uid}>
<RemoteUser cover="https://www.agora.io/en/wp-content/uploads/2022/10/3d-spatial-audio-icon.svg" user={user}>
<samp className="user-name">{user.uid}</samp>
</RemoteUser>
</div>
))}
</div>
</div>
<div className="control">
<div className="left-control">
<button className="btn" onClick={() => setMic(a => !a)}>
<i className={`i-microphone ${!micOn ? "off" : ""}`} />
</button>
<button className="btn" onClick={() => setCamera(a => !a)}>
<i className={`i-camera ${!cameraOn ? "off" : ""}`} />
</button>
</div>
<button
className={`btn btn-phone ${calling ? "btn-phone-active" : ""}`}
onClick={() => setCalling(a => !a)}
>
{calling ? <i className="i-phone-hangup" /> : <i className="i-mdi-phone" />}
</button>
</div>
</>
);
};
export default Basics;
最终的通话界面如下:

根据连接状态更新 UI
现在 App 已经具备加入频道界面和通话界面,但两个界面之间无法切换。
我们需要使用 useIsConnected
获取用户的连接状态,并且根据连接状态更新 App 界面:
- 在加入频道界面点击加入频道后,如果用户连接成功,App 显示通话界面。
- 在通话界面点击退出通话后,如果连接成功断开,App 显示加入频道界面。
export const Basics = () => {
const [calling, setCalling] = useState(false);
const isConnected = useIsConnected(); // 存储用户的连接状态
const [appId, setAppId] = useState("");
const [channel, setChannel] = useState("");
const [token, setToken] = useState("");
useJoin({appid: appId, channel: channel, token: token ? token : null}, calling);
const [micOn, setMic] = useState(true);
const [cameraOn, setCamera] = useState(true);
const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
const { localCameraTrack } = useLocalCameraTrack(cameraOn);
usePublish([localMicrophoneTrack, localCameraTrack]);
const remoteUsers = useRemoteUsers();
return (
<>
<div className="room">
{isConnected ? (
<div className="user-list">
<div className="user">
<LocalUser
audioTrack={localMicrophoneTrack}
cameraOn={cameraOn}
micOn={micOn}
videoTrack={localCameraTrack}
cover="https://www.agora.io/en/wp-content/uploads/2022/10/3d-spatial-audio-icon.svg"
>
<samp className="user-name">You</samp>
</LocalUser>
</div>
{remoteUsers.map((user) => (
<div className="user" key={user.uid}>
<RemoteUser cover="https://www.agora.io/en/wp-content/uploads/2022/10/3d-spatial-audio-icon.svg" user={user}>
<samp className="user-name">{user.uid}</samp>
</RemoteUser>
</div>
))}
</div>
) : (
<div className="join-room">
<img alt="agora-logo" className="logo" src={agoraLogo} />
<input
onChange={e => setAppId(e.target.value)}
placeholder="<Your App ID>"
value={appId}
/>
<input
onChange={e => setChannel(e.target.value)}
placeholder="<Your channel Name>"
value={channel}
/>
<input
onChange={e => setToken(e.target.value)}
placeholder="<Your token>"
value={token}
/>
<button
className={`join-channel ${!appId || !channel ? "disabled" : ""}`}
disabled={!appId || !channel}
onClick={() => setCalling(true)}
>
<span>Join Channel</span>
</button>
</div>
)}
</div>
{isConnected && (
<div className="control">
<div className="left-control">
<button className="btn" onClick={() => setMic(a => !a)}>
<i className={`i-microphone ${!micOn ? "off" : ""}`} />
</button>
<button className="btn" onClick={() => setCamera(a => !a)}>
<i className={`i-camera ${!cameraOn ? "off" : ""}`} />
</button>
</div>
<button
className={`btn btn-phone ${calling ? "btn-phone-active" : ""}`}
onClick={() => setCalling(a => !a)}
>
{calling ? <i className="i-phone-hangup" /> : <i className="i-mdi-phone" />}
</button>
</div>
)}
</>
);
};
export default Basics;
离开频道
当用户点击离开频道按钮、关闭浏览器窗口或刷新浏览器标签页时,由于触发了 React 组件的销毁,useJoin
会执行离开频道的操作。你不需要额外调用 API。
后续步骤
在测试或生产环境中,为保证通信安全,声网推荐从服务器中获取 Token,详情请参考使用 Token 鉴权。
更多信息
示例项目
声网在 GitHub 上提供一个开源的示例项目 agora-rtc-react 供你参考,包括 React SDK 提供的每个组件和 Hook 以及部分进阶功能的效果展示。
集成 SDK 的其他方法
除了通过 npm 集成 React SDK,你也可以在 HTML 文件中添加如下代码,使用 CDN 方法获取 SDK:
<!-- 必须按照以下顺序依次引入 -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.18.2.js"></script>
<!-- 如果以上依赖库都已经通过 npm 集成,只需要添加这一行 -->
<script src="https://download.agora.io/sdk/release/agora-rtc-react.2.0.0-beta.0.js"></script>