webrtc 实现实时通讯技术

304 阅读9分钟

什么是 WebRTC

WebRTC(Web Real-Time Communications)是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC 包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。

原文

原文:本文是根据声网的文档复译得笔记

官网: 需要注册下账号,后续会使用

前提条件

  • Windows 或 macOS 计算机,需满足以下要求:

  • Node.js 及 npm

  • 有效的声网账户和声网项目,请参考开通服务,从声网控制台获取以下信息:

    • App ID:声网随机生成的字符串,用于识别你的 App 。
    • 临时 Token:你的 App 客户端加入频道时会使用 Token 对用户进行鉴权。临时 Token 的有效期为 24 小时。
    • 频道名称:用于标识频道的字符串(随意,可test之类的)。

创建Web项目

创建一个名为 agora_web_quickstart 的文件夹。一个 Web 客户端项目至少需包含以下文件:

  • index.html: 用于设计 Web 应用的用户界面。
  • basicVideoCall.js: 通过 AgoraRTCClient 实现具体应用逻辑的代码。
  • package.json: 安装并管理项目依赖。

实现流程

1.集成SDK

  1. 在 package.json 文件的 dependencies 字段中添加 agora-rtc-sdk-ng 及版本号:
{  
    "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"
  1. 将以下代码复制到 basicVideoCall.js 文件中,在你的项目中导入 AgoraRTC 模块。
import 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. 实现音视频通话逻辑

  1. 创建本地客户端对象

调用 createClient 方法创建 AgoraRTCClient 对象

首先我们需要创建一个本地客户端对象,参数: mode我们选择rtc, 编码有H.264和VP8两种,我这里没有兼容性要求,所以选用默认的VP8,参考官方文档

const rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
  1. 加入频道

调用 join 方法加入一个 RTC 频道,加入频道这里需要传入4个参数:声网的项目appid,频道名称,如果鉴权开启了安全模式需要传入token,最后传入uid,uid为null时SDK会自动生成一个uid返回给你。

如果你是用app证书通过自己的代码生成token,需要保证频道名称和uid与生成token时保持一致,否则加入频道会失败。

临时token的生成方法,首先官网注册登录,项目管理 => 创建项目 => 点击配置

1701827525033.png

进入配置页面后找到临时token生成器

1701827715623.png

现在你就已经有 App ID, 频道名(生成token那个),token, uid(数字或字符串随便写, 为null时SDK会自动生成一个uid返回给你)

const uid = await rtcClient.join(<appid>, <channel name>, <token>, <uid>);
  1. 创建本地音视频轨道

加入完频道我们开始创建本地音视频轨道,调用createMicrophoneAudioTrack通过本地麦克风采集的音频创建音频轨道对象,调用 createCameraVideoTrack通过本地摄像头采集的视频创建视频轨道对象。你也可以在调用时传入参数来调整编码、前置\后置摄像头等配置。同时这里还需要注意浏览器提示或相关的设置,允许浏览器访问摄像头、麦克风等设备,必要时对用户进行引导和提示。

const localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
const localVideoTrack = await AgoraRTC.createCameraVideoTrack();
  1. 播放本地音视频轨道

创建完本地音视频轨道对象后,我们可以调用play()进行播放,预览一下音频和视频。播放音频轨道时不需要传入任何参数,播放视频轨道时需要指定一个DOM元素,你可以传入一个元素对象,也可以传入元素的id值。之后SDK会自动在该元素内部生成一个video元素播放视频轨道。

<div id="video-container"></div>
// 播放视频轨道
localVideoTrack.play(document.getElementById('video-container'));
// or
localVideoTrack.play('video-container');

// 播放音频轨道
localAudioTrack.play();
  1. 发布本地音视频轨道

现在我们需要调用publish将我们本地的音视频轨道发布到频道中,一个本地客户端对象可以发布多个音频轨道,比如:背景音乐、麦克风声音等,SDK会自动将其合并为一个音频轨道发布到频道中去,但是一个本地客户端对象只能发布一个视频轨道,如果有发布多个视频轨道的需求,你可以创建多个本地客户端对象加入同一个频道,然后发布不同的视频轨道,但是需要注意区分2个client使用的uid,并在本地订阅时过滤掉,防止重复订阅。

await rtcClient.publish([
    localAudioTrack,
    localVideoTrack
]);
  1. 订阅远端用户并播放远端音视频
  • 当一个远端用户加入频道并发布音视频轨道时:

    1. 监听 client.on("user-published") 事件。该回调通知远端用户发布了新的音频轨道或者视频轨道。当 SDK 触发该事件时,在这个事件回调函数的参数中你可以获取远端用户 AgoraRTCRemoteUser 对象 。
    2. 监听 client.on("user-unpublished")事件,该回调通知远端用户取消发布了音频或视频轨道。
    3. 调用 subscribe 方法订阅远端用户 AgoraRTCRemoteUser 对象,获取远端用户的远端音频轨道 RemoteAudioTrack 和远端视频轨道 RemoteVideoTrack 对象。
    4. 调用 play 方法播放远端音视频轨道。
<div id="remote-user"></div>
rtcClient.on("user-published", async (user, mediaType) => {
    await rtcClient.subscribe(user, mediaType);
    if (mediaType === "video") {
        console.log("subscribe video success", user);
        user.videoTrack.play(document.getElementById('remote-user'));
    }
    if (mediaType === "audio") {
        console.log("subscribe audio success");
        user.audioTrack.play();
    }
});

下图展示了基础的一对一音视频通话的 API 调用。注意图中的方法是对不同的对象调用的。

image.png

4. 完整代码

将以下代码复制到 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: "798f5d92652148738dad666a08c857e5",
    // Set the channel name.
    channel: "test",
    // Pass your temp token here.
    token: "007eJxTYFCtOWU8W6pn+xHt3X/Fd3Ef/nX+qcGeN+eKeZ5mXXwRzvNAgSEtJSUlOS3NxNDEwMLE1NAsMdU0xcgyydzAKNnUwjDV4PK1vNSGQEaGMxfbWBkZIBDEZ2EoSS0uYWAAADN6Izg=",
    // Set the user ID.
    uid: 123456,
};

