腾讯云TRTC服务实现Web视频会议

news2025/1/9 17:12:38

腾讯云TRTC服务实现Web视频会议

背景

近期公司承接了某高校智慧校园的项目建设工作,其中在家校协作的板块中需要进行视频教学,以及线上屏幕共享,为了完成这一需求,我在自研与第三方服务的选择之间选择了第三方,主要因为目前团队成员在音视频方向的深度不足以支撑完成整个高可用模块,而且现在受疫情影响,对于高校来说,直播教学板块尤为重要。

常用的第三方音视频服务:

  • 又拍云
  • 七牛云
  • 阿里云
  • 腾讯云

在几番选择后,我采用了腾讯云作为我们音视频板块的服务支撑组件,主要是因为我们很多服务都与腾讯云有对接,在一起方便于管理,而且操作起来比较方便,并且也是业内知名大厂。

腾讯云TRTC服务的入门

TRTC 是腾讯云主打全平台互通的多人音视频通话和低延时互动直播解决方案。TRTC服务有多种客户端的支持,对于IOS、Android、React native等都支持的比较好,我们主要在于 IOS、Android、Web三端进行处理,其中 TRTC Web SDK 基于 WebRTC 实现,基于腾讯云成熟的音视频积累,保证了视频流的低延时 高效传输等。

创建TRTC服务

首先,在腾讯云搜索 TRTC,https://cloud.tencent.com/search/%E5%AE%9E%E6%97%B6%E9%9F%B3%E8%A7%86%E9%A2%91/1_1
然后点击立即使用:

搜索TRTC

然后到登录账户到达 TRTC 控制台,我首先需要建立一个 Demo 进行接入测试,这里我们就建立一个 aopppTRTC_Demo

trtc-demo
实际上大家第一次使用他会有引导的,大家可以放心,这里我们要查看一下这个应用的配置,主要就是为了获得 SDKAppID 和 密钥

在这里插入图片描述
获取密钥和sdkid

这样,我们就在腾讯云上建立好了 TRTC 应用了,接下来我们根据开始实现我们的直播教学业务。

业务场景描述

我们先简单利用TRTC实现一下老师在教师端进行直播教学,这个需要能够共享老师屏幕,摄像头以及麦克风,老师登录教师端后在今天的课表上选择开始上课,然后启用屏幕、摄像头、麦克风共享即可

搭建项目

我们这里还是采用 Vue + Vite + Element-plus 进行 Web 页面设计,我最终的 demo 地址为 https://gitee.com/TestsLing/aoppp-trtc_-demo.git ,欢迎大家一起讨论交流

首先就是老师登录教师端后,去创建课程的直播间,这里我们需要构建一个开启课程直播的页面

开启课程教学

我们只需要输入课程名称,然后点击开启教学直播,就会把课程名称发送给服务端,然后服务端帮我们初始化好房间,并返回对应的房间ID,相关代码如下:

<script lang="ts" setup>
import {onMounted, ref, reactive} from "vue";
import {useRoute, useRouter} from "vue-router";
import {FormInstance} from "element-plus";

const rules = reactive({
  lesson: [{required: true, message: "请输入课程名称", trigger: "blur"}],
});
const form = reactive({
  lesson: "",
});
const router = useRouter();
const ruleFormRef = ref<FormInstance>();

async function handleCreateRoom() {
  await ruleFormRef.value?.validate((valid, fields) => {
    fetch('http://localhost:3888/creatRoom', {
      method: 'post',
      headers: {
        'Accept': 'application/json,text/plain,*/*',/* 格式限制:json、文本、其他格式 */
        'Content-Type': 'application/json'/* 请求内容类型 */
      },
      body: JSON.stringify({
        lesson: form.lesson
      })
    }).then((response) => {
      return response.json()
    }).then((data) => {
      let {roomId, code} = data
      if (code == 200 && valid) {
        router.push({path: "page", query: {...form, roomId}})
      }
    }).catch(function (error) {
    
	  // 可以进行异常上报
      console.log(error)
    })
  });
}

