快速使用阿里云ARTC Web SDK

阿里云ARTC Web SDK是由阿里云提供的一套基于Web的实时通信开发工具。它允许开发者在Web应用中快速集成高质量的音视频通话功能、实时消息传递等实时互动功能。本文为您演示快速搭建属于自己的ARTC应用。

步骤一:开通应用

  1. 登录视频直播控制台

  2. 在左侧导航栏单击直播+ > 实时音视频 > 应用管理

  3. 单击创建应用

  4. 填写自定义的实例名称,勾选服务协议后,点击立即购买。

  5. 提示开通成功后,刷新应用管理页面,即可查看您新建的实时音视频应用。

    说明

    创建应用默认不产生费用 , 实际按照您具体云上用量后付费。更多信息,请参见实时音视频费用

步骤二:获取应用ID和AppKey

成功开通应用后,在应用管理列表中找到该应用,单击操作栏中的管理按钮,进入基本信息页面,在该页面获取对应的应用IDAppKey

image

步骤三:集成接入

  1. 集成SDK。

    Script集成

    在您的HTML页面引入SDK脚本。

    <script src="https://g.alicdn.com/apsara-media-box/imp-web-rtc/6.12.3/aliyun-rtc-sdk.js"></script>

    NPM集成

    在您的项目中使用npm安装SDK。

    npm install aliyun-rtc-sdk --save
  2. 初始化引擎。

    // 以下两种引入方式二选一
    // 当以 npm 包方式引入时执行
    import AliRtcEngine from 'aliyun-rtc-sdk';
    // 当以 Script 方式引入时执行
    const AliRtcEngine = window.AliRtcEngine;
    
    // 检测环境
    const checkResult = await AliRtcEngine.isSupported();
    if (!checkResult.support) {
      // 当前环境不支持使用,提示用户更换或升级浏览器
    }
    
    // 创建引擎实例,可以保存至全局变量中
    const aliRtcEngine = AliRtcEngine.getInstance();
    
  3. 创建好AliRtcEngine实例后,需要监听、处理相关事件。

    // 当前用户离开频道
    aliRtcEngine.on('bye', (code) => {
      // code 为原因码,具体含义请查看 API 文档
      console.log(`bye, code=${code}`);
      // 这里做您的处理业务,如退出通话页面等
    });
    
    // 监听远端用户上线
    aliRtcEngine.on('remoteUserOnLineNotify', (userId, elapsed) => {
      console.log(`用户 ${userId} 加入频道,耗时 ${elapsed} 秒`);
      // 这里处理您的业务逻辑,如展示这个用户的模块
    });
    
    // 监听远端用户下线
    aliRtcEngine.on('remoteUserOffLineNotify', (userId, reason) => {
      // reason 为原因码,具体含义请查看 API 文档
      console.log(`用户 ${userId} 离开频道,原因码: ${reason}`);
      // 这里处理您的业务逻辑,如销毁这个用户的模块
    });
    
    // 监听远端流订阅变化
    aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
      // oldState、newState 类型均为AliRtcSubscribeState,值包含 0(初始化)、1(未订阅)、2(订阅中)、3(已订阅)
      // interval 为两个状态之间的变化时间间隔,单位毫秒
      console.log(`频道 ${channelId} 远端用户 ${userId} 订阅状态由 ${oldState} 变为 ${newState}`);
      // 这里处理观看远端流的逻辑
      // 当 newState 变为 3 时可以通过 setRemoteViewConfig 播放远端流
      // 当 newState 变为 1 时可以停止播放
    });
    
    // 监听鉴权信息过期
    aliRtcEngine.on('authInfoExpired', () => {
      // 该回调触发代表鉴权信息已过期
      // 需要重新获取 token 等数据,调用 refreshAuthInfo 接口更新鉴权数据
      aliRtcEngine.refreshAuthInfo({
        userId,
        token,
        timestamp
      });
    });
    
    // 监听用户鉴权信息即将过期
    aliRtcEngine.on('authInfoWillExpire', () => {
      // 该回调在鉴权信息30秒前触发,收到该回调后应该及时更新鉴权信息
      // 若想要继续在会中,需要重新获取 token 等数据,调用 joinChannel 重新入会
    });
    
  4. (可选)设置频道模式,默认通话模式。详细信息,请参见设置频道模式和用户角色

    // 设置频道模式,支持传入字符串 communication(通话模式)、interactive_live(互动模式)
    aliRtcEngine.setChannelProfile('interactive_live');
    // 设置角色,互动模式时调用才生效
    // 支持传入字符串 interactive(互动角色,允许推拉流)、live(观众角色,仅允许拉流)
    aliRtcEngine.setClientRole('interactive');
  5. 加入频道。Token计算方式参见Token鉴权,您可以按需选择单参数入会或者多参数入会的方案。

    • 单参数入会

      const userName = '测试用户1'; // 可以修改为您的用户名(支持中文)
      
      try {
        // fetchToken需要您来实现,从服务端获取Base64Token
        const base64Token = await fetchToken();
        await aliRtcEngine.joinChannel(base64Token, userName);
        // 加入成功,继续执行其他操作
      } catch (error) {
        // 加入失败
      }
    • 多参数入会

      // 参考文档 Token 鉴权部分,从服务端或本地生成鉴权信息
      // 注意:为了您的数据安全,任何情况下都不应该将带有 appKey 的 token 计算逻辑发布给用户
      const appId = 'yourAppId'; // 从控制台获取
      const appKey = 'yourAppKey'; // 从控制台获取,注意请勿在生产环境露出您的 AppKey
      const channelId = 'AliRtcDemo'; // 可以修改为您的频道ID(仅支持英文字母、数字)
      const userId = 'test1'; // 可以修改为您的用户ID(仅支持英文字母、数字)
      const userName = '测试用户1'; // 可以修改为您的用户名(支持中文)
      const timestamp = Math.floor(Date.now() / 1000) + 3600; // 一个小时后过期
      
      try {
        const token = await generateToken(appId, appKey, channelId, userId, timestamp);
        // 加入频道,参数 token、nonce 等一般由服务端返回
        await aliRtcEngine.joinChannel({
          channelId,
          userId,
          appId,
          token,
          timestamp,
        }, userName);
        // 加入成功,继续执行其他操作
      } catch (error) {
        // 加入失败
      }
  6. 预览画面和推流。默认情况下,加入频道后将自动采集本地的音视频数据并推送至阿里云GRTN网络中。您可以参考以下方式预览您的本地画面。

    1. 在HTML代码中增加一个idlocalPreviewer的VIDEO元素。

      <video
        id="localPreviewer"
        muted
        style="display: block;width: 320px;height: 180px;background-color: black;"
      ></video>
    2. 通过setLocalViewConfig方法传入元素ID开启预览。

      // 第一个参数支持传入 HTMLVideoElement 或对应的元素 ID,传入 null 时停止预览
      // 第二个参数支持传入 1 (预览相机流)、2(预览屏幕共享流)
      aliRtcEngine.setLocalViewConfig('localPreviewer', 1);
  7. 订阅远端音视频流。默认情况下,加入频道后将自动订阅其他主播用户的音视频流,若有音频流将自动播放,需要观看相机流、屏幕流,则需要通过setRemoteViewConfig接口开启。

    1. 在HTML代码中增加一个idremoteVideoContainer的DIV元素作为容器。

      <div id="remoteVideoContainer"></div>
    2. 监听到远端视频流订阅变化后,若已订阅setRemoteViewConfig接口播放,若未订阅则移除。

      // 存储 Video 元素
      const remoteVideoElMap = {};
      // 远端容器元素
      const remoteVideoContainer = document.querySelector('#remoteVideoContainer');
      
      function removeRemoteVideo(userId) {
        const el = remoteVideoElMap[userId];
        if (el) {
          aliRtcEngine.setRemoteViewConfig(null, userId, 1);
          el.pause();
          remoteVideoContainer.removeChild(el);
          delete remoteVideoElMap[userId];
        }
      }
      
      // 同第二步监听事件示例代码中 videoSubscribeStateChanged 示例
      aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
        // oldState、newState 类型均为AliRtcSubscribeState,值包含 0(初始化)、1(未订阅)、2(订阅中)、3(已订阅)
        // interval 为两个状态之间的变化时间间隔,单位毫秒
        console.log(`频道 ${channelId} 远端用户 ${userId} 订阅状态由 ${oldState} 变为 ${newState}`);
        // 处理示例
        if (newState === 3) {
          const video = document.createElement('video');
          video.autoplay = true;
          video.setAttribute('style', 'display: block;width: 320px;height: 180px;background-color: black;');
          remoteVideoElMap[userId] = video;
          remoteVideoContainer.appendChild(video);
          // 第一个参数传入 HTMLVideoElement
          // 第二个参数传入远端用户 ID
          // 第三个参数支持传入 1 (预览相机流)、2(预览屏幕共享流)
          aliRtcEngine.setRemoteViewConfig(video, userId, 1);
        } else if (newState === 1) {
          removeRemoteVideo(userId);
        }
      });
  8. 结束流程。

    // 停止本地预览
    await aliRtcEngine.stopPreview();
    // 离开频道
    await aliRtcEngine.leaveChannel();
    // 销毁实例
    aliRtcEngine.destroy();

