V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
cookiezmq
V2EX  ›  分享发现

基于声网 Web SDK 实现一对一视频通话

  •  
  •   cookiezmq · 2022-10-09 18:41:27 +08:00 · 1238 次点击
    这是一个创建于 783 天前的主题,其中的信息可能已经有所发展或是发生改变。

    视频互动直播是当前比较热门的玩法,我们经常见到有 PK 连麦、直播答题、一起 KTV 、电商直播、互动大班课、视频相亲等。本文将演示如何通过声网 视频 SDK 在 Web 端实现一个视频直播应用。话不多说,我们开始动手实操。

    前提准备

    在声网开发者控制台 Console https://console.agora.io 注册声网开发者账号后,需要获取项目 AppID 。另外,开发者每个月可获得 10000 分钟的免费使用额度,可实现各类实时音视频场景。

    先来体验下 Demo

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

    动手实践

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

    开发环境

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

    • 浏览器:Chrome 、Firefox 、Safari 及 Edge
    • 开发环境
      • MacBook Pro (13-inch, M1, 2020)
      • Visual Studio Code (1.67.1)
      • AgoraWebSDK (4.12.2)
    • 测试环境
      • Chrome (101.0.4951.64)

    手动集成设置

    文件组织结构

    实现视频通话之前,参考如下步骤设置你的项目: 如需创建新项目,可以在 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 方式集成 Agora SDK 。

    在 index.html 中添加以下代码

    
    <!DOCTYPE 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>
    

    视频通话逻辑

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

    1 )初始化 Client
    var client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
    
    2 )加入 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()
    ]);
    
    3 )播放本地视频
    // Play the local video track to the local browser and update the UI with the user ID.
    localTracks.videoTrack.play("local-player");
    
    4 )发布本地音视频到频道中
    // Publish the local video and audio tracks to the channel.
    await client.publish(Object.values(localTracks));
    
    5 )监听远端用户音视频
    // 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 = $(`
          <div id="player-wrapper-${uid}">
            <p class="player-name">remoteUser(${uid})</p>
            <div id="player-${uid}" class="player"></div>
          </div>
        `);
        $("#remote-playerlist").append(player);
        user.videoTrack.play(`player-${uid}`);
      }
     
      if (mediaType === 'audio') {
        user.audioTrack.play();
      }
    }
    
    6 )离开频道
    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 = {
      videoTrack: null,
      audioTrack: null
    };
    var remoteUsers = {};
    // Agora client options
    var options = {
      appid: null,
      channel: null,
      uid: null,
      token: null
    };
     
    // 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 = $(`
          <div id="player-wrapper-${uid}">
            <p class="player-name">remoteUser(${uid})</p>
            <div id="player-${uid}" class="player"></div>
          </div>
        `);
        $("#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 运行网页,使用两个用户加入同一个频道,如果能看见两个自己,说明你成功了。 在这里插入图片描述

    完整代码工程下载

    https://download.agora.io/sdk/release/Agora_Web_SDK_v4_14_0_FULL.zip


    以上就是基于声网 Web SDK 实现一对一视频通话的全部内容。有问题欢迎到「 RTE 开发者社区」https://www.agora.io/cn/community/ 交流吐槽。

    2 条回复    2022-10-17 17:13:56 +08:00
    fuxinya
        1
    fuxinya  
       2022-10-09 23:17:37 +08:00 via Android
    请移动至推广节点
    cookiezmq
        2
    cookiezmq  
    OP
       2022-10-17 17:13:56 +08:00
    收到。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   968 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 20:17 · PVG 04:17 · LAX 12:17 · JFK 15:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.