开启视频教学之后,我们就需要一个直播间,这个时候我们就需要使用到集成的 trtc-js-sdk,大致思路是这样,我们先通过trtc创建一个客户端,然后我们要对视频流进行处理

  1. 我们要让别人看到我们的视频内容,首先我们要先进行推流,把视频流推到公网上去
  2. 其他客户端要看到我们的视频内容,只需要加入直播间,然后通过拉流操作即可

大致页面和代码如下:

页面布局
我们先通过共享屏幕进行流测试

屏幕分享

然后流就会回显到我们的主内容区域

屏幕共享

在代码中的实现:

要注意一下这个 userSig,签名的作用就是为了防篡改,需要确定你是使用方身份,我们需要使用 secretKey、sdkAppId、userId 来创造这个签名,secretKey是私密信息,不能放到客户端进行硬编码,应该由服务端处理完后将签名返回给客户端,但是官方也提供了前端加密的方法, 供我们调试使用,所以我们现在直接使用官方提供的demo中的加密方法来获取 UserSig。

  • generateTestUserSig

/* eslint-disable */

/*
 * Module:   GenerateTestUserSig
 *
 * Function: 用于生成测试用的 UserSig,UserSig 是腾讯云为其云服务设计的一种安全保护签名。
 *           其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
 *
 * Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
 *
 *            本文件中的代码虽然能够正确计算出 UserSig,但仅适合快速调通 SDK 的基本功能,不适合线上产品,
 *            这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
 *            一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
 *
 *            正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
 *            由于破解服务器的成本要高于破解客户端 App,所以服务器计算的方案能够更好地保护您的加密密钥。
 *
 * Reference:https://cloud.tencent.com/document/product/647/17275#Server
 */
interface Login {
  sdkAppId: number;
  userId: string;
  secretKey: string;
}

interface UserSig {
  userSig: string;
  sdkAppId: number;
}

export function genTestUserSig({
  sdkAppId,
  userId,
  secretKey,
}: Login): UserSig {
  /**
   * 腾讯云 SDKAppId,需要替换为您自己账号下的 SDKAppId。
   *
   * 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId,
   * 它是腾讯云用于区分客户的唯一标识。
   */
  const SDKAPPID = sdkAppId;

  /**
   * 签名过期时间,建议不要设置的过短
   * <p>
   * 时间单位:秒
   * 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
   */
  const EXPIRETIME = 604800;

  /**
   * 计算签名用的加密密钥,获取步骤如下:
   *
   * step1. 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ),如果还没有应用就创建一个,
   * step2. 单击“应用配置”进入基础配置页面,并进一步找到“帐号体系集成”部分。
   * step3. 点击“查看密钥”按钮,就可以看到计算 UserSig 使用的加密的密钥了,请将其拷贝并复制到如下的变量中
   *
   * 注意:该方案仅适用于调试Demo,正式上线前请将 UserSig 计算代码和密钥迁移到您的后台服务器上,以避免加密密钥泄露导致的流量盗用。
   * 文档:https://cloud.tencent.com/document/product/647/17275#Server
   */
  const SECRETKEY = secretKey;

  // a soft reminder to guide developer to configure sdkAppId/secretKey
  if (SDKAPPID == undefined || SECRETKEY === "") {
    alert(
      "请先配置好您的账号信息: SDKAPPID 及 SECRETKEY " +
        "\r\n\r\nPlease configure your SDKAPPID/SECRETKEY in js/debug/GenerateTestUserSig.js"
    );
  }
  const generator = new (<any>window).LibGenerateTestUserSig(
    SDKAPPID,
    SECRETKEY,
    EXPIRETIME
  );
  const userSig = generator.genTestUserSig(userId);
  return {
    sdkAppId: SDKAPPID,
    userSig: userSig,
  };
}

userSig
官方的详细的介绍: https://cloud.tencent.com/document/product/647/17275