快速体验

前提条件

快速体验demo需要您的开发环境启动一个HTTP服务,如果您没有安装http-server的npm包,可以先执行npm install --global http-server指令全局安装。

步骤一:创建目录

创建一个demo文件夹,并按照下方文件目录结构创建好quick.htmlquick.js两个文件。

- demo
  - quick.html
  - quick.js

步骤二:编辑quick.html

将下方代码复制到quick.html,并保存。

示例代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>aliyun-rtc-sdk quick start</title>
    <link rel="stylesheet" href="https://g.alicdn.com/code/lib/bootstrap/5.3.0/css/bootstrap.min.css" />
    <style>
      .video {
        display: inline-block;
        width: 320px;
        height: 180px;
        margin-right: 8px;
        margin-bottom: 8px;
        background-color: black;
      }
    </style>
  </head>
  <body class="container p-2">
    <h1>aliyun-rtc-sdk 快速开始</h1>

    <div class="toast-container position-fixed top-0 end-0 p-3">
      <div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">登录消息</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="loginToastBody"></div>
      </div>

      <div id="onlineToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">用户上线</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="onlineToastBody"></div>
      </div>

      <div id="offlineToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">用户下线</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="offlineToastBody"></div>
      </div>
    </div>

    <div class="row mt-3">
      <div class="col-6">
        <form id="loginForm">
          <div class="form-group mb-2">
            <label for="channelId" class="form-label">频道号</label>
            <input class="form-control" id="channelId" />
          </div>
          <div class="form-group mb-2">
            <label for="userId" class="form-label">用户ID</label>
            <input class="form-control" id="userId" />
          </div>
          <button id="joinBtn" type="submit" class="btn btn-primary mb-2">加入频道</button>
          <button id="leaveBtn" type="button" class="btn btn-secondary mb-2" disabled>离开频道</button>
        </form>
    
        <div class="mt-3">
          <h4>本地预览</h4>
          <video
            id="localPreviewer"
            muted
            class="video"
          ></video>
        </div>
      </div>
      <div class="col-6">
        <h4>远端用户</h4>
        <div id="remoteVideoContainer"></div>
      </div>
    </div>

    <script src="https://g.alicdn.com/code/lib/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://g.alicdn.com/code/lib/bootstrap/5.3.0/js/bootstrap.min.js"></script>
    <script src="https://g.alicdn.com/apsara-media-box/imp-web-rtc/6.12.3/aliyun-rtc-sdk.js"></script>
    <script src="./quick.js"></script>
  </body>
