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.setAttribute(
// 'style',
// 'display: inline-block;width: 320px;height: 180px;background-color: black;margin-right: 8px;margin-bottom: 8px;'
// );
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');
}
});
// 推屏幕流状态变化
aliRtcEngine.on('screenSharePublishStateChanged', (oldState, newState, interval, channelId) => {
// oldState、newState 类型均为AliRtcSubscribeState,值包含 0(初始化)、1(未发布)、2(发布中)、3(已发布)
// interval 为两个状态之间的变化时间间隔,单位毫秒
console.log(`频道 ${channelId} 本地屏幕流的推流状态由 ${oldState} 变为 ${newState}`);
if (oldState === 3 && newState === 1) {
showToast('screenToast', '已停推屏幕流');
}
});
}
$('#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);
$('#boardBtn').prop('disabled', false);
$('#screenBtn').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);
$('#boardBtn').prop('disabled', true);
$('#screenBtn').prop('disabled', true);
showToast('loginToast', '已离开频道');
});
// 这里填入您的网易云信白板的 AppKey 和 AppSecret
// 仅限本地开发体验,线上环境请勿露出 AppSecret
const boradAppKey = '';
const boradAppSecret = '';
const boradUid = Date.now(); // 易云信白板要求是数字uid
const boradNickname = boradUid.toString();
const boradChannel = '821937123';
async function sha1(data) {
// 将字符串转换为ArrayBuffer
const buffer = new TextEncoder().encode(data);
// 使用Crypto API计算哈希值
const digest = await crypto.subtle.digest('SHA-1', buffer);
// 将ArrayBuffer转换为十六进制字符串
const hashArray = Array.from(new Uint8Array(digest));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
async function getAuthInfo() {
const Nonce = 'xxxx'; //任意长度小于128位的随机字符串
const curTime = Math.round((Date.now() / 1000)); //当前UTC时间戳,从1970年1月1日0点0分0秒开始到现在的秒数
const checksum = await sha1(boradAppSecret + Nonce + curTime);
return {
nonce: Nonce,
checksum: checksum,
curTime: curTime
};
}
// 创建白板实例
const boardIns = WhiteBoardSDK.getInstance({
appKey: boradAppKey,
nickname: boradNickname, //非必须
uid: boradUid,
container: document.getElementById('whiteboard'),
platform: 'web',
record: false, //是否开启录制
getAuthInfo: getAuthInfo
});
let drawPluginIns;
// 登录白板房间
boardIns.joinRoom({
channel: boradChannel,
createRoom: true
})
.then((drawPlugin) => {
drawPluginIns = drawPlugin;
// 允许编辑
drawPlugin.enableDraw(true);
// 设置画笔颜色
drawPlugin.setColor('rgb(243,0,0)');
// 初始化工具栏
const toolCollection = ToolCollection.getInstance({
container: document.getElementById('whiteboard'),
handler: drawPlugin,
options: {
platform: 'web'
}
});
toolCollection.addOrSetTool({
position: 'left',
insertAfterTool: 'pan',
item: {
tool: 'uploadCenter',
hint: '上传文档',
supportPptToH5: true,
supportDocToPic: true,
supportUploadMedia: false, // 关闭上传多媒体文件
supportTransMedia: false, // 关闭转码多媒体文件
},
});
toolCollection.removeTool({ name: 'image' });
// 显示工具栏
toolCollection.show();
// 监听文档事件
toolCollection.on('docAdd', (newDocs, allDocs) => {
console.log('add allDocs->', newDocs, allDocs);
// 您可以在 docAdd 事件回调中将文档数据上传至您的服务端
// 建议:服务端通过白板 channel 维度去储存
});
toolCollection.on('docDelete', (newDocs, allDocs) => {
console.log('delete allDocs->', newDocs, allDocs);
// 您可以在 docDelete 事件回调中将文档数据上传至您的服务端
// 建议:服务端通过白板 channel 维度去储存
});
// 初始化后从服务端中获取该 channel 的文件列表,并更新至白板SDK中
// fetch('/docList', (list) => {
// toolCollection.setDefaultDocList(list);
// });
});
$('#screenBtn').click(async() => {
if (!aliRtcEngine) {
showToast('screenToast', 'sdk 未准备好');
return;
}
try {
await aliRtcEngine.startScreenShare();
showToast('screenToast', '推屏幕成功');
} catch (error) {
showToast('screenToast', '推屏幕失败');
}
});
$('#boardBtn').click(async() => {
if (!aliRtcEngine || !drawPluginIns) {
showToast('screenToast', 'sdk 未准备好');
return;
}
const mediaStream = drawPluginIns.getStream({
width: 720,
frameRate: 15,
});
const videoTrack = mediaStream.getVideoTracks()[0];
if (videoTrack) {
try {
await aliRtcEngine.startScreenShare({
videoTrack,
});
showToast('screenToast', '推白板成功');
} catch (error) {
showToast('screenToast', '推白板失败');
}
} else {
showToast('screenToast', '无白板 videoTrack');
}
});