const router = useRouter();
const route = useRoute();
const appConf = reactive({
  userId: "",
  roomId: 0,
  sdkAppId: 0,
  userSig: "",
});

// 一般是服务端返回的,这里为了测试方便,先写死
const getUserId = () => "123"

// 在生命周期中进行初始化一下
onMounted(async () => {
  appConf.userId = getUserId();
  appConf.roomId = parseInt(route.query.roomId as string, 10);

  const { sdkAppId, userSig } = await getSign(appConf.userId);
  appConf.sdkAppId = sdkAppId;
  appConf.userSig = userSig;
  handlerJoin();
});
// 加入房间
const handlerJoin = async () => {
  const { userId, userSig, roomId, sdkAppId } = appConf;
  console.log({ userId, userSig, roomId, sdkAppId });

  try {
    // 创建客户端
    localClient = TRTC.createClient({
      mode: "rtc", // 采用rtc方式
      sdkAppId,
      userId,
      userSig,
    });
    //
    await localClient.join({ roomId });
  } catch (e: any) {
    console.log("加入房间失败", e);
  }
  
  // 对相关视频事件进行监听
  installEventHandlers();
  
  // 创建流
  await createStream();
  
  // 推送流
  await publishHandler();
};

const installEventHandlers = () => {
  if (!localClient) return;
  localClient.on("error", nothingHandle);
  localClient.on("client-banned", nothingHandle);
  localClient.on("peer-join", nothingHandle);
  localClient.on("peer-leave", nothingHandle);
  localClient.on("stream-added", handleStreamAdded);
  localClient.on("stream-subscribed", handleStreamSubscribed);
  localClient.on("stream-removed", handleStreamRemoved);
  localClient.on("stream-updated", nothingHandle);
  localClient.on("mute-video", nothingHandle);
  localClient.on("mute-audio", nothingHandle);
  localClient.on("unmute-video", nothingHandle);
  localClient.on("unmute-audio", nothingHandle);
};


/**
 * 流处理
 */
const createStream = async () => {
  try {
    if (streamMode.value === "screenStream") localStream = await creatScreenStream();
    if (streamMode.value === "cameraStream") localStream = await createCameraStream();
    // 主屏幕显示流
    await localStream.play("stream_main");
  } catch (e: any) {
    throw new Error(e);
  }
};
// 摄像头流
const createCameraStream = async () => {
  const { userId, userSig, roomId, sdkAppId } = appConf;
  let localStream;
  try {
    // const cameraItems = await TRTC.getCameras();
    // const microphoneItems = await TRTC.getMicrophones();
    // console.log(cameraItems, "cameraItems");

    localStream = TRTC.createStream({
      userId: userId,
      audio: true,
      video: true,
      // cameraId: cameraItems[0].deviceId,
      // microphoneId: microphoneItems[0].deviceId,
    });
    await localStream.initialize();
  } catch (e: any) {
    throw new Error(e);
  }
  return localStream;
};
// 屏幕流
const creatScreenStream = async () => {
  const { userId, userSig, roomId, sdkAppId } = appConf;
  let localStream;
  try {
    localStream = TRTC.createStream({
      audio: false,
      screen: true,
      userId,
    });
    await localStream.initialize();
  } catch (e: any) {
    throw new Error(e);
  }
  return localStream;
};
// 推流
const publishHandler = async () => {
  if (!localClient || !localStream) return;
  try {
    await localClient.publish(localStream);
    console.log("推流成功");
  } catch (e: any) {
    console.log("推流失败", e);
  }
};
// 取消推流
const unpublishHandler = async () => {
  if (!localClient || !localStream) return;
  try {
    await localClient.unpublish(localStream);
  } catch (e: any) {
    throw new Error(e);
  }
};

// ....更多请看 gitee demo

相关问题

如果推流失败,需要先检查当前环境,文档也有说明: https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/tutorial-23-advanced-support-detection.htm

相关链接

  • https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/TRTC.html#createClient trtc-api文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/25334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【交通标志识别】基于BP神经网络实现交通标志识别系统(含语音报警)附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

