基于云开发 CloudBase 搭建在线视频会议应用
在线视频会议应用是基于浏览器的能力 WebRTC 以及 腾讯云开发 CloudBase 能力构建而成的应用. 在云开发的助力下, 一个复杂的在线会议应用, 一个人一两天即可完成.
云开发CloudBase开通,参加:https://console.cloud.tencent.com/tcb?tdl_anchor=techsite
在线体验 Demo
应用体验地址: Online Meeting Powered by Tencent Cloudbase
项目源码地址: Github
注: 应用仅供演示之用, 目前仅支持两人视频会议, 功能还不够完善, 还有许多可完善之处.
创建会议后可将会议地址发给他人, 或者在本机另起一浏览器窗口(未避免数据混乱, 可开隐私模式窗口, 或使用另一个浏览器)打开会议地址 来体验
在自己的云开发环境中部署
可以在线一键部署或通过本地部署的方式,来独立部署一个自己的在线视频会议应用
在线一键部署
只需要点击下方按钮,跳转到腾讯云控制台,即可在云端一键安装一个在线视频会议应用
本地部署
- 修改 .env 文件中的
ENV_ID
的值tcb-demo-10cf5b
修改为自己的环境 ID - 命令行 cd 到本目录中, 执行
npm run deploy
即可
技术解析
本应用用到的能力、工具、框架有:
- CloudBase Framework 用于项目基础目录结构生成, 一键部署
- Simple Peer 流行的 WebRTC 库
- 云开发-云函数, 包括云函数的定时调用
- 云开发-数据库
- 云开发-静态网站托管
- React
- Ant design
如果你不清楚项目开发的基本命令, 可阅读本项目使用的模版的 readme.md
背景知识
Web RTC
- WebRTC 即 Web 实时通信技术, 由一系列浏览器 API 组成, 包括 navigator.getUserMedia*,* MediaStream*, RTC**相关的全局对象*
- WebRTC 是一种 P2P 的通信技术, 浏览器之间可以对等连接. 但浏览器之间需要通过一个信令服务器来交换信令后方可建立连接
- 浏览器的信令信息的获取需要一个 ICE 服务器, 一般默认会使用谷歌的公共服务器
云开发
云开发(CloudBase)是云端一体化的后端云服务 ,采用 serverless 架构,免去了应用构建中繁琐的服务器搭建和运维。同时云开发提供的静态托管、命令行工具(CLI)、Flutter SDK 等能力降低了应用开发的门槛。使用云开发可以构建完整的小程序/小游戏、H5、Web、移动 App 等应用。
CloudBase Framework
CloudBase Framework 是云开发官方出品的开源前后端一体化部署工具,无需改动代码,实现前后端一键托管部署,支持常见的框架和语言,支持自动识别并部署。不仅可以部署应用前后端到 Serverless,还可以扩展更多后端能力。
Github 地址: https://github.com/TencentCloudBase/cloudbase-framework
完整搭建步骤:从 0 到 1 实现一个在线会议应用
整个应用的构建, 从项目初始化到最终可以一键部署, 共分为 6 个部分. 为方便读者查阅,主要的代码实现分了 6 次提交, 下述说明中会列出每一步对应的提交 commit.
第 1 步 初始化项目和视频页面
注意要点:
- 在进行操作之前, 请确保已经注册腾讯云账户
- WebRTC 需要浏览器支持, 只有现代浏览器才支持, 建议使用 Chrome、Firefox 来体验, 具体兼容性可查看 caniuse
- 由于安全策略限制, WebRTC 仅支持 https 协议网站; 其为方便本地开发, 也支持 http 的
localhost
及127.0.0.1
(不限端口), 不支持其他自定义的本机域名、IP - WebRTC 并不具备穿透内网功能, 测试体验时, 确保双方机器都处于公网之中并能访问云开发相关域名
操作步骤
- 初始化项目结构
首先需要全局安装 Cloudbase CLI
npm i @cloudbase/cli@latest -g
使用以下命令来使用云开发的 react 应用模版创建一个 React 云开发项目
cloudbase init --template react-starter
- 引入 UI 库 ant-design
npm i ant-d @ant-design/icons -S
- 增加 landing 页, 用于检测 WebRTC 能力以及判断用户是否授予摄像头及麦克风访问权限
landing 页面核心代码 meeting-simple/src/landing/index.js
import {
LoadingOutlined, WarningOutlined } from "@ant-design/icons";
import React, {
useEffect, useState } from "react";
import * as utils from "../utils";
// import * as api from './meeting/api'
export default function Landing(props) {
// 检测 RTC 支持
return !utils.isSupportRTC() ? (
<NotSupport />
) : (
<NotReady setReady={
props.setReady} />
);
}
// 不支持时的显示
function NotSupport() {
// ...
}
// 支持 RTC 时的显示
function NotReady(props) {
const [permissionState, setPermissionState] = useState("prompt");
const [timeCount, setTimeCount] = useState(0);
const [loadingState, setLoadingState] = useState("init");
const retry = () => {
setTimeCount(timeCount + 1);
};
// 不同状态时的提示信息,prompt、granted、denied
const permissionStr = {
prompt: (
<p>
Please allow camera and microphone access to continue, you can turn off
camera or microphone later in meeting
</p>
),
denied: (
<p>
You should granted camera microphone permissions,{
" "}
<a onClick={
retry}>click to retry</a>
</p>
),
granted: <p>Loading meeting info...</p>,
};
useEffect(() => {
(async () => {
// 检测权限
const status = await utils.checkMediaPermission();
// 设置授权信息
setPermissionState(status ? "granted" : "denied");
if (!status) return;
try {
// 从浏览器参数拿到会话信息
const sessID = location.hash.slice(1);
// if (sessID) {
// await api.getSessionInfo(sessID)
// }
props.setReady("landing");
} catch (error) {
console.warn("failed to get session info", error);
setLoadingState("Failed to get meeting info: " + JSON.stringify(error));
}
})();
}, [timeCount]);
const tip =
permissionStr[permissionState] ||
(loadingState === "init" ? "loading..." : loadingState);
return <div className="landing-mask"><!--loading 信息--></div>;
}
- 增加 Video-window 页, 用于支持视频画面显示
Video-window 核心代码 meeting-simple/src/meeting/video-window/index.js
import React, {
useRef, useEffect } from "react";
import * as utils from "../../utils";
export default function VideoWindow(props) {
const videoRef = useRef(null);
useEffect(() => {
const updateStream = (stream) => {
// video 对象对应的dom
const dom = videoRef.current