human: 如何处理七牛云的实时音视频实现视频通话

gpt: [实时音视频](https://developer.qiniu.com/rtc) > 快速入门 > Web >实现视频通话

#  实现视频通话

最近更新时间: 2022-07-14 15:44:44

如果您已经完成了上文中提到的开发准备，现在就让我们开始编写一个基本的实时音视频应用吧。这个应用会展示 SDK 基本的连麦功能，包括
加入房间，采集，发布，订阅 等过程。但是在实际的连麦应用开发过程中，是需要后端介入的（用于给用户鉴权并生成
RoomToken），在这里为了减少开发的时间，请您先预先生成 2 个 RoomToken（要求同一个 APPID，同一个 RoomName，不同的
UserName）。

> 如果您还不知道如何生成 RoomToken，请先阅读
> [七牛实时音视频云接入指南](https://developer.qiniu.com/rtc/8813/roomToken)

下文代码中的 `ROOMTOKEN_1` 和 `ROOMTOKEN_2` 表示生成的两个RoomToken。  
我们将会编写 1 个独立的 `html` 页面，主要包括采集本地的摄像头/麦克风数据并将其发布与订阅发布流并将其展示在页面上的功能。

> 您可以从 [这里](https://github.com/pili-engineering/QNRTC-SampleCode-Video-
> Basic/tree/master/One-On-One-Video/One-On-One-Video-Web/native) 查看这个 Demo
> 最终的成品代码，但是还是建议您在按照步骤阅读完之后再参阅。

# 引入 SDK 并准备页面

如果一切准备就绪，让我们开始吧，首先创建一个 `oneOnOne.html` 文件。

    
    
    <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8" />
        <title>One on One Sample</title>
        <script src="https://docs.qnsdk.com/qnweb-rtc.js"></script>
        <style>
          #localtracks, #remotetracks {
            width: 320px;
            height: 240px;
            background: #000;
          }
        </style>
        </head>
        <body>
            <label>请输入 RoomToken 加入房间开始连麦</label>
            <input id="roomtoken" type="text" />
            <button onclick="joinRoom()">加入房间</button>
            <p>本地视频</p>
            <div id="localtracks"></div>
            <p>远端视频</p>
            <div id="remotetracks"></div>
        </body>
    </html>
    

> **<https://docs.qnsdk.com/qnweb-rtc.js> 该链接仅供测试，正式环境需要自行准备 JS 文件或自行 NPM
> 下载。JS SDK 文件 放在我们的 [Github](https://github.com/pili-engineering/QNRTC-
> Web/blob/master/Release/qnweb-rtc.js) 上，只需要访问我们的
> [Github](https://github.com/pili-engineering/QNRTC-
> Web/blob/master/Release/qnweb-rtc.js) 页面，然后替换一下自己的 js 文件即可。**

然后在项目的目录下起一个 `http` 服务，打开 Chrome 通过 `localhost` 或者 `127.0.0.1` 访问刚刚那 2
个页面(因为屏幕和摄像头采集只能在 `localhost` 或者 `https` 下完成), 观察到控制台打印出了 sdk 的版本信息说明这一步完成了。

> 如果您不知道如何在本地起一个 `http` 服务，推荐使用基于 nodejs 的 [http-
> server](https://github.com/indexzero/http-
> server)。可以参考[这篇文章](http://www.cnblogs.com/xing901022/p/4286273.html)。

> 注意！Chrome 只允许 `localhost` 或者 `https` 的网络页面访问媒体设备。请在之后的开发和上线中注意这一点

# 加入房间

上文提到过，SDK 所有的功能都是从 `RoomToken` 开始的，所以加入房间只需要将 `RoomToken` 作为参数传给 SDK 就可以了。编辑
`html` 在 `&ltscript>` 中添加如下代码。

    
    
    // 确认引入成功
    console.log("current version", QNRTC.VERSION);
    // 这里采用的是 async/await 的异步方案，您也可以根据需要或者习惯替换成 Promise 的写法
    async function joinRoom() {
        // 创建QNRTCClient对象
        const client = QNRTC.createClient();
        const roomTokenInput = document.getElementById("roomtoken");
        const roomToken = roomTokenInput.value;
        // 这里替换成刚刚生成的 RoomToken
        await client.join(roomToken);
        console.log("joinRoom success!");
    }
    

再次访问这个页面，输入 `ROOMTOKEN_1` 点击加入房间按钮，看到控制台打印出 `joinRoom success!` 表示这一步完成。

# 采集并发布本地的音视频轨

接下来，我们的任务是在 `html` 下采集本地的媒体数据并把它发布到房间中。在编写代码之前，请先确认您的电脑已经连接了摄像头和麦克风设备（支持 USB
设备）。

> 如果没有摄像头或者没有麦克风（缺少其中一个），请稍微修改下文中的采集参数关闭音频或者视频采集

这里我们将要采集 2 个音视频轨，分别是 `摄像头视频轨` 和 `麦克风音频轨`。

编辑 `html` 中的 `&ltscript>`，添加如下代码。

    
    
    // 增加一个函数 publish，用于采集并发布自己的媒体流
    // 这里的参数 client 是指刚刚初始化的 QNRTCClient 对象
    async function publish(client) {
      // 同时采集麦克风音频和摄像头视频轨道。
      // 这个函数会返回一组audio track 与 video track
      const localTracks = await QNRTC.createMicrophoneAndCameraTracks();
      console.log("my local tracks", localTracks);
      // 将刚刚的 Track 列表发布到房间中
      await client.publish(localTracks);
      console.log("publish success!");
    }
    

这个函数很简单，主要就 2 个语句，我们现在仔细分析一下这 2 行代码分别做了什么。  
第一句，调用了 QNRTC 提供的静态方法 `createMicrophoneAndCameraTracks`
，从名字我们可以看出，这个方法负责采集本地的麦克风频轨与摄像头轨道，并将其包装成 SDK 的 `Track` 对象并以列表的形式返回给我我们。

第二句，调用了 QNRTCClient 提供的方法 `publish` ，将本地 `Track` 发布至远端。

然后在刚刚 `joinRoom` 的函数末尾调用我们刚刚编写的 `publish`

    
    
    async function joinRoom() {
      // 创建QNRTCClient对象
      const client = QNRTC.createClient();
      const roomTokenInput = document.getElementById("roomtoken");
      const roomToken = roomTokenInput.value;
      // 这里替换成刚刚生成的 RoomToken
      await client.join(roomToken);
      console.log("joinRoom success!");
      await publish(client);
    }
    

现在访问页面，加入房间后 Chrome 应该会申请媒体设备的权限，同意之后观察控制台打印出 `publish success!` 表示发布成功。  
如果您有需要想要看到自己刚刚采集的视频画面，可以在 `publish` 函数的末尾添加如下代码

    
    
    ...
    ...
    await client.publish(localTracks);
    console.log("publish success!");
    
    // 在这里添加
    // 获取页面上的一个元素作为播放画面的父元素
    const localElement = document.getElementById("localtracks");
    // 遍历本地采集的 Track 对象
    for (const localTrack of localTracks) {
        console.log(localTrack)
        // 如果这是麦克风采集的音频 Track，我们就不播放它。
        if (localTrack.isAudio()) continue;
        // 调用 Track 对象的 play 方法在这个元素下播放视频轨
        localTrack.play(localElement, {
            mirror: true
        });
    }
    

下一步，我们将在 `html` 中完成订阅的代码，实现对我们刚刚发布的音视频轨道的订阅。

# 订阅远端发布的音视频轨

从上文发布的过程中我们可以知道，一个用户可以发布多个 `Track` ，而在实际需求中我们不一定会把他所有发布的 Track
都订阅（可能只订阅音频之类的），所以订阅的目标是以 `Track` 为单位的。  
那么，我怎么知道房间中有哪些 `Track` ，这些 `Track` 又分别对应哪些用户呢？这就是我们接下来讲述的重点。

我们可以通过 SDK 的 `user-published` 事件获取当前房间的 `Track` 列表：

> 需要在join房间之前监听对应事件，`join` 加入房间成功后，`user-published` 会返回当前所有房间内已发布 `Track`
> 列表以及用户 `userID` 。

进入房间后，当一个远端用户发布了自己的 `Track` ，此时在我们这边也会触发 `user-published` 事件，事件会带上远端用户的
`userID` 以及所发布的 `Track` 列表。

具体让我们通过代码来理解，在 `oneOnOne.html` 的 `&ltscript>` 中添加函数 `subscribe` 和
`autoSubscribe`, `subscribe` 用于传入 TrackInfo 完成订阅操作并播放订阅流，`autoSubscribe`
用于实时检测房间的 TrackInfo 变化并在合适的时机调用 `subscribe`。

    
    
    // 这里的参数 client 是指刚刚初始化的 QNRTCClient 对象
    async function subscribe(client, tracks) {
      // 传入 Track 对象数组调用订阅方法发起订阅，异步返回成功订阅的 Track 对象。
      const remoteTracks = await client.subscribe(tracks);
    
      // 选择页面上的一个元素作为父元素，播放远端的音视频轨
      const remoteElement = document.getElementById("remotetracks");
      // 遍历返回的远端 Track，调用 play 方法完成在页面上的播放
      for (const remoteTrack of [...remoteTracks.videoTracks, ...remoteTracks.audioTracks]) {
          remoteTrack.play(remoteElement);
      }
    }
    

> 在一次批量订阅中，如果想区分返回的 Track（比如播放屏幕分享的 `Track` 时给更大的界面），和上文一样，通过 tag 来区分。这里的 tag
> 来自于远端采集它的时候所指定的 tag。

代码不多，总共就两步。先是发起订阅拿到返回的 `Track` 对象，之后调用 `Track` 的 play 方法在页面上播放。  
下面我们实现 `autoSubscribe` 将 `TrackInfo` 传给 `subscribe`。

    
    
    // 这里的参数 client 是指刚刚初始化的 QNRTCClient 对象, 同上
    function autoSubscribe(client) {
        // 添加事件监听，当房间中出现新的 Track 时就会触发，参数是 trackInfo 列表
        client.on("user-published", (userId,tracks) => {
            console.log("user-published!", userId,tracks);
            subscribe(client, tracks)
            .then(() => console.log("subscribe success!"))
            .catch(e => console.error("subscribe error", e));
        });
        // 就是这样，就像监听 DOM 事件一样通过 on 方法监听相应的事件并给出处理函数即可
    }
    

最后，让我们在 `html` 的 `joinRoom` 中调用 `autoSubscribe` 即可

    
    
    async function joinRoom() {
      // 创建QNRTCClient对象
      const client = QNRTC.createClient();
      // 需要先监听对应事件再加入房间
      autoSubscribe(client);
      const roomTokenInput = document.getElementById("roomtoken");
      const roomToken = roomTokenInput.value;
      // 这里替换成刚刚生成的 RoomToken
      await client.join(roomToken);
      console.log("joinRoom success!");
      await publish(client);
    }
    

好啦，这个时候再访问这个页面，使用两个不同的 `RoomToken` 分别进入房间中，在 `html`
中看到你自己和屏幕的画面就代表成功啦。您成功完成了一个最简单的一对一连麦应用(而且还支持单用户多流)。

## 接下来...

经过上面的步骤之后，相信您已经对一次连麦互动的流程以及 SDK 的使用有了大概的了解。如果您已经准备正式开始接入我们的 SDK，下面一些建议可以让您更加熟悉
SDK。

  * SDK 使用中每个功能的细节都可以通过左侧的功能列表去查看
  * 如果您想了解 SDK 的架构以及所暴露的模块细节，可以阅读 [API 文档](https://developer.qiniu.com/rtc/9246/WEB%20API%20%E6%A6%82%E8%A7%88) 。

> 如果您在使用过程中遇到什么疑问，或者遇到了 BUG，请在 Github 的 [issue](https://github.com/pili-
> engineering/QNRTC-Web/issues) 列表提问，我们会尽快为您解答。

以上内容是否对您有帮助？