用声网Agora 创建一对一交互式视频会议 Web 工具

当今社会,人与人之间的交流互动很多都转移到了线上。随着此类技术的普及,许多行业都难免要面对此类转型。

在过去的一年中,许多公司和开发人员都发布了可以取代传统人际交流方式的数字解决方案。创建实时视频推流和聊天 web 应用比之前容易多了。

本教程会向大家展示如何通过简单的步骤创建一个一对一视频会议工具。

这类工具在社会上的潜在用例:

  • 医患视频通话

  • 导师-学员视频通话

  • 客户-承包商视频通话

  • 一对一咨询视频通话

我们打算使用声网Agora 让双方参与到视频会议中。我们会使用 vanilla JavaScript 来创建这个项目,比较酷的是大家可以输入并创建自己的版本!

最终结果类似于下列 demo:

image

前期准备

首先,创建一个声网Agora 帐户,可以按照声网注册指南创建;另外,还需具备 JavaScript 和 HTML 的基本知识。(别担心,我会指导大家完成这个过程~)

此外,本教程将使用 Visual Studio Code 实时服务器 插件。

创建项目

首先,在我们的电脑里创建一个文件夹,将其命名为 agora-demo。

创建此项目结构后,在 Visual Studio Code 中打开该文件夹。VSCode 中的项目结构如下:

1-to-1-interactive-meeting-video-call-on-web-2

这是一个原生 JavaScript 驱动的教程,不包含任何框架。我们将使用 SASS 编译器 将 SCSS 文件转换为 CSS 文件。

我们还将使用 Live Server 来启动我们的项目。

运行 Live Server 后,我们可以在 127.0.0.1:5500/index.html 上查看我们的项目。

首先,创建一个声网Agora 账户。大家可以参照下列设置帐户的文档进行操作:

设置声网agora 账户

创建一对一的视频会议工具

我们回到 Visual Studio,这样就可以创建我们的工具了。

注意: 此 demo 只提供一个频道。虽然声网Agora 支持大家生成任意数量的频道,但在本指南中,我们不提供 UI,所以用户无法创建自己的频道。

HTML 结构

我们从在 index.html 中设置 HTML 开始。在我们的例子中,我们正在创建一个非常基本的外观,用户会看到一个屏幕,其中远端用户的推流位于中心,他们自己的推流位于右上角,底部有一些控件。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Video Call</title>
    <script
      src="https://kit.fontawesome.com/c8500fcdd6.js"
      crossorigin="anonymous"
    ></script>
    <link href="./styles/styles.css" rel="stylesheet" />
  </head>
  <body>
    <div class="container">
      <header><i class="fas fa-film"></i> Video call</header>

      <section class="holder holder--video">
        <div id="remote" class="holder--video-remote waiting"></div>
        <div id="me" class="holder--video-me"></div>
      </section>
      <section class="holder holder--options">
        <button class="holder--options-option" id="btnCam">
          <i class="fas fa-video"></i>
          Cam
        </button>
        <button class="holder--options-option" id="btnMic">
          <i class="fas fa-microphone"></i>
          Mic
        </button>
        <button class="holder--options-option hidden" id="btnStop">
          <i class="fas fa-window-close"></i> Leave
        </button>
        <button class="holder--options-option" id="btnStart">
          <i class="fas fa-plug"></i> Join
        </button>
      </section>
    </div>
    <script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.2.1.js"></script>
    <script src="./scripts/script.js"></script>
  </body>
</html>

我们还会使用 CDN 将 Fontawesome 和 Agora 加载到我们的项目中,并链接到 CSS 样式表和 JavaScript 文件中。HTML 部分到此结束。我们会得到如下结果:

1-to-1-interactive-meeting-video-call-on-web-3

看起来并不抓人眼球,对吧?

下一步我们将添加一些样式,对其进行美化。

设计应用程序

首先,重置所有的填充和外边距。

* {
  margin: 0;
  padding: 0;
  font-family: Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
}

让 body 充当一个网格,这样我们就可以把应用程序放置在页面中间。

body {
  display: -ms-grid;
  display: grid;
  place-items: center;
  min-height: 100vh;
  width: 100%;
  background: #f15bb51f;
}

然后我们可以给容器一些基本的 flex 样式,填充标题,使其不那么密集。