</html>

步骤三:编辑quick.js

将下方代码复制到quick.js,并将应用IDAppKey粘贴至代码指定变量中保存。

示例代码

function hex(buffer) {
  const hexCodes = [];
  const view = new DataView(buffer);
  for (let i = 0; i < view.byteLength; i += 4) {
    const value = view.getUint32(i);
    const stringValue = value.toString(16);
    const padding = '00000000';
    const paddedValue = (padding + stringValue).slice(-padding.length);
    hexCodes.push(paddedValue);
  }
  return hexCodes.join('');
}
async function generateToken(appId, appKey, channelId, userId, timestamp) {
  const encoder = new TextEncoder();
  const data = encoder.encode(`${appId}${appKey}${channelId}${userId}${timestamp}`);

  const hash = await crypto.subtle.digest('SHA-256', data);
  return hex(hash);
}

function showToast(baseId, message) {
  $(`#${baseId}Body`).text(message);
  const toast = new bootstrap.Toast($(`#${baseId}`));

  toast.show();
}

// 填入您的应用ID 和 AppKey
const appId = '';
const appKey = '';
AliRtcEngine.setLogLevel(0);
let aliRtcEngine;
const remoteVideoElMap = {};
const remoteVideoContainer = document.querySelector('#remoteVideoContainer');