async function startBasicCall() {
    // 创建一个 AgoraRTCClient 对象.
    rtc.client = AgoraRTC.createClient({mode: "rtc", codec: "vp8"});

    // 监听 “user-published” 事件,你可以从中获取 AgoraRTCRemoteUser 对象
    rtc.client.on("user-published", async (user, mediaType) => {
        // 当 SDK 触发 “user-published” 事件时订阅远端用户
        await rtc.client.subscribe(user, mediaType);
        console.log("subscribe success");

        // 如果远端用户发布视频轨道。
        if (mediaType === "video") {
            // 获取 AgoraRTCRemoteUser 对象中的 RemoteVideoTrack 对象.
            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 (mediaType === "audio") {
            // 获取 AgoraRTCRemoteUser 对象中的 RemoteAudioTrack 对象
            const remoteAudioTrack = user.audioTrack;
            // 播放远程音轨。无需传递任何 DOM 元素。
            remoteAudioTrack.play();
        }

        // 侦听“user-unpublished”事件
        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 () {
            // 加入 RTC 频道。
            await rtc.client.join(options.appId, options.channel, options.token, options.uid);
            // 从麦克风采样的音频创建本地音轨。
            rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
            // 从摄像机拍摄的视频创建本地视频轨道。
            rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
            // 将本地音视频轨道发布到RTC通道。
            await rtc.client.publish([rtc.localAudioTrack, rtc.localVideoTrack]);
            // 以 DIV 元素的形式动态创建容器,用于播放本地视频轨道。
            const localPlayerContainer = document.createElement("div");
            // 指定 DIV 容器的 ID。您可以使用本地用户的 uid。
            localPlayerContainer.id = options.uid;
            localPlayerContainer.textContent = "Local user " + options.uid;
            localPlayerContainer.style.width = "640px";
            localPlayerContainer.style.height = "480px";
            document.body.append(localPlayerContainer);

            // 播放本地视频轨道
            // 传递 DIV 容器,SDK 会在容器中动态创建一个播放器,用于播放本地视频轨道。
            rtc.localVideoTrack.play(localPlayerContainer);
            console.log("publish success!");
        };

        document.getElementById("leave").onclick = async function () {
            //销毁本地音视频轨道。
            rtc.localAudioTrack.close();
            rtc.localVideoTrack.close();

            // 遍历所有远程用户。
            rtc.client.remoteUsers.forEach(user => {
                // 销毁动态创建的 DIV 容器。
                const playerContainer = document.getElementById(user.uid);
                playerContainer && playerContainer.remove();
            });

            // 离开频道。
            await rtc.client.leave();
        };
    };
}

startBasicCall();


5. 运行项目

本文使用 webpack 打包项目,使用 webpack-dev-server 运行项目。

  1. 在 package.json 的 dependencies 中添加 webpack-cli 和 webpack-dev-server 字段中,在 scripts 字段中添加 build 和 start:dev 字段。
{
    "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"
}
  1. 在项目目录中创建一个名为 webpack.config.js 的文件,将以下代码复制到 webpack.config.js 配置 webpack:
const path = require("path");

module.exports = {
    entry: "./basicVideoCall.js",
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist"),
    },
    devServer: {
        compress: true,
        port: 9000,
    },
};
  1. 运行下列命令安装依赖:
npm install
  1. 运行下列命令通过 webpack 编译项目:
# Use webpack to package the project
npm run build
  1. 通过 webpack-dev-server 运行项目:
npm run start:dev

*注:本文中的快速开始项目通过 webpack 打包并在本地运行。由于 Node.js 16 及以上版本更改了对 OpenSSL 的依赖(详见 node issue),影响了项目中本地开发环境的依赖(详见 webpack issue),运行项目会发生错误。解决方案如下:

  • (推荐)运行如下命令,设置临时的环境变量:
export NODE_OPTIONS=--openssl-legacy-provider
  • 建议切换成Node14版本

运行成功后浏览器会自动打开以下页面:

image.png

点击 JOIN 加入频道。你还可以邀请朋友克隆 github.com/AgoraIO/API… 项目到本地,在浏览器中打开 Demo/index.html 文件,并输入相同的 App ID、频道名称和临时 Token。你的朋友加入频道后,你们可以看到彼此,并听到彼此的声音。