大数跨境
0
0

基于 Web SDK 实现视频通话场景 | 声网 SDK 教程

基于 Web SDK  实现视频通话场景 | 声网 SDK 教程 RTE开发者社区
2022-07-19
1
导读:本文为「声网 SDK 教程」系列内容

声网视频 SDK 被广泛应用于多种实时互动场景中,例如视频会议、视频通话、音视频社交、在线教育等。为了让刚刚接触声网 SDK 的开发者,可以更顺畅地实现基础的视频通话功能,我们基于声网 Web SDK 4.x 版本梳理了本篇教程。

在本文末,会提供相应 Demo 、文档地址供大家参考使用。同时,欢迎点击「阅读原文」注册声网账号体验。声网每个月会为开发者提供 10000 分钟的免费额度。


本文为「声网 SDK 教程」系列内容

 目录
 上下滚动查看

 1. Demo 体验

 2. 动手实践
    2.1 实践任务
    2.2 开发环境
    2.3 项目设置
       2.3.1 文件组织架构
       2.3.2 集成声网SDK
       2.3.3 最终完整代码为
    2.4 视频通话逻辑
       2.4.1 实现视频通话逻辑
       2.4.2 初始化client
       2.4.3 加入RTC频道并创建本地音频轨道
       2.4.4 播放本地视频
       2.4.5 发布本地视频到频道中
       2.4.6 监听远端用户音视频
       2.4.7 离开频道
       2.4.8 最终完整的代码
   2.5运行效果
3.完整代码下载



我们在 GitHub 上提供一个开源的基础视频通话示例项目,在开始开发之前你可以通过该示例项目体验音视频通话效果。Demo 与线上体验地址,可在文末获取。

 


 



从 Web 前端页面引入声网 SDK,发起视频通话。


 

声网 SDK 的兼容性良好,对硬件设备和软件系统的要求不高,开发环境和测试环境满足以下条件即可:

 

  • Chrome
  • Firefox
  • Safari
  • Edge
 
以下是本文的开发环境和测试环境:
 
开发环境
  • MacBook Pro (13-inch, M1, 2020)
  • Visual Studio Code (1.67.1)
 
测试环境
  • Chrome (101.0.4951.64)
 
如果你此前还未接触过声网 SDK,那么你还需要做以下准备工作:
  • 注册一个声网账号,进入后台创建 AppID、获取 Token;
  • 下载声网官方最新的 视频 SDK。
 


文件组织结构

实现视频通话之前,参考如下步骤设置你的项目:
 
如需创建新项目,可以在 Visual Studio Code 里 File > New Window,创建 Web 项目。完整的目录结构如下,根据个人经验会有所变化。

          
.
├── index.css # 用于设计 Web 应用的用户界面样式
├── index.html # 用于设计 Web 应用的用户界面
├── index.js # 通过 AgoraRTCClient 实现具体应用逻辑的代码。
└── vendor # 第三方前端插件,辅助页面布局和交互,本教程中是下载到本地使用,你也可以使用 CDN 的方式
    ├── bootstrap.bundle.min.js
    ├── bootstrap.min.css
    └── jquery-3.4.1.min.js


集成声网 SDK


可以下载到本地使用,也可以直接使用声网的 CDN 引入, 本文推荐使用 CDN 方式集成声网 SDK。

 

在 index.html 中添加以下代码


...
  <link rel="stylesheet" href="./vendor/bootstrap.min.css">
  <link rel="stylesheet" href="./index.css">
...
  <script src="./vendor/jquery-3.4.1.min.js">script>
  <script src="./vendor/bootstrap.bundle.min.js">script>
  <script src="https://download.agora.io/sdk/release/AgoraRTC_N.js">script>
  <script src="./index.js">script>
...


最终完整代码为


可以直接复制运行。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Basic Video Call -- Agora</title>
  <link rel="stylesheet" href="./vendor/bootstrap.min.css">
  <link rel="stylesheet" href="./index.css">