.container {
  display: flex;
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  flex-direction: column;
  padding: 1rem;
  header {
    margin-bottom: 1rem;
    font-size: 2rem;
    font-weight: bold;
    i {
      color: #00f5d4;
    }
  }
}

我们还需要一个隐藏类来隐藏开始和停止按钮。

.hidden {
  display: none !important;
}

然后,我们可以设置两个支架的样式(一个用于视频,一个用于按钮)。

视频样式如下所示:

.holder {
  border-radius: 15px;
  background: #00bbf9;
  width: 100%;
  min-height: 50px;
  border: 1px solid #333;

  &--video {
    position: relative;
    overflow: hidden;
    margin-bottom: 0.5rem;
    min-height: 500px;
    color: #fee440;
    &-me {
      position: absolute;
      right: 0.5rem;
      top: 0.5rem;
      width: 150px;
      height: 150px;
      overflow: hidden;
      border: 3px solid #fff;
      border-radius: 15px;
      background: #efefef;
      display: flex;
      justify-content: center;
      align-items: center;
      font-style: italic;
      &.connecting {
        &:before {
          content: "Connecting...";
        }
      }
    }
    &-remote {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 500px;
      &.waiting {
        &:before {
          content: "Waiting...";
        }
      }
      video {
        position: relative !important;
        display: block;
        transform: rotateY(180deg);
        object-fit: cover;
      }
    }
  }
}

这种样式能确保视频有一些空间,并且放置在其中的视频元素也有相应地定位。

大家可能已经发现了:before pseudo-element,我们使用它们向用户反馈正在发生的事情。

按钮栏需要以下样式:

.holder {
  &--options {
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: 1rem 0;
    &-option {
      display: flex;
      flex-direction: column;
      align-items: center;
      background-color: none;
      outline: none;
      border: 0px;
      background: none;
      color: #fff;
      i {
        background: #fff;
        border-radius: 50%;
        margin-bottom: 0.5rem;
        font-size: 1.5rem;
        width: 64px;
        height: 64px;
        display: flex;
        align-items: center;
        justify-content: center;
        color: #555;
        box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
      }
      &.active {
        i {
          color: #00f5d4;
        }
      }
    }
  }
}

通过使用 box-shadow 并给它们提供一些空间,我们可以让按钮更醒目。我们还添加了一个活动类来显示哪个按钮是活跃的。

我们的应用程序应该如下所示:

现在它已经有了会议工具的雏形了,但还不能工作。

连接 Agora

要连接到 Agora,我们需要在 script.js 文件中执行以下几个步骤:

首先,创建一些变量。我们需要一些选项卡,用来保存 appID 和 token,我们还将在这里添加我们的频道。

const options = {
  appId: "{APPID}",
  channel: "demo",
  token: "{TOKEN}",
};

另一个变量会保存用户的本地推流。

let rtc = {
  client: null,
  localAudioTrack: null,
  localVideoTrack: null,
};

然后,我们一次性添加想要访问的所有前端元素。

下一步是创建一个加入函数,把我们连接到声网 Agora。

const join = async () => {
  rtc.client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
  return await rtc.client.join(
    options.appId,
    options.channel,
    options.token,
    null
  );
};

创建视频会议

现在,我们已经设置好了 Agora,运行这个 app 并连接我们的按钮,这样就可以开始视频聊天啦!

我们要连接的第一个按钮是加入按钮。

btnStart.addEventListener("click", () => {
  startBasicCall();
});

当我们点击这个按钮时,它会运行 startBasicCall 函数。

这个函数将确保调用加入(join)函数,开启我们的视频和音频,并订阅音视频流。

async function startBasicCall() {
  join().then(() => {
    startVideo();
    startAudio();

    rtc.client.on("user-published", async (user, mediaType) => {
      await rtc.client.subscribe(user, mediaType);
      remote.classList.remove("waiting");

      if (mediaType === "video") {
        const remoteVideoTrack = user.videoTrack;
        remoteVideoTrack.play("remote");
      }

      if (mediaType === "audio") {
        const remoteAudioTrack = user.audioTrack;
        remoteAudioTrack.play();
      }
    });
  });
  btnStop.classList.remove("hidden");
  btnStart.classList.add("hidden");
}

大家可以看到,我们调用了加入函数,在回调中,我们调用了开启视频和音频函数。然后我们连接到推流,等待其他用户连接。如果连接完成,我们更新特定推流,开始接收。

