最近项目在做直播老师端客户端需求,第一次接触直播的技术,还有比较前言的react技术,值得好好学习学习。
公司用的是声网sdk
来实时音视频互动的。
本篇文章概要:
- 功能介绍
- 技术方案
- 实时消息RTM
- 实时音视频RTC
- 白板Netless
- 三个SDK使用及成本
- 三个SDK初始化流程
1.功能介绍
大班课场景描述:一名老师在课堂上进行教学,成千上万的学生通过网络实时观看和收听;同时,学生可以举手请求发言,与老师进行实时音视频互动。 该场景在大型网络公开课中应用尤为广泛。
功能点剖析:
实时音视频
实时消息
白板
录制
课堂管理
设备及网络检测
屏幕共享
实时音视频: 教师对学生讲课,学生能实时接收老师的音频和视频。 教学过程中,学生可以举手请求发言,与老师进行互动。 所有学生都可以看到和听到互动学生和老师的画面及声音。
实时消息: 学生和教师在课堂中发送实时文字消息进行互动。
白板: 教师在白板上涂鸦、上传文件(PPT、Word 和 PDF)或播放视频, 有助于提炼教学重点,帮助学生理解或记忆。
录制: 教师将课堂内容录制下来,并即时生成回放链接,方便学生课后复习, 和学校评估教学质量。
课堂管理: 教师控制课堂的开始或结束,并管理学生在上课过程中发送音、视频 和实时消息的权限。
设备及网络检测: 正式上课前,教师可以检测麦克风、摄像头等音视频设备能否正常工作, 同时整个上课过程中,学生和教师都可以实时检测网络质量,确保课堂顺利进行。
屏幕共享: 教师将自己屏幕的内容分享给学生观看,提高教学效果。
2.技术方案
明确使用的SDK有:
实时消息
:RTM
(agora-rtm-sdk)
实时音视频
:RTC
Web RTC (agora-rtc-sdk)
Electron RTC (agora-electron-sdk)
互动白板
:Netless
White Board(white-web-sdk)
RTM 使用流程: createInstance:创建并返回一个 RtmClient 实例 login:登录 Agora RTM 系统 createChannel:创建 Agora RTM 频道,一个 RtcClient 可以创建多个频道 join:加入 Agora RTM 频道 sendMessage:发送频道消息,成功发送后,频道内所有用户都能收到。 leave:离开 RTM 频道
RTC 使用流程: createClient:创建客户端 Client.init:初始化客户端对象 Client.setClientRole:设置直播场景下的用户角色,互动直播大班课场景中,我们将老师的用户角色设为主播。 createStream:创建并返回音视频流对象 Stream.init:初始化音视频对象 Client.join:加入 Agora RTC 频道 Client.publish:发布本地音视频流至 SD-RTN Client.on("stream-added"):远端音视频已添加 Client.subscribe:订阅远端音视频流 Stream.play:播放音、视频流 Client.leave:离开 RTC 频道
Netless 使用流程: new WhiteWebSdk:创建白板房间whiteWebSdk实例 joinRoom:调用joinRoom,获得room对象 WhiteWebSdk.on("onPhaseChanged"):白板房间连接状态发生改变 WhiteWebSdk.on("onRoomStateChanged"):白板房间状态发生改变 disconnect:离开白板房间
Netless 白板流程图:
想用好SDK,必须对其常用API进行学习和使用,哪些API在哪些场景,哪个生命周期中使用,是必要的。本篇着重解释三个SDK其涉及的方法~
3.实时消息RTM(Real-time Messaging)
// 创建rtm实例
const rtmClient = AgoraRTM.createInstance(appID, { enableLogUpload: ENABLE_LOG, logFilter });
// 登录
rtmClient.login({uid, token});
// 创建频道
rtmClient.createChannel(channel);
// 加入频道
rtmClient.join();
// 发送频道消息
rtmClient.sendMessage({text: body}, {enableHistoricalMessaging});
// 离开频道频道
rtmClient.leave()
// 登出
rtmClient.logout();
// 移除所有监听函数
rtmClient.removeAllListeners();
// 查询某指定频道的全部属性
rtmClient.getChannelAttributes(this._currentChannelName)
// 查询单个或多个频道的成员人数
rtmClient.getChannelMemberCount(ids)
// 查询指定用户的在线状态
rtmClient.queryPeersOnlineStatus(ids)
// 添加或更新某指定频道的属性
rtmClient.addOrUpdateChannelAttributes(
this._currentChannelName,
channelAttributes,
{enableNotificationToChannelMembers: true}
);
// 删除某指定频道的指定属性
rtmClient.deleteChannelAttributesByKeys(
this._currentChannelName,
[this._channelAttrsKey],
{enableNotificationToChannelMembers: true}
);
// 连接状态改变的处理逻辑
rtmClient.on("ConnectionStateChanged", (newState: string, reason: string) => {
this._bus.emit("ConnectionStateChanged", {newState, reason});
});
// 收到点对点消息的处理逻辑
rtmClient.on("MessageFromPeer", (message: any, peerId: string, props: any) => {
this._bus.emit("MessageFromPeer", {message, peerId, props});
});
// 收到频道消息的处理逻辑
rtmClient.on('ChannelMessage', (message: string, memberId: string) => {
this._bus.emit('ChannelMessage', {message, memberId});
});
// 收到频道成员加入的处理逻辑
rtmClient.on('MemberJoined', (memberId: string) => {
this._bus.emit('MemberJoined', memberId);
});
// 收到频道成员离开的处理逻辑
rtmClient.on('MemberLeft', (memberId: string) => {
this._bus.emit('MemberLeft', memberId);
});
// 收到频道成员数量更新的逻辑
rtmClient.on('MemberCountUpdated', (count: number) => {
this._bus.emit('MemberCountUpdated', count);
})
// 收到频道属性更新的处理逻辑
rtmClient.on('AttributesUpdated', (attributes: any) => {
this._bus.emit('AttributesUpdated', attributes);
});
想看更多关于RTM的 API,可参考声网官方文档: Agora RTM JavaScript SDK API 参考: https://docs.agora.io/cn/Real-time-Messaging/API%20Reference/RTM_web/index.html
4.实时音视频RTC(Real-Time Communication)
const AgoraRtcEngine = require('agora-electron-sdk').default;
const rtcEngine = new AgoraRtcEngine();
// 初始化
rtcEngine.initialize(APP_ID);
// 设置频道场景
rtcEngine.setChannelProfile(1);
// 启用视频模块
rtcEngine.enableVideo();
// 启用音频模块
rtcEngine.enableAudio();
// 桌面端开启与 Web SDK 的互通
rtcEngine.enableWebSdkInteroperability(true);
// 设置本地流的视频编码属性
// 分辨率 640 * 480,帧率 30 fps,码率 750 Kbps
rtcEngine.setVideoProfile(43, false);
// 设置日志文件
rtcEngine.setLogFile(logPath)
// set for preview
// 设置本地视图和渲染器
rtcEngine.setupLocalVideo(dom);
// 设置视窗内容显示模式 哪个用户的流/视频尺寸等比缩放
rtcEngine.setupViewContentMode(streamID, fillContentMode);
// 设置直播场景下的用户角色 1主播
rtcEngine.setClientRole(1);
// 开启视频预览
rtcEngine.startPreview();
// 停止/恢复发送本地视频流
rtcEngine.muteLocalVideoStream(nativeClient.published);
// 停止/恢复发送本地音频流
rtcEngine.muteLocalAudioStream(nativeClient.published);
// 停止视频预览
rtcEngine.stopPreview();
// 设置直播场景下的用户角色 2观众
rtcEngine.setClientRole(2);
// 设置 videoSource 的渲染器
rtcEngine.setupLocalVideoSource(dom);
// 销毁渲染视图
rtcEngine.destroyRenderView(streamID, dom, (err: any) => { console.warn(err.message) });
// 是否启动摄像头采集并创建本地视频流
rtcEngine.enableLocalVideo(false);
想看更多关于RTC的 API,可参考声网官方文档: Agora Electron SDK API 参考: https://docs.agora.io/cn/Video/API%20Reference/electron/index.html
5.白板Netless
import { Room, WhiteWebSdk, DeviceType, SceneState, createPlugins, RoomPhase } from 'white-web-sdk';
//初始化 SDK
const whiteWebSdk = new WhiteWebSdk({
appIdentifier: "{{appIdentifier}}"
preloadDynamicPPT: false, // 可选,是否预先加载动态 PPT 中的图片,会显著提升用户体验,降低翻页的图片加载时长
deviceType: "touch", // 可选, touch or desktop , 默认会根据运行环境进行推断
plugins,
loggerOptions: {
disableReportLog: ENABLE_LOG ? false : true,
reportLevelMask: "debug",
printLevelMask: "debug",
}
// ...更多可选参数配置
});
// 引入plugins的地方,集成视频、音频插件
import { videoPlugin } from '@netless/white-video-plugin';
import { audioPlugin } from '@netless/white-audio-plugin';
// createPlugins 方法可以构造出 plugins
const plugins = createPlugins({"video": videoPlugin, "audio": audioPlugin});
// setPluginContext 方法可以设置 plugin 谁可以控制
plugins.setPluginContext("video", {identity: "host"});
// 如果身份是老师填 host 是学生 guest
plugins.setPluginContext("audio", {identity: "host"});
// 初始化完成后,调用joinRoom,获得room对象
const roomParams = {
uuid,
roomToken,
disableBezier: true,
disableDeviceInputs,
disableOperations,
isWritable,
}
const room = await whiteWebSdk.joinRoom(roomParams, {
// 房间连接状态发生改变时
onPhaseChanged: (phase: RoomPhase) => {
if (phase === RoomPhase.Connected) {
this.updateLoading(false);
} else {
this.updateLoading(true);
}
console.log("[White] onPhaseChanged phase : ", phase);
},
// 房间状态发生改变时
onRoomStateChanged: state => {
console.log("onRoomStateChanged", state)
if (state.zoomScale) {
whiteboard.updateScale(state.zoomScale);
}
if (state.sceneState || state.globalState) {
whiteboard.updateRoomState();
}
},
onDisconnectWithError: error => {},
onKickedWithReason: reason => {},
onKeyDown: event => {},
onKeyUp: event => {},
onHandToolActive: active => {},
onPPTLoadProgress: (uuid: string, progress: number) => {},
});
onPhaseChanged:
仅当房间处于connected
状态时,房间接受用户教具操作。为了用户体验,推荐对连接中状态进行处理。
export enum RoomPhase {
//正在连接
Connecting = "connecting",
//已连接服务器
Connected = "connected",
//正在重连
Reconnecting = "reconnecting",
//正在断开连接
Disconnecting = "disconnecting",
//连接中断
Disconnected = "disconnected",
}
onRoomStateChanged:
房间状态发生改变时,会返回RoomState
发生变化的房间状态字段。
RoomState 定义:
///Room.d.ts
type RoomState = {
// 全局状态,所有人可读
readonly globalState: GlobalState;
// 房间成员列表
readonly roomMembers: ReadonlyArray<RoomMember>;
// 获取场景状态 [页面(场景)管理]
readonly sceneState: SceneState;
// 用户的教具状态 [教具使用]
readonly memberState: MemberState;
// 主播用户信息 [视角操作]
readonly broadcastState: Readonly<BroadcastState>;
// 切换主播,观众,自由视角模式 [视角操作]
readonly zoomScale: number;
};
想看更多关于Netless的 API,可参考Netless官方文档: Agora Electron SDK API 参考: https://developer.herewhite.com/docs/javascript/parameters/js-sdk/
6.三个SDK使用及成本
在一个项目里,同时集成这么多SDK的时候,需要清楚的明白,为什么使用该SDK,每个SDK的使用场景是什么,在什么时候使用
,每个SDK的成本
?
6.1 使用场景
RTM:使用它实现实时消息服务
,场景是发送频道消息
,比如:加入/离开房间、消息聊天、学生举手请求发言、老师操作xxx,影响学生端等功能;
定义一个Message Model:
export type ChatMessage = {
cmd: ChatCmdType, // 消息类型
data: string // cmd为聊天类型时,data为聊天内容,其余为json数据
fromUserId: string // 发送消息的用户id,不可缺省
toUserId?: string // 接收消息的用户id,部分消息可缺省,看具体业务
}
业务场景,目前定义的cmd类型有:
export enum ChatCmdType {
chat = 1, // 群聊消息,data表示消息内容
addAnnounce = 2, // 发布公告,data表示消息内容
deleteAnnounce = 3, // 删除公告,data表示公告内容
startCourse = 4, // 开始上课
endCourse = 5, // 结束上课
roomMemberJoin = 6, // 用户进入,data表示用户数据json
roomMemberLeft = 7, // 用户离开
bannedOn = 8, // 开启禁言
bannedOff = 9, // 关闭禁言
videoMajor = 10, // 老师画面出于主区域
videoMinor = 11, // 老师画面出于副区域
studentSendApply = 12, // 学生举手连麦
teacherSendAccept = 13, // 老师同意连麦,包含强制连麦
teacherSendReject = 14, // 老师拒绝连麦
teacherSendStop = 15, // 断开连麦
muteVideo = 16, // 禁用学生视频
unmuteVideo = 17, // 开启学生视频
muteAudio = 18, // 禁用学生音频
unmuteAudio = 19, // 开启学生音频
lockBoard = 20, // 老师锁定白板
unlockBoard = 21, // 老师解锁白板
studentCancelApply = 22, // 学生取消举手连麦
muteAllChat = 23, // 全员禁言
unmuteAllChat = 24, // 取消全员禁言
}
RTC:使用它实现实时音视频互动
,场景是传输音视频
,比如:老师开始上课,老师和学生进行连麦,学生能看到老师,听到老师的声音等。
NetLess:使用它实现电子上课黑板
,场景是老师上课
,比如:老师上传的课件资源(PPT、EXCEL、图片、视频、音频等)。
6.2 成本
实时消息RTM成本
:
每月最高日活跃用户2w以下免费
,超过日活2w以上,每多1000人,收100元。
付费成本最低,RTM相当于免费用,只是日活多2w的时候,就得注意了。
实时音视频RTC成本
:
每月1w分钟的免费时长
,超过1w分钟,按照音频每1000分钟7元,视频每1000分钟28元/105元收费,小程序的话更贵。
付费成本算最高的了,每月只有1w分钟可以免费用,超过就开始收费了。
白板Netless的成本
:
付费成本还算合理,用多少付多少,价格也便宜。
7.三个SDK初始化流程:
老师端: 老师进入房间: 初始化RTM,加入RTM频道; 初始化RTC,不加入RTC频道,点击上课,才开始加入RTC频道; 初始化NetLess,加入NetLess频道。
老师开始上课: 发RTM消息,通知学生上课; 加入RTC频道,开始推送视频流给学生。
老师结束上课: 发RTM消息,通知学生下课; 离开RTC频道。
老师离开房间: 离开RTM频道,离开NetLess频道。
学生端: 学生进入房间: 初始化RTM,加入RTM频道; 初始化RTC,不加入RTC频道,等收到老师发的RTM上课消息,才开始加入RTC频道; 初始化NetLess,不加入NetLess频道,等收到老师发的RTM上课消息,才开始加入NetLess频道。
学生开始上课: 收到老师开始上课的RTM消息,加入RTC频道,开始接收老师视频流。
学生结束上课: 收到老师结束上课的RTM消息,离开RTC频道,断开老师视频流; 收到老师离开RTC频道的消息,离开RTC频道,断开老师视频流。
学生离开房间: 正在上课离开:离开RTM频道、离开RTC频道,离开NetLess频道。 结束上课离开:离开RTM频道、离开NetLess频道。
熟悉完这三个SDK后,一个直播间,其实是由三个小房间组成
。完成它们正常的调用逻辑,相当于完成整个项目的30%了。下一篇着重从项目入手,介绍技术栈electron、react hooks、及ts,尽情期待。