[mirotalksfu] - add collaborative whiteboard
هذا الالتزام موجود في:
@@ -41,6 +41,14 @@ let isVideoOn = true;
|
||||
let recTimer = null;
|
||||
let recElapsedTime = null;
|
||||
|
||||
const wbWidth = 800;
|
||||
const wbHeight = 600;
|
||||
let wbCanvas = null;
|
||||
let wbIsDrawing = false;
|
||||
let wbIsOpen = false;
|
||||
var wbIsRedoing = false;
|
||||
var wbPop = [];
|
||||
|
||||
const socket = io();
|
||||
|
||||
function getRandomNumber(length) {
|
||||
@@ -56,6 +64,11 @@ function getRandomNumber(length) {
|
||||
function initClient() {
|
||||
if (!DetectRTC.isMobileDevice) {
|
||||
setTippy('closeNavButton', 'Close', 'right');
|
||||
setTippy('whiteboardUndoBtn', 'Undo', 'top');
|
||||
setTippy('whiteboardRedoBtn', 'Redo', 'top');
|
||||
setTippy('whiteboardSaveBtn', 'Save', 'top');
|
||||
setTippy('whiteboardCleanBtn', 'Clear', 'top');
|
||||
setTippy('whiteboardCloseBtn', 'Close', 'top');
|
||||
setTippy('participantsRefreshBtn', 'Refresh', 'top');
|
||||
setTippy('participantsCloseBtn', 'Close', 'top');
|
||||
setTippy('chatMessage', 'Press enter to send', 'top-start');
|
||||
@@ -66,6 +79,7 @@ function initClient() {
|
||||
setTippy('chatCloseButton', 'Close', 'bottom');
|
||||
setTippy('sessionTime', 'Session time', 'right');
|
||||
}
|
||||
setupWhiteboard();
|
||||
initEnumerateDevices();
|
||||
}
|
||||
|
||||
@@ -394,6 +408,7 @@ function roomIsReady() {
|
||||
} else {
|
||||
rc.makeDraggable(chatRoom, chatHeader);
|
||||
rc.makeDraggable(participants, participantsHeader);
|
||||
rc.makeDraggable(whiteboard, whiteboardHeader);
|
||||
if (navigator.getDisplayMedia || navigator.mediaDevices.getDisplayMedia) {
|
||||
show(startScreenButton);
|
||||
}
|
||||
@@ -408,6 +423,7 @@ function roomIsReady() {
|
||||
show(raiseHandButton);
|
||||
if (isAudioAllowed) show(startAudioButton);
|
||||
if (isVideoAllowed) show(startVideoButton);
|
||||
show(whiteboardButton);
|
||||
show(participantsButton);
|
||||
show(lockRoomButton);
|
||||
show(aboutButton);
|
||||
@@ -579,6 +595,24 @@ function handleButtons() {
|
||||
stopScreenButton.onclick = () => {
|
||||
rc.closeProducer(RoomClient.mediaType.screen);
|
||||
};
|
||||
whiteboardButton.onclick = () => {
|
||||
toggleWhiteboard();
|
||||
};
|
||||
whiteboardUndoBtn.onclick = () => {
|
||||
whiteboardAction(getWhiteboardAction('undo'));
|
||||
};
|
||||
whiteboardRedoBtn.onclick = () => {
|
||||
whiteboardAction(getWhiteboardAction('redo'));
|
||||
};
|
||||
whiteboardSaveBtn.onclick = () => {
|
||||
wbCanvasSaveImg();
|
||||
};
|
||||
whiteboardCleanBtn.onclick = () => {
|
||||
whiteboardAction(getWhiteboardAction('clear'));
|
||||
};
|
||||
whiteboardCloseBtn.onclick = () => {
|
||||
whiteboardAction(getWhiteboardAction('close'));
|
||||
};
|
||||
participantsButton.onclick = () => {
|
||||
getRoomParticipants();
|
||||
};
|
||||
@@ -604,6 +638,7 @@ function handleButtons() {
|
||||
// ####################################################
|
||||
|
||||
function handleSelects() {
|
||||
// devices options
|
||||
microphoneSelect.onchange = () => {
|
||||
rc.closeThenProduce(RoomClient.mediaType.audio, microphoneSelect.value);
|
||||
};
|
||||
@@ -613,6 +648,13 @@ function handleSelects() {
|
||||
videoSelect.onchange = () => {
|
||||
rc.closeThenProduce(RoomClient.mediaType.video, videoSelect.value);
|
||||
};
|
||||
// whiteboard options
|
||||
wbDrawingLineWidthEl.onchange = () => {
|
||||
wbCanvas.freeDrawingBrush.width = parseInt(wbDrawingLineWidthEl.value, 10) || 1;
|
||||
};
|
||||
wbDrawingColorEl.onchange = () => {
|
||||
wbCanvas.freeDrawingBrush.color = wbDrawingColorEl.value;
|
||||
};
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
@@ -749,7 +791,7 @@ function handleRoomClientEvents() {
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// SHOW LOG
|
||||
// UTILITY
|
||||
// ####################################################
|
||||
|
||||
function userLog(icon, message, position, timer = 3000) {
|
||||
@@ -766,6 +808,26 @@ function userLog(icon, message, position, timer = 3000) {
|
||||
});
|
||||
}
|
||||
|
||||
function saveDataToFile(dataURL, fileName) {
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = dataURL;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(dataURL);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function getDataTimeString() {
|
||||
const d = new Date();
|
||||
const date = d.toISOString().split('T')[0];
|
||||
const time = d.toTimeString().split(' ')[0];
|
||||
return `${date}-${time}`;
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// SOUND
|
||||
// ####################################################
|
||||
@@ -780,6 +842,174 @@ async function sound(name) {
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// HANDLE WHITEBOARD
|
||||
// ####################################################
|
||||
|
||||
function toggleWhiteboard() {
|
||||
toggleWhiteboardSettings();
|
||||
let whiteboard = rc.getId('whiteboard');
|
||||
whiteboard.classList.toggle('show');
|
||||
whiteboard.style.top = '50%';
|
||||
whiteboard.style.left = '50%';
|
||||
wbIsOpen = wbIsOpen ? false : true;
|
||||
}
|
||||
|
||||
function toggleWhiteboardSettings() {
|
||||
rc.getId('whiteboardSettings').classList.toggle('show');
|
||||
}
|
||||
|
||||
function setupWhiteboard() {
|
||||
setupWhiteboardCanvas();
|
||||
setupWhiteboardCanvasSize();
|
||||
setupWhiteboardLocalListners();
|
||||
}
|
||||
|
||||
function setupWhiteboardCanvas() {
|
||||
wbCanvas = new fabric.Canvas('wbCanvas');
|
||||
wbCanvas.freeDrawingBrush.color = '#FFFFFF';
|
||||
wbCanvas.freeDrawingBrush.width = 3;
|
||||
wbCanvas.isDrawingMode = true;
|
||||
}
|
||||
|
||||
function setupWhiteboardCanvasSize() {
|
||||
let optimalSize = [wbWidth, wbHeight];
|
||||
let scaleFactorX = window.innerWidth / optimalSize[0];
|
||||
let scaleFactorY = window.innerHeight / optimalSize[1];
|
||||
if (scaleFactorX < scaleFactorY && scaleFactorX < 1) {
|
||||
wbCanvas.setWidth(optimalSize[0] * scaleFactorX);
|
||||
wbCanvas.setHeight(optimalSize[1] * scaleFactorX);
|
||||
wbCanvas.setZoom(scaleFactorX);
|
||||
document.documentElement.style.setProperty('--wb-width', optimalSize[0] * scaleFactorX);
|
||||
document.documentElement.style.setProperty('--wb-height', optimalSize[1] * scaleFactorX);
|
||||
} else if (scaleFactorX > scaleFactorY && scaleFactorY < 1) {
|
||||
wbCanvas.setWidth(optimalSize[0] * scaleFactorY);
|
||||
wbCanvas.setHeight(optimalSize[1] * scaleFactorY);
|
||||
wbCanvas.setZoom(scaleFactorY);
|
||||
document.documentElement.style.setProperty('--wb-width', optimalSize[0] * scaleFactorY);
|
||||
document.documentElement.style.setProperty('--wb-height', optimalSize[1] * scaleFactorY);
|
||||
} else {
|
||||
wbCanvas.setWidth(optimalSize[0]);
|
||||
wbCanvas.setHeight(optimalSize[1]);
|
||||
wbCanvas.setZoom(1);
|
||||
document.documentElement.style.setProperty('--wb-width', optimalSize[0]);
|
||||
document.documentElement.style.setProperty('--wb-height', optimalSize[1]);
|
||||
}
|
||||
wbCanvas.calcOffset();
|
||||
wbCanvas.renderAll();
|
||||
}
|
||||
|
||||
function setupWhiteboardLocalListners() {
|
||||
wbCanvas.on('mouse:down', function () {
|
||||
mouseDown();
|
||||
});
|
||||
wbCanvas.on('mouse:up', function () {
|
||||
mouseUp();
|
||||
});
|
||||
wbCanvas.on('mouse:move', function () {
|
||||
mouseMove();
|
||||
});
|
||||
wbCanvas.on('object:added', function () {
|
||||
objectAdded();
|
||||
});
|
||||
}
|
||||
|
||||
function mouseDown() {
|
||||
wbIsDrawing = true;
|
||||
}
|
||||
|
||||
function mouseUp() {
|
||||
wbIsDrawing = false;
|
||||
wbCanvasToJson();
|
||||
}
|
||||
|
||||
function mouseMove() {
|
||||
if (!wbIsDrawing) return;
|
||||
}
|
||||
|
||||
function objectAdded() {
|
||||
if (!wbIsRedoing) wbPop = [];
|
||||
wbIsRedoing = false;
|
||||
}
|
||||
|
||||
function wbCanvasUndo() {
|
||||
if (wbCanvas._objects.length > 0) {
|
||||
wbPop.push(wbCanvas._objects.pop());
|
||||
wbCanvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
function wbCanvasRedo() {
|
||||
if (wbPop.length > 0) {
|
||||
wbIsRedoing = true;
|
||||
wbCanvas.add(wbPop.pop());
|
||||
}
|
||||
}
|
||||
|
||||
function wbCanvasSaveImg() {
|
||||
const dataURL = wbCanvas.toDataURL({
|
||||
width: wbCanvas.getWidth(),
|
||||
height: wbCanvas.getHeight(),
|
||||
left: 0,
|
||||
top: 0,
|
||||
format: 'png',
|
||||
});
|
||||
const dataNow = getDataTimeString();
|
||||
const fileName = `whiteboard-${dataNow}.png`;
|
||||
saveDataToFile(dataURL, fileName);
|
||||
}
|
||||
|
||||
function wbCanvasToJson() {
|
||||
if (rc.thereIsConsumers()) {
|
||||
var wbCanvasJson = JSON.stringify(wbCanvas.toJSON());
|
||||
rc.socket.emit('wbCanvasToJson', wbCanvasJson);
|
||||
}
|
||||
}
|
||||
|
||||
function JsonToWbCanvas(json) {
|
||||
if (!wbIsOpen) toggleWhiteboard();
|
||||
|
||||
wbCanvas.loadFromJSON(json);
|
||||
wbCanvas.renderAll();
|
||||
}
|
||||
|
||||
function getWhiteboardAction(action) {
|
||||
return {
|
||||
peer_name: peer_name,
|
||||
action: action,
|
||||
};
|
||||
}
|
||||
|
||||
function whiteboardAction(data, emit = true) {
|
||||
if (emit) {
|
||||
if (rc.thereIsConsumers()) {
|
||||
rc.socket.emit('whiteboardAction', data);
|
||||
}
|
||||
} else {
|
||||
userLog(
|
||||
'info',
|
||||
`${data.peer_name} <i class="fas fa-chalkboard-teacher"></i> whiteboard action: ${data.action}`,
|
||||
'top-end',
|
||||
);
|
||||
}
|
||||
|
||||
switch (data.action) {
|
||||
case 'undo':
|
||||
wbCanvasUndo();
|
||||
break;
|
||||
case 'redo':
|
||||
wbCanvasRedo();
|
||||
break;
|
||||
case 'clear':
|
||||
wbCanvas.clear();
|
||||
break;
|
||||
case 'close':
|
||||
if (wbIsOpen) toggleWhiteboard();
|
||||
break;
|
||||
//...
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// HANDLE PARTICIPANTS
|
||||
// ####################################################
|
||||
|
||||
@@ -393,6 +393,22 @@ class RoomClient {
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
this.socket.on(
|
||||
'wbCanvasToJson',
|
||||
function (data) {
|
||||
console.log('Received whiteboard canvas JSON');
|
||||
JsonToWbCanvas(data);
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
this.socket.on(
|
||||
'whiteboardAction',
|
||||
function (data) {
|
||||
console.log('Whiteboard action', data);
|
||||
whiteboardAction(data, false);
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
this.socket.on(
|
||||
'disconnect',
|
||||
function () {
|
||||
@@ -1029,7 +1045,6 @@ class RoomClient {
|
||||
if (this.consumers.size > 0) {
|
||||
return true;
|
||||
}
|
||||
this.userLog('info', 'No participants in the room', 'top-end');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1156,6 +1171,7 @@ class RoomClient {
|
||||
sendMessage() {
|
||||
if (!this.thereIsConsumers()) {
|
||||
chatMessage.value = '';
|
||||
this.userLog('info', 'No participants in the room', 'top-end');
|
||||
return;
|
||||
}
|
||||
let peer_msg = this.formatMsg(chatMessage.value);
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم