实现音视频互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。
前提条件
- Windows 或 macOS 计算机,需满足以下要求:
- 下载声网 Web SDK 支持的浏览器。声网强烈推荐使用最新稳定版 Google Chrome 浏览器。
- 具备物理音视频采集设备。
- 可连接到互联网。如果你的网络环境部署了防火墙,请参考应用企业防火墙限制以正常使用声网服务。
- 搭载 2.2 GHz Intel 第二代 i3/i5/i7 处理器或同等性能的其他处理器。
 
- Node.js 及 npm
- 有效的声网账户和声网项目,请参考开通服务,从声网控制台获取以下信息:
- App ID:声网随机生成的字符串,用于识别你的 App 。
- 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
- 频道名称:用于标识频道的字符串。
 
创建 Web 项目
创建一个名为 agora_web_quickstart 的文件夹。一个 Web 客户端项目至少需包含以下文件:
- index.html: 用于设计 Web 应用的用户界面。
- basicVideoCall.js: 通过- AgoraRTCClient实现具体应用逻辑的代码。
- package.json: 安装并管理项目依赖。你可以通过命令行进入- agora_web_quickstart目录并运行- npm init命令来创建- package.json文件。
实现流程
下文介绍通过声网实时互动 SDK 在你的 Web 应用中实现音视频互动的详细步骤。
1. 集成 SDK
参考以下步骤通过 npm 将声网 Web SDK 集成到你的项目中:
- 
在 package.json文件的dependencies字段中添加agora-rtc-sdk-ng及版本号:JSON{
 "name": "agora_web_quickstart",
 "version": "1.0.0",
 "description": "",
 "main": "basicVideoCall.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "dependencies": {
 "agora-rtc-sdk-ng": "latest"
 },
 "author": "",
 "license": "ISC"
 }
- 
将以下代码复制到 basicVideoCall.js文件中,在你的项目中导入AgoraRTC模块。JavaScriptimport AgoraRTC from "agora-rtc-sdk-ng"
2. 实现用户界面
将以下代码复制到 index.html 实现客户端用户界面:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Video Web SDK Quickstart</title>
        <!--
        This line is used to refer to the bundle.js file packaged by webpack. A sample webpack configuration is shown in the later step of running your App .
        -->
        <script src="./dist/bundle.js"></script>
    </head>
    <body>
        <h2 class="left-align">Video Web SDK Quickstart</h2>
        <div class="row">
            <div>
                <button type="button" id="join">JOIN</button>
                <button type="button" id="leave">LEAVE</button>
            </div>
        </div>
    </body>
</html>
3. 实现音视频通话逻辑
参考以下步骤实现音视频通话的逻辑:
- 调用 createClient 方法创建 AgoraRTCClient对象。
- 调用 join 方法加入一个 RTC 频道,你需要在该方法中传入 App ID 、用户 ID、Token、频道名称。
- 先调用 createMicrophoneAudioTrack 通过麦克风采集的音频创建本地音频轨道对象,调用 createCameraVideoTrack 通过摄像头采集的视频创建本地视频轨道对象;然后调用 publish 方法,将这些本地音视频轨道对象当作参数即可将音视频发布到频道中。
- 当一个远端用户加入频道并发布音视频轨道时:
- 监听 client.on("user-published") 事件。当 SDK 触发该事件时,在这个事件回调函数的参数中你可以获取远端用户 AgoraRTCRemoteUser对象 。
- 调用 subscribe 方法订阅远端用户 AgoraRTCRemoteUser对象,获取远端用户的远端音频轨道RemoteAudioTrack和远端视频轨道RemoteVideoTrack对象。
- 调用 play方法播放远端音视频轨道。
 
- 监听 client.on("user-published") 事件。当 SDK 触发该事件时,在这个事件回调函数的参数中你可以获取远端用户 
下图展示了基础的一对一音视频通话的 API 调用。注意图中的方法是对不同的对象调用的。
将以下代码复制到 basicVideoCall.js 文件中,注意将 appID 和 token 替换为你自己的 App ID 和临时 Token。
import AgoraRTC from "agora-rtc-sdk-ng";
let rtc = {
    localAudioTrack: null,
    localVideoTrack: null,
    client: null,
};
let options = {
    // Pass your App ID here.
    appId: "Your App ID",
    // Set the channel name.
    channel: "test",
    // Pass your temp token here.
    token: "Your temp token",
    // Set the user ID.
    uid: 123456,
};
async function startBasicCall() {
    // Create an AgoraRTCClient object.
    rtc.client = AgoraRTC.createClient({mode: "rtc", codec: "vp8"});
    // Listen for the "user-published" event, from which you can get an AgoraRTCRemoteUser object.
    rtc.client.on("user-published", async (user, mediaType) => {
        // Subscribe to the remote user when the SDK triggers the "user-published" event
        await rtc.client.subscribe(user, mediaType);
        console.log("subscribe success");
        // If the remote user publishes a video track.
        if (mediaType === "video") {
            // Get the RemoteVideoTrack object in the AgoraRTCRemoteUser object.
            const remoteVideoTrack = user.videoTrack;
            // Dynamically create a container in the form of a DIV element for playing the remote video track.
            const remotePlayerContainer = document.createElement("div");
            // Specify the ID of the DIV container. You can use the uid of the remote user.
            remotePlayerContainer.id = user.uid.toString();
            remotePlayerContainer.textContent = "Remote user " + user.uid.toString();
            remotePlayerContainer.style.width = "640px";
            remotePlayerContainer.style.height = "480px";
            document.body.append(remotePlayerContainer);
            // Play the remote video track.
            // Pass the DIV container and the SDK dynamically creates a player in the container for playing the remote video track.
            remoteVideoTrack.play(remotePlayerContainer);
        }
        // If the remote user publishes an audio track.
        if (mediaType === "audio") {
            // Get the RemoteAudioTrack object in the AgoraRTCRemoteUser object.
            const remoteAudioTrack = user.audioTrack;
            // Play the remote audio track. No need to pass any DOM element.
            remoteAudioTrack.play();
        }
        // Listen for the "user-unpublished" event
        rtc.client.on("user-unpublished", user => {
            // Get the dynamically created DIV container.
            const remotePlayerContainer = document.getElementById(user.uid);
            // Destroy the container.
            remotePlayerContainer.remove();
        });
    });
    window.onload = function () {
        document.getElementById("join").onclick = async function () {
            // Join an RTC channel.
            await rtc.client.join(options.appId, options.channel, options.token, options.uid);
            // Create a local audio track from the audio sampled by a microphone.
            rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
            // Create a local video track from the video captured by a camera.
            rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
            // Publish the local audio and video tracks to the RTC channel.
            await rtc.client.publish([rtc.localAudioTrack, rtc.localVideoTrack]);
            // Dynamically create a container in the form of a DIV element for playing the local video track.
            const localPlayerContainer = document.createElement("div");
            // Specify the ID of the DIV container. You can use the uid of the local user.
            localPlayerContainer.id = options.uid;
            localPlayerContainer.textContent = "Local user " + options.uid;
            localPlayerContainer.style.width = "640px";
            localPlayerContainer.style.height = "480px";
            document.body.append(localPlayerContainer);
            // Play the local video track.
            // Pass the DIV container and the SDK dynamically creates a player in the container for playing the local video track.
            rtc.localVideoTrack.play(localPlayerContainer);
            console.log("publish success!");
        };
        document.getElementById("leave").onclick = async function () {
            // Destroy the local audio and video tracks.
            rtc.localAudioTrack.close();
            rtc.localVideoTrack.close();
            // Traverse all remote users.
            rtc.client.remoteUsers.forEach(user => {
                // Destroy the dynamically created DIV containers.
                const playerContainer = document.getElementById(user.uid);
                playerContainer && playerContainer.remove();
            });
            // Leave the channel.
            await rtc.client.leave();
        };
    };
}
startBasicCall();
运行项目
本文使用 webpack 打包项目,使用 webpack-dev-server 运行项目。
- 
在 package.json的dependencies中添加webpack-cli和webpack-dev-server字段中,在scripts字段中添加build和start:dev字段。JSON{
 "name": "agora_web_quickstart",
 "version": "1.0.0",
 "description": "",
 "main": "basicVideoCall.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1",
 "build": "webpack --config webpack.config.js",
 "start:dev": "webpack serve --open --config webpack.config.js"
 },
 "dependencies": {
 "agora-rtc-sdk-ng": "latest",
 "webpack": "5.28.0",
 "webpack-dev-server": "3.11.2",
 "webpack-cli": "4.10.0"
 },
 "author": "",
 "license": "ISC"
 }
- 
在项目目录中创建一个名为 webpack.config.js的文件,将以下代码复制到webpack.config.js配置 webpack:JavaScriptconst path = require("path");
 module.exports = {
 entry: "./basicVideoCall.js",
 output: {
 filename: "bundle.js",
 path: path.resolve(__dirname, "./dist"),
 },
 devServer: {
 compress: true,
 port: 9000,
 },
 };
- 
运行下列命令安装依赖: Shellnpm install
- 
运行下列命令通过 webpack 编译项目: Shell# Use webpack to package the project
 npm run build
- 
通过 webpack-dev-server 运行项目: Shellnpm run start:dev你的浏览器会自动打开以下页面: 
点击 JOIN 加入频道。你还可以邀请朋友克隆 https://github.com/AgoraIO/API-Examples-Web 项目到本地,在浏览器中打开 Demo/index.html 文件,并输入相同的 App ID、频道名称和临时 Token。你的朋友加入频道后,你们可以看到彼此,并听到彼此的声音。
后续步骤
为保障通信安全,在正式生产环境中,你需要在自己的 App 服务端生成 Token。详见使用 Token 鉴权。
更多信息
示例项目
声网在 GitHub 上提供一个开源的示例项目供你参考。
其它集成方式
除上文介绍的使用 npm 获取 Web SDK 之外,你还可以使用以下方法获取 SDK:
- 
在项目 HTML 文件中,添加如下代码,使用 CDN 方法获取 SDK: html<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.18.3.js"></script>
- 
下载声网 Web SDK 4.x 版本 SDK 包至本地,将 SDK 包中的 .js文件保存到项目文件所在的目录下,然后在项目 HTML 文件中添加如下代码:html<script src="./AgoraRTC_N-4.18.3.js"></script>信息在以上方法中,SDK 都会在全局导出一个 AgoraRTC对象,直接访问这个对象即可操作 SDK。
常见问题
1. 为什么在本地运行快速开始项目时会报错 digital envelope routines::unsupported?
本文中的快速开始项目通过 webpack 打包并在本地运行。由于 Node.js 16 及以上版本更改了对 OpenSSL 的依赖(详见 node issue),影响了项目中本地开发环境的依赖(详见 webpack issue),运行项目会发生错误。解决方案如下:
- (推荐)运行如下命令,设置临时的环境变量:
Shellexport NODE_OPTIONS=--openssl-legacy-provider
- 暂时换用低版本的 Node.js。
然后再次尝试运行项目。