java在Windows配置Path环境变量

java在Windows配置Path环境变量 在命令行窗口输入java时&#xff0c;命令行提示’java’ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 第一步&#xff1a;鼠标右键点击我的电脑&#xff0c;点击“属性” 第二步&#xff1a;点击“高级系统设置” 第三步&…

ProNas-振动噪声工程界新一代的前沿技术

作者&#xff1a;张工 随着汽车、船舶、高铁动车、轨道车辆、商用飞机、起重机械等交通运输工具和风电机组、家用电器、航天器等振动噪声。仿真模型的日益精细和庞大&#xff0c;现代噪声、振动及舒适性&#xff08;NVH&#xff09;仿真计算及验证领域面临着计算效益&#xf…

FL Studio21水果体验尝鲜版音乐宿主程序FL2023

FL Studio21从大家看来的音乐玩具&#xff0c;逐渐发展成相当严肃的DAW。如今&#xff0c;正被全球大量用户使用。它总是有着自己的方式&#xff0c;在工作流程和设计上&#xff0c;都不与竞争者相仿。 FL Studio是一款功能强大的虚拟音乐制作软件,通过它创作wav,MP3,MIDI,loo…

iOS多项选项卡TYTabPagerBar和分页控制器TYPagerController使用小结

最近做项目的时候&#xff0c;用到了顶部选项卡和底部分页控制器相关的功能。之前做的话都是自己手动封装&#xff0c;通过两个UIScrollView联动来实现。公司同事给推荐了一个封装好的库&#xff0c; TYPagerController&#xff0c;内部也是通过ScrollView的联动来实现的&…

微信小程序 java ssm电影迷爱好者交流平台

电影交流平台是基于java编程语言&#xff0c;mysql数据库&#xff0c;idea开发工具开发的后台&#xff0c;前端是微信小程序开发工具开发。本设计分为用户和管理员两个角色&#xff0c;其中用户可以登陆微信端&#xff0c;查看电影信息&#xff0c;查看电影分类&#xff0c;对电…

Inobitec DICOM Viewer Pro 2.8.0 Crack

Inobitec DICOM Viewer Pro 2.8.0扩大医生可见和可能的极限。通过为医疗行业提供优质的创新 IT 解决方案&#xff0c;我们可以为改善全球人民的健康做出很多贡献。 动态渐进版&#xff08;每季度发布&#xff09;扩展功能 高级 3D 重建 以 OBJ、STL、PLY 格式导出表面 先进的…

SpringBoot2.x 自动化生成代码整合Mybatis

前言 Mybatis 是一个持久层ORM框架&#xff0c;负责Java与数据库数据交互&#xff0c;也可以简易理解为中介&#xff0c;相对于它&#xff0c;还有个中介是hibernate,不过在mybatis中sql语句的灵活性&#xff0c;可优化性比较强&#xff0c;这也是现在大多数人选择的原因。 1. …

【快速学习系列】Mybatis缓存和使用SpringBoot开启MyBatis缓存+ehcache

【快速学习系列】Mybatis缓存和使用SpringBoot开启MyBatis缓存ehcache Mybatis缓存 MyBatis一级缓存 Mybatis对缓存提供支持&#xff0c;但是在没有配置的默认情况下&#xff0c;它只开启一级缓存&#xff0c;一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一…

全员全域安全守护,蔚来ET7获Euro NCAP五星安全评级背后的硬核实力

2022年11月16日&#xff0c;蔚来智能电动旗舰轿车ET7 获得Euro NCAP&#xff08;欧盟新车安全评鉴协会&#xff09;五星安全评级&#xff0c;成为继ES8之后蔚来又一款获得欧洲五星安全评级的车型。ET7分别在乘客安全、儿童安全、弱势道路使用者安全、辅助安全得分91%、87%、73%…

两个事务并发写,能保证数据唯一吗?