function removeRemoteVideo(userId, type = 'camera') {
  const vid = `${type}_${userId}`;
  const el = remoteVideoElMap[vid];
  if (el) {
    aliRtcEngine.setRemoteViewConfig(null, userId, type === 'camera' ? 1: 2);
    el.pause();
    remoteVideoContainer.removeChild(el);
    delete remoteVideoElMap[vid];
  }
}

function listenEvents() {
  if (!aliRtcEngine) {
    return;
  }
  // 监听远端用户上线
  aliRtcEngine.on('remoteUserOnLineNotify', (userId, elapsed) => {
    console.log(`用户 ${userId} 加入频道,耗时 ${elapsed} 秒`);
    // 这里处理您的业务逻辑,如展示这个用户的模块
    showToast('onlineToast', `用户 ${userId} 上线`);
  });

  // 监听远端用户下线
  aliRtcEngine.on('remoteUserOffLineNotify', (userId, reason) => {
    // reason 为原因码,具体含义请查看 API 文档
    console.log(`用户 ${userId} 离开频道,原因码: ${reason}`);
    // 这里处理您的业务逻辑,如销毁这个用户的模块
    showToast('offlineToast', `用户 ${userId} 下线`);
    removeRemoteVideo(userId, 'camera');
    removeRemoteVideo(userId, 'screen');
  });

  aliRtcEngine.on('bye', code => {
    // code 为原因码,具体含义请查看 API 文档
    console.log(`bye, code=${code}`);
    // 这里做您的处理业务,如退出通话页面等
    showToast('loginToast', `您已离开频道,原因码: ${code}`);
  });

  aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
    // oldState、newState 类型均为AliRtcSubscribeState,值包含 0(初始化)、1(未订阅)、2(订阅中)、3(已订阅)
    // interval 为两个状态之间的变化时间间隔,单位毫秒
    console.log(`频道 ${channelId} 远端用户 ${userId} 订阅状态由 ${oldState} 变为 ${newState}`);
    const vid = `camera_${userId}`;
    // 处理示例
    if (newState === 3) {
      const video = document.createElement('video');
      video.autoplay = true;
      video.className = 'video';
      remoteVideoElMap[vid] = video;
      remoteVideoContainer.appendChild(video);
      // 第一个参数传入 HTMLVideoElement
      // 第二个参数传入远端用户 ID
      // 第三个参数支持传入 1 (预览相机流)、2(预览屏幕共享流)
      aliRtcEngine.setRemoteViewConfig(video, userId, 1);
    } else if (newState === 1) {
      removeRemoteVideo(userId, 'camera');
    }
  });

  aliRtcEngine.on('screenShareSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
    // oldState、newState 类型均为AliRtcSubscribeState,值包含 0(初始化)、1(未订阅)、2(订阅中)、3(已订阅)
    // interval 为两个状态之间的变化时间间隔,单位毫秒
    console.log(`频道 ${channelId} 远端用户 ${userId} 屏幕流的订阅状态由 ${oldState} 变为 ${newState}`);
    const vid = `screen_${userId}`;
    // 处理示例    
    if (newState === 3) {
      const video = document.createElement('video');
      video.autoplay = true;
      video.className = 'video';
      remoteVideoElMap[vid] = video;
      remoteVideoContainer.appendChild(video);
      // 第一个参数传入 HTMLVideoElement
      // 第二个参数传入远端用户 ID
      // 第三个参数支持传入 1 (预览相机流)、2(预览屏幕共享流)
      aliRtcEngine.setRemoteViewConfig(video, userId, 2);
    } else if (newState === 1) {
      removeRemoteVideo(userId, 'screen');
    }
  });
}