</head>
<body>
  <div class="container-fluid banner">
    <p class="banner-text">Basic Video Call</p>
    <a style="color: rgb(255, 255, 255);fill: rgb(255, 255, 255);fill-rule: evenodd; position: absolute; right: 10px; top: 4px;"
      class="Header-link " href="https://github.com/AgoraIO-Community/AgoraWebSDK-NG/tree/master/Demo">

      <svg class="octicon octicon-mark-github v-align-middle" height="32" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg>
    </a>
  </div>


  <div id="success-alert" class="alert alert-success alert-dismissible fade show" role="alert">
    <strong>Congratulations!</strong><span> You can invite others join this channel by click </span><a href="" target="_blank">here</a>
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
  <div id="success-alert-with-token" class="alert alert-success alert-dismissible fade show" role="alert">
    <strong>Congratulations!</strong><span> Joined room successfully. </span>
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
  <div id="success-alert-with-token" class="alert alert-success alert-dismissible fade show" role="alert">
    <strong>Congratulations!</strong><span> Joined room successfully. </span>
    <button type="button" class="close" data-dismiss="alert" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>

  <div class="container">
    <form id="join-form">
      <div class="row join-info-group">
          <div class="col-sm">
            <p class="join-info-text">AppID</p>
            <input id="appid" type="text" placeholder="enter appid" required>
            <p class="tips">If you don`t know what is your appid, checkout <a href="https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#a-nameappidaapp-id">this</a></p>
          </div>
          <div class="col-sm">
            <p class="join-info-text">Token(optional)</p>
            <input id="token" type="text" placeholder="enter token">
            <p class="tips">If you don`t know what is your token, checkout <a href="https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#a-namekeyadynamic-key">this</a></p>
          </div>
          <div class="col-sm">
            <p class="join-info-text">Channel</p>
            <input id="channel" type="text" placeholder="enter channel name" required>
            <p class="tips">If you don`t know what is your channel, checkout <a href="https://docs.agora.io/en/Agora%20Platform/terms?platform=All%20Platforms#channel">this</a></p>
          </div>
      </div>

      <div class="button-group">
        <button id="join" type="submit" class="btn btn-primary btn-sm">Join</button>
        <button id="leave" type="button" class="btn btn-primary btn-sm" disabled>Leave</button>
      </div>
    </form>

    <div class="row video-group">
      <div class="col">
        <p id="local-player-name" class="player-name"></p>
        <div id="local-player" class="player"></div>
      </div>
      <div class="w-100"></div>
      <div class="col">
        <div id="remote-playerlist"></div>
      </div>
    </div>
  </div>

  <script src="./vendor/jquery-3.4.1.min.js"></script>
  <script src="./vendor/bootstrap.bundle.min.js"></script>
  <script src="https://download.agora.io/sdk/release/AgoraRTC_N.js"></script>
  <script src="./index.js"></script>
</body>
</html>




实现视频通话逻辑

 

下图展示视频通话的 API 调用时序,注意图中的方法是对不同的对象调用的。




参考以下步骤实现音视频通话的逻辑:

  1. 调用 createClient 方法创建 AgoraRTCClient 对象。
  2. 调用 join 方法加入一个 RTC 频道,你需要在该方法中传入 App ID 、用户 ID、Token、频道名称。
  3. 先调用 createMicrophoneAudioTrack 通过麦克风采集的音频创建本地音频轨道对象,调用 createCameraVideoTrack 通过摄像头采集的视频创建本地视频轨道对象;然后调用 publish 方法,将这些本地音视频轨道对象当作参数即可将音视频发布到频道中。
  4. 当一个远端用户加入频道并发布音视频轨道时:
    1. 监听 client.on("user-published") 事件。当 SDK 触发该事件时,在这个事件回调函数的参数中你可以获取远端用户 AgoraRTCRemoteUser 对象 。
    2. 调用 subscribe 方法订阅远端用户 AgoraRTCRemoteUser 对象,获取远端用户的远端音频轨道 RemoteAudioTrack 和远端视频轨道 RemoteVideoTrack 对象。
    3. 调用 play 方法播放远端音视频轨道。

 

 

注:以下代码都将在 index.js 中添加



初始化client

var client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });


加入RTC 频道并创建本地音频轨道


// Join a channel and create local tracks. Best practice is to use Promise.all and run them concurrently.
[ options.uid, localTracks.audioTrack, localTracks.videoTrack ] = await Promise.all([
  // Join the channel.
  client.join(options.appid, options.channel, options.token || null, options.uid || null),
  // Create tracks to the local microphone and camera.
  AgoraRTC.createMicrophoneAudioTrack(),
  AgoraRTC.createCameraVideoTrack()
]);



播放本地视频


// Play the local video track to the local browser and update the UI with the user ID.
localTracks.videoTrack.play("local-player");


发布本地音视频到频道中


// Publish the local video and audio tracks to the channel.
await client.publish(Object.values(localTracks));


监听远端用户音视频


// Add an event listener to play remote tracks when remote user publishes.
client.on("user-published", handleUserPublished);
client.on("user-unpublished", handleUserUnpublished);

function handleUserPublished(user, mediaType{
  const id = user.uid;
  remoteUsers[id] = user;
  subscribe(user, mediaType);
}

function handleUserUnpublished(user, mediaType{
  if (mediaType === 'video') {
    const id = user.uid;
    delete remoteUsers[id];
    $(`#player-wrapper-${id}`).remove();
  }
}