remoteVideoTrack.play() 接受远端的参数,远端的参数引用它应该渲染的 div 的 ID。

最后,我们隐藏加入按钮并显示离开按钮。

现在,创建 startVideo 函数。

const startVideo = async () => {
  me.classList.add("connecting");
  rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack();
  rtc.client.publish(rtc.localVideoTrack);
  me.classList.remove("connecting");
  rtc.localVideoTrack.play("me");
  btnCam.classList.add("active");
};

首先,添加连接类,向用户显示我们正在做的事情。然后,设置 rtc 变量,使用 Agora 连接的视频轨道更新 localVideoTrack。接着,我们发布此轨道并删除连接类。

通过使用 rtc.localVideoTrack.play(“me”);,我们让 ID 为 “me” 的 div 播放该用户的本地推流。我们通过将活动类添加到我们的相机按钮来完成。

我们对 startAudio 函数采用相同操作,但我们使用 AgoraRTC.createMicrophoneAudioTrack 方法。

const startAudio = async () => {
  rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
  rtc.client.publish(rtc.localAudioTrack);
  btnMic.classList.add("active");
};

另外,我们还希望能够停止推流,所以要连接到停止按钮。

btnStop.addEventListener("click", () => {
  leave();
});

离开(leave)函数如下:

const leave = () => {
  stopVideo();
  stopAudio();
  rtc.client.leave();
  btnStop.classList.add("hidden");
  btnStart.classList.remove("hidden");
};

它会调用“停止视频和音频”函数,并离开频道。我们还用加入按钮切换了离开按钮。

创建停止(stop)函数。

const stopAudio = () => {
  rtc.localAudioTrack.close();
  rtc.client.unpublish(rtc.localAudioTrack);
  btnMic.classList.remove("active");
};

我们关闭本地音频连接,并对另一端接收器取消发布,然后从按钮中删除活跃类。

我们对视频也采取同样操作步骤。

const stopVideo = () => {
  rtc.localVideoTrack.close();
  rtc.client.unpublish(rtc.localVideoTrack);
  btnCam.classList.remove("active");
};

这样,我们就停止了整个推流。但如果我们只是想暂时断开我们的视频或音频怎么办?

btnCam.addEventListener("click", () => {
  btnCam.classList.contains("active") ? stopVideo() : startVideo();
});
btnMic.addEventListener("click", () => {
  btnMic.classList.contains("active") ? stopAudio() : startAudio();
});

我们可以根据音频或视频的活跃类,调用相应的开始或停止函数。

确保是一对一通话

由于我们想确保通话是一对一的,没有其他人可以加入我们的频道,所以我们用 Agora RTC 添加一层安保。

一旦有人加入此频道,我们就会检查客户端拥有的用户数。如果数字大于 1,它就是无效的,而且要删除加入的用户。

修改用户发布的回调:

rtc.client.on("user-published", async (user, mediaType) => {
  if (rtc.client._users.length > 1) {
    roomFull();
  }
  // Rest of the code
});

roomFull 函数会为我们处理所有逻辑:

const roomFull = () => {
  leave();
  remote.classList.add("full");
};

这回调用我们之前制作的离开(leave)函数,并为远端用户 div 添加一个完整的类。

现在我们只需要为这个 div 添加一些样式:

.holder - video-remote.full:before {
  content: "Room is full!";
}

大家可以在 GitHub 上 找到此 demo 的代码。

测试

如果要测试应用程序,大家可以从 GitHub 下载文件。请确保打开 script.js 文件并添加客户端详细信息,然后,在一个浏览器选项卡中打开 index.html 文件并将其复制到另一个选项卡中。

现在,大家应该在视频会议中看到自己两次了。请最好将麦克风静音,因为麦克风会产生回声。

总结

以上就是所有设置啦!我们现在有了一个使用 Agora 和 vanilla JavaScript 搭建的交互式会议工具!

感谢声网Agora,让创建一个交互式会议工具变得如此简单。希望大家能找到使用声网Agora 的其他方法并提出相应的视频解决方案。

原文作者 Chris Bongers
原文链接 https://www.agora.io/en/blog/creating-a-one-on-one-interactive-video-meeting-web-tool-using-agora/

推荐阅读
相关专栏
SDK 教程
164 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。