哟&#xff0c;又是我小白。最近有点高产了。 连我自己都害怕了。 直接进入正题吧。 两个事务并发写&#xff0c;能保证数据唯一吗&#xff1f; 我先来解释下标题讲的是个啥。 我们假设有这么一个用户注册的场景。用户并发请求注册新用户。 你有一张数据库表&#xff0c;也就…

【数据可视化】第二章——基于matplotlib的数据可视化

目录系列课程学习目标1. 基于matplotlib的数据可视化2 matplotlib.pyplot函数库简介3 matplotlib.pyplot相关函数简介3.1 plt.plot(x, y, format_string, **kwargs)3.2 plt.title()3.3 plt.xlabel()/ylabel()3.4 plt.legend()3.5 pyplot的中文显示3.5.1 第一种方法3.5.2 第二种…

C#上位机系列(2)—常用控件的介绍:文本标签,按键,输入框

本文是讲解C#.net平台的Winform框架下的第二个内容&#xff0c;手把手介绍项目的创建方式以及一些写软件时常用的功能&#xff0c;讲解从零开始的每一个步骤。 本次介绍三个最常用控件的使用 1.文本控件Label 功能&#xff1a;显示一个文本 2.按键控件Button 功能&#x…

考研 | 组成原理【第二章】数据的表示和运算

考研 | 组成原理【第二章】数据的表示和运算 文章目录考研 | 组成原理【第二章】数据的表示和运算I. 数制与编码a. 进位计数制及其相互转换b. BCD码1. 8421码2. 余3码3. 2421码c. 无符号整数表示和运算1. 表示2. 运算d. 带符号整数的表示和运算1. 原码2. 反码3. 补码4. 移码5. …

多肽标签VSV-G Tag、YTDIEMNRLGK

VSV-G肽是一种源自水泡性口腔炎病毒糖蛋白的11个氨基酸的肽。编号: 191225 中文名称: 多肽标签VSV-G Tag 英文名: VSV-G Tag 英文同义词: VSV tag 单字母: H2N-YTDIEMNRLGK-OH 三字母: H2N-Tyr-Thr-Asp-Ile-Glu-Met-Asn-Arg-Leu-Gly-Lys-COOH 氨基酸个数: 11 分子式: C57H94N16…

【HMS Core】ScanPlus和Push推送库出现冲突该如何解决?

1、问题描述 项目中之前是同时集成了华为统一扫码服务SDK和华为推送SDK&#xff0c;这两天升级华为推送版本至6.3.0.304时发现这两个库出现了冲突&#xff0c; 这两个库的依赖版本情况如下所示&#xff1a; implementation com.huawei.hms:scanplus:2.7.0.301 implementatio…

[附源码]java毕业设计小型医院药品及门诊管理

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

《垃圾回收算法手册 自动内存管理的艺术》——内存分配(笔记)

文章目录七、内存分配7.1 顺序分配7.2 空闲链表分配7.2.1 首次适应分配7.2.2 循环首次适应分配7.2.3 最佳适应分配7.2.4空闲链表分配的加速快速适应分配(fast-fit allocation)位图适应分配(bitmapped-fits allocation)7.3 内存碎片化7.4 分区适应分配7.4.1 内存碎片7.4.2 空间大…

LINUX SECCOMP模块介绍

SECCOMP介绍 Seccomp是 "secure computing" 的 缩写。Linux内核2.6.12版本&#xff08;2005年3月8日&#xff09;引入。是linux一个安全模块&#xff0c;用于限制程序系统调用&#xff1b;当时如果使用了SECCOMP只允许4个系统调用&#xff1a; read&#xff0c;wri…

Flutter组件--Padding和AnimatedPadding

示意图: Padding介绍 在应用程序中有许多widget 时&#xff0c;这个时候画面常常会变得很拥挤&#xff0c;这个时候如果想要在widget之间来保留一些间距&#xff0c;那就用 Padding 为什么使用 Padding 而不使用 Container.padding 属性的 Container? Container 是将许多更…