async function subscribe(user, mediaType{
  const uid = user.uid;
  // subscribe to a remote user
  await client.subscribe(user, mediaType);
  console.log("subscribe success");
  if (mediaType === 'video') {
    const player = $(`
      


        

remoteUser(${uid})


        
      


    `
);
    $("#remote-playerlist").append(player);
    user.videoTrack.play(`player-${uid}`);
  }

  if (mediaType === 'audio') {
    user.audioTrack.play();
  }
}



离开频道


async function leave({
  for (trackName in localTracks) {
    var track = localTracks[trackName];
    if(track) {
      track.stop();
      track.close();
      localTracks[trackName] = undefined;
    }
  }

  // Remove remote users and player views.
  remoteUsers = {};
  $("#remote-playerlist").html("");

  // leave the channel
  await client.leave();

}



最终完整的代码


// create Agora client
var client = AgoraRTC.createClient({ mode"rtc"codec"vp8" });


var localTracks = {
  videoTracknull,
  audioTracknull
};
var remoteUsers = {};
// Agora client options
var options = {
  appidnull,
  channelnull,
  uidnull,
  tokennull
};

// the demo can auto join channel with params in url
$(() => {
  var urlParams = new URL(location.href).searchParams;
  options.appid = urlParams.get("appid");
  options.channel = urlParams.get("channel");
  options.token = urlParams.get("token");
  if (options.appid && options.channel) {
    $("#appid").val(options.appid);
    $("#token").val(options.token);
    $("#channel").val(options.channel);
    $("#join-form").submit();
  }
})

$("#join-form").submit(async function (e{
  e.preventDefault();
  $("#join").attr("disabled"true);
  try {
    options.appid = $("#appid").val();
    options.token = $("#token").val();
    options.channel = $("#channel").val();
    await join();
    if(options.token) {
      $("#success-alert-with-token").css("display""block");
    } else {
      $("#success-alert a").attr("href"`index.html?appid=${options.appid}&channel=${options.channel}&token=${options.token}`);
      $("#success-alert").css("display""block");
    }
  } catch (error) {
    console.error(error);
  } finally {
    $("#leave").attr("disabled"false);
  }
})

$("#leave").click(function (e{
  leave();
})

async function join({

  // add event listener to play remote tracks when remote user publishs.
  client.on("user-published", handleUserPublished);
  client.on("user-unpublished", handleUserUnpublished);

  // join a channel and create local tracks, we can use Promise.all to run them concurrently
  [ options.uid, localTracks.audioTrack, localTracks.videoTrack ] = await Promise.all([
    // join the channel
    client.join(options.appid, options.channel, options.token || null),
    // create local tracks, using microphone and camera
    AgoraRTC.createMicrophoneAudioTrack(),
    AgoraRTC.createCameraVideoTrack()
  ]);

  // play local video track
  localTracks.videoTrack.play("local-player");
  $("#local-player-name").text(`localVideo(${options.uid})`);

  // publish local tracks to channel
  await client.publish(Object.values(localTracks));
  console.log("publish success");
}

async function leave({
  for (trackName in localTracks) {
    var track = localTracks[trackName];
    if(track) {
      track.stop();
      track.close();
      localTracks[trackName] = undefined;
    }
  }

  // remove remote users and player views
  remoteUsers = {};
  $("#remote-playerlist").html("");

  // leave the channel
  await client.leave();

  $("#local-player-name").text("");
  $("#join").attr("disabled"false);
  $("#leave").attr("disabled"true);
  console.log("client leaves channel success");
}

async function subscribe(user, mediaType{
  const uid = user.uid;
  // subscribe to a remote user
  await client.subscribe(user, mediaType);
  console.log("subscribe success");
  if (mediaType === 'video') {
    const player = $(`
      


        

remoteUser(${uid})


        
      


    `
);
    $("#remote-playerlist").append(player);
    user.videoTrack.play(`player-${uid}`);
  }
  if (mediaType === 'audio') {
    user.audioTrack.play();
  }
}

function handleUserPublished(user, mediaType{
  const id = user.uid;
  remoteUsers[id] = user;
  subscribe(user, mediaType);
}

function handleUserUnpublished(user{
  const id = user.uid;
  delete remoteUsers[id];
  $(`#player-wrapper-${id}`).remove();
}



在浏览器开两个tab运行网页,使用两个用户加入同一个频道,如果能看见两个自己,说明你成功了。



 


访问声网文档中心,根据下图所示路径,下载对应 SDK 压缩包。压缩包中包含完整代码。




 

(正文完)



READING
参考链接

1.Github 源码

https://github.com/AgoraIO/API-Examples-Web/tree/main/Demo/basicVideoCall


2.线上体验Demo

https://webdemo.agora.io/basicVideoCall/index.html


3.声网文档中心

https://docs.agora.io/cn/Video/downloads?platform=Web





关注「声网开发者」

关注实时互动领域的

技术实践行业洞察人物观点


【声明】内容源于网络
0
0
RTE开发者社区
RTE 开发者社区是聚焦实时互动领域的中立开发者社区。不止于纯粹的技术交流,我们相信开发者具备更加丰盈的个体价值。行业发展变革、开发者职涯发展、技术创业创新资源,我们将陪跑开发者,共享、共建、共成长。
内容 1122
粉丝 0
RTE开发者社区 RTE 开发者社区是聚焦实时互动领域的中立开发者社区。不止于纯粹的技术交流,我们相信开发者具备更加丰盈的个体价值。行业发展变革、开发者职涯发展、技术创业创新资源,我们将陪跑开发者,共享、共建、共成长。
总阅读653
粉丝0
内容1.1k