11.12
增加屏幕共享功能。
面试官点击按钮,要求考生发布自己的屏幕共享。
发送屏幕共享,A和B方都要init融云,先初始化im,再使用RTClib,再两人都进入房间。然后B获取屏幕信息(先不指定窗口,让考生那边自己选择)然后发布到服务器,然后A订阅到B的媒体流,展示。
初始化:初始化+进入房间
A:发送命令,让B主动发布视频共享。然后展示屏幕共享的媒体流
B:收到某个指令,或某个指令帮B执行发布操作,B就选择屏幕共享弹窗中的窗口发布。
先考虑,A发送命令,让B主动做事情,这个事情怎么实现。
B收到很多消息,通过判断extra来判断这是命令还是文本消息。如果是命令,就执行对应的操作,比如获取屏幕并发布。
(现在要找到在哪里接收信息的……)√发送成功的信息都在message.js里面了,可以实时监听commands有没有变多。
message.js中,它会return一个messages,这个东西是通过append增加的。append被使用两次,第一次使用是将准备发送的消息发过来,此时extra是PENDING状态,这时候就setMessages(为什么,这时候还没有发送成功啊???),然后sendMessage成功后再append一次,此时extra为空,进入条件分支语句将之前append的那个message的PENDING状态去掉。
append(message) {
const {type, content, extra} = message;
if (type === SELF && extra !== PENDING) {
for (const message of _messages) {
if (message.extra === PENDING && message.content === content) {
message.extra = extra;
return setMessages([..._messages]);
}
}
}
_messages = [..._messages, message];
setMessages(_messages);
}
那对方发送的message会怎么加到这个messages里面?
rongyun.js里面,im.watch中,有一个message的方法,这个方法会在收到函数时触发。可以修改这里的逻辑,通过判断extra,如果是普通message,就append;如果是command,就将message发送到commands中。
message({message}) {
const {content: {content}, messageUId: uid, senderUserId: id, sentTime, extra:{extra}} = message;
if(extra === "screen")
imModel.addCommand({content, extra})
else
imModel.append({uid, type: Message.OTHER, content, id, timestamp: sentTime});
},
(useMessages好像是一个管理全局状态的东西,所有我觉得也可以把命令发到这里?)那在useMessages这里增加一个commands相关方法:
const [commands, setCommands] = useState([]);
let _commands = [];
addCommand(message) {
const {type, command, extra} = message;
_commands = [..._commands, message];
setCommands(_commands);
}
假设A点击“查看屏幕共享”按钮,就会生成一条message,extra设置为"screenshare",然后addCommands。
B处(在哪)收到新的commands后,根据不同的extra做不同的事情。比如现在它收到extra = "screenshare",那就立即获取屏幕信息并发布。
我认为B是在面试过程中收到面试官要查看屏幕的要求的,所有这个”监听“写在interviewee/room中比较好?
(怎么监听?)发送成功的信息都在message.js里面了,可以实时监听commands有没有变多。
干脆先这样,反正现在也只有screen这一个command,那只要command里面有东西,就获取屏幕资源并发布。并且发布成功后就清空命令。
然后看看,怎么在这个组件中使用融云那个组件里的东西
初始化后,给出send sendCommand sendMessage到rong里面。
rong在useRongyunVideo里面。
那我再rong增加一个发布屏幕资源的方法,让考生那边用。然后面试官就用sendCommand。
const video = useRongyunVideoModel();
const {setRong, setInfo} = video;
发布屏幕资源的方法:
async function screenShare(){
const mediaStream = await navigator.mediaDevices.getDisplayMedia();
user.stream = {
tag: 'ScreenShare',
type: StreamType.VIDEO,
mediaStream
};
stream.publish(user).then(()=>{
console.log("screen share success");});
}
但是这样使用同一个user的stream会不会覆盖?没有覆盖
在考生那里,导入commands和useEffect,当commands变化时,调用发布的方法。
修改了rongyun.js,在订阅资源那里,根据stream的tag来判断,是对方的摄像头资源,还是屏幕共享资源,分别放到不同的标签里去展示。
成功了!
优化一:
参考简历上传页面。如果没有屏幕资源,就显示<Result>,如果有屏幕资源,就显示屏幕资源。
优化二:
命令存放为数组,每发送一个指令就询问共享哪个窗口。√
如何更新新的窗口,现在是考生重新共享别的窗口后,也不更新视频信息
优化三:
监听考生离开,监听考生回来
优化四:
只允许考生选择整个窗口(防止作弊),若未选择整个窗口,就要求重新选择,直到选择整个窗口。(这个我做出来了,只要检测到不是选择整个窗口的行为,包括关掉、拒绝共享、未选择整个窗口等,就重复发起共享请求。不要忘记停止这个资源的获取,有个类似stop track的方法可以做这件事,不然很可能会捕获很多次屏幕资源,风扇嗡嗡响
let screenStream;
async function screenStart() {
for (; ;) {
try {
screenStream = await utils.shareScreen();
} catch {
message.warning("请选择整个屏幕");
continue;
}
if (screenStream.getVideoTracks()[0].label.startsWith('screen')) {
break;
}
message.warning("请选择整个屏幕");
screenStream.getVideoTracks()[0].stop();
}
const screenUser = {
...user,
stream: {
tag: TAG_SCREEN,
type: StreamType.VIDEO,
mediaStream: screenStream
}
};
stream.publish(screenUser);
screenStream.getVideoTracks()[0].onended = () => stream.unpublish(screenUser);
}
async function screenStop() {
screenStream.getVideoTracks()[0].stop();
const screenUser = {
...user,
stream: {
tag: TAG_SCREEN,
type: StreamType.VIDEO,
mediaStream: screenStream
}
}
stream.unpublish(screenUser);
}