$('#loginForm').submit(async e => {
  // 防止表单默认提交动作
  e.preventDefault();
  const channelId = $('#channelId').val();
  const userId = $('#userId').val();
  const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;

  if (!channelId || !userId) {
    showToast('loginToast', '数据不完整');
    return;
  }

  aliRtcEngine = AliRtcEngine.getInstance();
  listenEvents();

  try {
    const token = await generateToken(appId, appKey, channelId, userId, timestamp);
    // 设置频道模式,支持传入字符串 communication(通话模式)、interactive_live(互动模式)
    aliRtcEngine.setChannelProfile('communication');
    // 设置角色,互动模式时调用才生效
    // 支持传入字符串 interactive(互动角色,允许推拉流)、live(观众角色,仅允许拉流)
    // aliRtcEngine.setClientRole('interactive');
    // 加入频道,参数 token、nonce 等一般有服务端返回
    await aliRtcEngine.joinChannel(
      {
        channelId,
        userId,
        appId,
        token,
        timestamp,
      },
      userId
    );
    showToast('loginToast', '加入频道成功');
    $('#joinBtn').prop('disabled', true);
    $('#leaveBtn').prop('disabled', false);

    // 预览
    aliRtcEngine.setLocalViewConfig('localPreviewer', 1);
  } catch (error) {
    console.log('加入频道失败', error);
    showToast('loginToast', '加入频道失败');
  }
});

$('#leaveBtn').click(async () => {
  Object.keys(remoteVideoElMap).forEach(vid => {
    const arr = vid.split('_');
    removeRemoteVideo(arr[1], arr[0]);
  });
  // 停止本地预览
  await aliRtcEngine.stopPreview();
  // 离开频道
  await aliRtcEngine.leaveChannel();
  // 销毁实例
  aliRtcEngine.destroy();
  aliRtcEngine = undefined;
  $('#joinBtn').prop('disabled', false);
  $('#leaveBtn').prop('disabled', true);
  showToast('loginToast', '已离开频道');
});

步骤四:运行体验

  1. 在终端中进入demo文件夹,然后执行http-server -p 8080,启动一个HTTP服务。

  2. 浏览器中新建标签页,访问localhost:8080/quick.html,在界面上填入频道ID用户ID ,单击加入频道按钮。

  3. 浏览器中再新建一个标签页,访问localhost:8080/quick.html,在界面上填入与上一步相同的频道ID和另一个用户ID ,单击加入频道按钮。

  4. 界面上将自动订阅另一个用户的媒体流。