实时音视频云

  • 基本连麦应用

    最近更新时间:2018-08-31 17:49:40

    交互流程

    让我们把工作区切到之前创建的 app 文件夹,现在轮到前端部分,我们的目标是搭建一个连麦应用。首先,我们先梳理这个应用的整个交互流程。整个应用分为 2 个页面,主页(负责输入用户名,采集参数等等信息),房间页面(加入连麦房间后显示的页面,主要为自己和远端的视频画面)。用户首先进入主页输入用户名和房间号,点击进入房间后我们通过这个信息通过后台获取 roomToken,获取成功后加入连麦房间加入第二个页面,开始连麦。

    好的,让我们开始逐步完成这个流程

    获取 Web SDK 并准备主页

    这里我们不打算使用任何 web 开发框架,使用最传统的方式开发这个应用,所以我们通过直接引用 js 来引入 Web SDK。关于引入方式的细节,参照 引入方式。从 Github 上获取到 Web SDK 的最新代码,将其放置在 app/libs 文件夹下。

    创建 app/js 文件夹并新建一个空白的 index.js,我们将会在这里编写我们主页的 js 代码。

    app 文件夹下创建如下的 index.html

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>Index</title>
        </head>
        <body>
        <h1>Qiniu Web SDK Demo 1v1ver.</h1>
        <p>请输入以下信息加入房间</p>
    
        <form id="rtcroom">
          <input id="userid" type="text" placeholder="请输入用户名" required />
          <input id="roomname" type="text" placeholder="请输入房间号" required />
        </form>
    
        <button form="rtcroom">加入房间</botton>
    
        <script src="./js/index.js"></script>
        </body>
    </html>
    

    可以看到,主页就是一个简单的 form 表单,用户输入用户名和房间号,点击加入房间进入我们的下一个步骤。

    加入房间

    从加入连麦房间开始,就进入到连麦 SDK 负责的步骤了,我们使用这个 API 来 加入房间 。这个方法需要 roomToken 作为加入房间的参数,所以加入房间就分为了 2 个步骤:获取 roomToken、调用 SDK 加入房间。

    回到我们之前的设计,在加入房间之后需要进入房间页面进行连麦的逻辑。所以这里涉及到一个页面跳转,如果我们在页面跳转之前就调用连麦 SDK 的 joinRoomWithToken 方法,页面跳转后下一个页面同步上一个页面的 SDK 状态就会比较复杂。所以这里我们让第一个页面能在获取到 roomToken 的时候就跳转页面,将 roomToken 带给下一个页面,然后在房间页面调用 SDK 避免复杂的状态同步。

    // js/index.js
    document.getElementById('rtcroom').onsubmit = joinRoom;
    
    function joinRoom(e) {
      e.preventDefault();
      const userId = document.getElementById('userid').value;
      const roomName = document.getElementById('roomname').value;
      // 获取 roomToken
      fetch(`/roomtoken/user/${userId}/room/${roomName}`)
        .then(res => res.text())
        .then(roomToken => {
          // 跳转到 room.html
          window.location = "/room.html?token=" + roomToken;
        }).catch(e => {
          console.log('get roomToken error!', e);
        })
    }
    

    之后,让我们创建 room.html ,在下一个页面完成加入房间的逻辑,除了 html 文件以外,再创建 css/room.css文件来编写样式,以及 js/room.js 来放置我们 room 页面的 js 代码。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Room Page</title>
        <link rel="stylesheet" href="./css/room.css"></link>
        <script src="./libs/pili-rtc-web.js"></script>
      </head>
      <body>
        <div id="localplayer" class="mini-player"></div>
        <div id="remoteplayer" class="fullscreen-player"></div>
    
        <script src="./js/room.js"></script>
      </body>
    </html>
    

    这里 localplayerremoteplayer 分别用来放置自己和远端的视频流。

    /* css/room.css */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    .qnrtc-stream-player {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
    .fullscreen-player {
      width: 100vw;
      height: 100vh;
      position: absolute;
      top: 0;
      left: 0;
      background: #000;
    }
    .mini-player {
      width: 300px;
      height: 200px;
      position: absolute;
      bottom: 10px;
      right: 10px;
      background: #000;
      z-index: 10;
    }
    
    

    为 room 页面添加一些基本的 css,其中 .qnrtc-stream-player 这个类是 SDK 在播放音视频流自动生成的 video/audio 标签元素。详细情况可以见 stream.play

    好了,下面让我们在 room 页面调用 SDK 完成加入房间吧,在 js 文件夹下创建 room.js

    // js/room.js
    
    // 注意这里外层套了一个 async iife
    // 为了使下面的代码里能够运行 await 方法
    // 关于 async/await 的介绍可以看这里 https://www.jianshu.com/p/8d73e187b9e1
    (async () => {
      // 先通过地址的 query 拿到上一个页面传过来的 roomToken
      const tokenMatch = window.location.search.match(/\?token\=(.*)$/);
      const roomToken = tokenMatch[1];
      // 初始化 SDK
      const myRTC = new QNRTC.QNRTCSession();
      try {
        // 调用 SDK 加入房间
        const users = await myRTC.joinRoomWithToken(roomToken);
        console.log('joinRoom success! 当前房间用户:', users);
      } catch (e) {
        console.log('error!', e);
      }
    })();
    

    好啦,现在访问 http://localhost:8888 输入一对合法的用户名和房间号(只能有数字、字母和下划线且不少于 3 个字符),就会先获取 roomToken 然后跳转到 room 页面完成加入房间。您可以在 room 页面打开浏览器控制台,看到如下输出就代表加入房间成功了。

    您可以尝试新开一个 tab 页用另一个用户名访问同一个房间,观察房间里 log 输出的变化。

    预览并发布自己的视频流

    当用户加入房间之后,就可以将自己本地的视频流发布到房间里了,这就是我们通常所说的上麦(发布自己的流)。当然了,在发布自己的流之前,我们需要先采集自己的本地媒体流,所以整个过程就是 加入房间--采集本地媒体流—发布媒体流。让我们在 room.js 中加入如下代码

    // js/room.js
    ...
    ...
    const users = await myRTC.joinRoomWithToken(roomToken);
    console.log('joinRoom success!', users);
    // 采集本地媒体流,视频和音频都采集
    const localStream = await QNRTC.deviceManager.getLocalStream({
        video: { enabled: true, width: 640, height: 480, bitrate: 600 },
        audio: { enabled: true, bitrate: 32 },
    });
    // 获取我们 room.html 中准备用来显示本地媒体流的元素
    const localPlayer = document.getElementById('localplayer');
    // 调用媒体流的 play 方法,在我们指定的元素上播放媒体流,其中第二个参数代表 静音播放
    localStream.play(localPlayer, true);
    
    // 发布刚刚采集到的媒体流到房间
    await myRTC.publish(localStream);
    console.log('publish success!');
    ...
    ...
    

    这里涉及到 3 个 SDK 的 API,getLocalStream 用来采集本地的媒体流,play 用来指定一个页面元素播放媒体流, publish 用来将媒体流发布到房间。点击这 3 个方法的链接查看详细的 API 说明。

    好啦,重新访问 http://localhost:8888 加入房间,一切顺利的话就能在右下角看到自己的视频流。打开控制台,看到 publish success! 就代表发布也成功了,说明我们采集到的视频流已经顺利地发布到房间中了。

    自动订阅其他用户

    对于一对一连麦来说,这是一个基本功能,即自动订阅房间里另一个用户来获取他发布的媒体流。但是订阅这个操作不像发布一样进入房间就可以调用,订阅操作成功必须满足 2 个条件:

    • 获取订阅目标的用户名 userId
    • 订阅目标必须已经发布了自己的媒体流

    为了能让您更好地感受到这个 2 个条件何时被满足,建议您先感受一下两人依次加入房间的时候浏览器控制台的 log 输出的变化。浏览器打开 2 个 tab 页,都访问 http://localhost:8888,在第一个 tab 页以 user1 为用户名 roomtest 为房间号加入房间,加入房间后打开浏览器的控制台观察输出,在joinRoom success! 那一行我们可以看到当我们加入房间之后当前房间里的用户,此时只有user1 一人,也就是他自己。 这时我们切到第二个 tab 页,以 user2 为用户名 roomtest 为房间号也加入这个房间,观察第二个 tab 页的控制台输出,我们可以看到此时输出的当前房间里的用户就已经是 2 个人了。再切回第一个 tab 页观察控制台输出,我们收到了 2 个事件,分别是 user-join 代表有新用户加入了房间(user2),和 user-publish 代表房间内有其他用户发布了自己的媒体流。

    自己感受过一次之后,我们就知道,当一个用户加入房间之后,他可以立刻获得这个房间内已有用户的信息,其中 published 字段代表这个用户是否已经发布了媒体流,此时如果有除自己以外的用户已经发布的话,就满足了订阅条件可以发起订阅了。之后我们再通过事件监听获取之后发生的用户加入/用户发布事件,当满足订阅条件时发起订阅,这就是我们实现自动订阅功能实现的基础。

    讲的可能有点繁琐,让我们直接来看代码怎么写吧,订阅过程可能在加入房间后或者事件监听回调中发生,所以我们把这个过程抽出来作为一个函数复用。在 room.js 的一开始加入如下代码

    // js/room.js
    
    // 订阅用户的函数,myRTC 代表之前初始化 SDK 后拿到的示例
    // user 代表加入房间返回或者事件返回的单个用户对象
    function subscribeUser(myRTC, user) {
      // 如果用户没有发布就直接返回
      if (!user.published) {
        return;
      }
      // 注意这里订阅使用了 Promise 的写法而没有用 async/await
      // 因为在我们 Demo 中并没有依赖订阅这个操作的后续操作
      // 即没有操作必须等到订阅操作结束之后再运行
      myRTC.subscribe(user.userId).then(remoteStream => {
        // 我们在 room 页面上准备用来显示远端媒体流的元素
        const remotePlayer = document.getElementById('remoteplayer');
        // 在我们准备的元素上播放远端媒体流
        remoteStream.play(remotePlayer);
      }).catch(e => {
        console.log('subscribe error!', e);
      });
    }
    
    (async () => {
      const tokenMatch = window.location.search.match(/\?token\=(.*)$/);
      const roomToken = tokenMatch[1];
    ...
    ...
    

    好了,准备好了订阅函数之后让我们看准时机发起订阅把,继续在 room.js 中加入如下代码

    // js/room.js
    ...
    ...
    const users = await myRTC.joinRoomWithToken(roomToken);
    console.log('joinRoom success! 当前房间用户:', users);
    // 监听房间里的用户发布事件,一旦有用户发布,就订阅他
    myRTC.on('user-publish', user => {
        subscribeUser(myRTC, user);
    });
    // 判断房间当前的用户是否有可以订阅的
    for (let i = 0; i < users.length; i += 1) {
        const user = users[i];
        // 如果当前房间的用户不是自己并且已经发布
        // 那就订阅他
        if (user.published && user.userId !== myRTC.userId) {
            subscribeUser(myRTC, user);
        }
    }
    ...
    ...
    

    好啦,现在再重复之前 2 个 tab 页的操作,就能在页面上同时看到本地和远端了。这里我们使用了 SDK 的这 2 个功能,subscribe 用来订阅其他用户发布的媒体流,事件监听 用来通过事件回调同步房间各种状态的变化,事件列表见此。想要了解详细说明点击文中的链接。

    自动退出房间

    通过上面的步骤我们已经完成了一个连麦应用打大部分功能,这里我们做一个小优化。假设您现在正在使用这个应用进行 2 人连麦,此时关闭其中一人的浏览器窗口,我们在另一个人的页面发现远端的画面立刻卡住了,之后黑屏。此时打开控制台观察 log,发现 SDK 在不断尝试重新订阅,过了很久才会收到 user-unpublishuser-leave 这 2 个事件。这是因为我们在关闭浏览器之前没有立刻发出 “我马上要离开房间了” 这个信息给到房间其他人,其他端发现 P2P 连接断开后认为远端可能发生了网络波动在不断重试。

    所以当我们在关闭浏览器页面之前,需要调用 SDK 的 leaveRoom 方法来离开房间。如何在浏览器页面被关闭之前完成这个操作呢,onbeforeunload 事件 的设计就是为了满足这个需求。

    在我们的 room.js 中加入如下代码

    // js/room.js
    ...
    ...
    
    const users = await myRTC.joinRoomWithToken(roomToken);
    console.log('joinRoom success! 当前房间用户:', users);
    
    // 加入房间成功后注册事件,当页面被关闭就离开房间
    window.onbeforeunload = () => {
        myRTC.leaveRoom();
    };
    
    ...
    ...
    

    现在再重复我们刚刚关闭页面的操作,可以从远端的 log 中看出我们很快收到了用户取消发布和离开房间的消息。

    至此,我们完成了一个基本可用的一对一连麦应用,下一步,我们将逐渐完善这个应用的功能来达到我们的目标

    以上内容是否对您有帮助?
  • Icon free helper
    Close