[mirotalksfu] - add File Sharing
هذا الالتزام موجود في:
@@ -138,6 +138,7 @@ access to use this app.
|
|||||||
<button id="stopVideoButton" class="hidden"><i class="fas fa-video"></i> Stop video</button>
|
<button id="stopVideoButton" class="hidden"><i class="fas fa-video"></i> Stop video</button>
|
||||||
<button id="startScreenButton" class="hidden"><i class="fas fa-desktop"></i> Start screen</button>
|
<button id="startScreenButton" class="hidden"><i class="fas fa-desktop"></i> Start screen</button>
|
||||||
<button id="stopScreenButton" class="hidden"><i class="fas fa-stop-circle"></i> Stop screen</button>
|
<button id="stopScreenButton" class="hidden"><i class="fas fa-stop-circle"></i> Stop screen</button>
|
||||||
|
<button id="fileShareButton" class="hidden"><i class="fas fa-folder-open"></i> File Sharing</button>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button id="whiteboardButton" class="hidden">
|
<button id="whiteboardButton" class="hidden">
|
||||||
<i class="fas fa-chalkboard-teacher"></i> Whiteboard
|
<i class="fas fa-chalkboard-teacher"></i> Whiteboard
|
||||||
@@ -171,6 +172,13 @@ access to use this app.
|
|||||||
</div>
|
</div>
|
||||||
<div id="remoteAudios"></div>
|
<div id="remoteAudios"></div>
|
||||||
|
|
||||||
|
<div id="sendFileDiv" class="fadein center">
|
||||||
|
<div id="sendFileInfo"></div>
|
||||||
|
<div id="sendFilePercentage"></div>
|
||||||
|
<progress id="sendProgress" max="0" value="0"></progress>
|
||||||
|
<button id="sendAbortBtn" class="fas fa-stop-circle"> Abort</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<section id="participants" class="fadein center hidden">
|
<section id="participants" class="fadein center hidden">
|
||||||
<header id="participantsHeader" class="participants-header">
|
<header id="participantsHeader" class="participants-header">
|
||||||
<div id="participantsTitle" class="participants-header-title"></div>
|
<div id="participantsTitle" class="participants-header-title"></div>
|
||||||
|
|||||||
@@ -577,6 +577,40 @@ button:hover {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------
|
||||||
|
# Send File
|
||||||
|
--------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#sendFileDiv {
|
||||||
|
z-index: 7;
|
||||||
|
display: none;
|
||||||
|
padding: 10px;
|
||||||
|
margin: auto;
|
||||||
|
min-width: 320px;
|
||||||
|
background: var(--body-bg);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendAbortBtn {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sendAbortBtn:hover {
|
||||||
|
color: rgb(255, 0, 0);
|
||||||
|
transform: var(--btns-hover-scale);
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
width: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/*--------------------------------------------------------------
|
/*--------------------------------------------------------------
|
||||||
# Whiteboard
|
# Whiteboard
|
||||||
--------------------------------------------------------------*/
|
--------------------------------------------------------------*/
|
||||||
|
|||||||
@@ -436,6 +436,7 @@ function roomIsReady() {
|
|||||||
show(raiseHandButton);
|
show(raiseHandButton);
|
||||||
if (isAudioAllowed) show(startAudioButton);
|
if (isAudioAllowed) show(startAudioButton);
|
||||||
if (isVideoAllowed) show(startVideoButton);
|
if (isVideoAllowed) show(startVideoButton);
|
||||||
|
show(fileShareButton);
|
||||||
show(whiteboardButton);
|
show(whiteboardButton);
|
||||||
show(participantsButton);
|
show(participantsButton);
|
||||||
show(lockRoomButton);
|
show(lockRoomButton);
|
||||||
@@ -608,6 +609,12 @@ function handleButtons() {
|
|||||||
stopScreenButton.onclick = () => {
|
stopScreenButton.onclick = () => {
|
||||||
rc.closeProducer(RoomClient.mediaType.screen);
|
rc.closeProducer(RoomClient.mediaType.screen);
|
||||||
};
|
};
|
||||||
|
fileShareButton.onclick = () => {
|
||||||
|
rc.selectFileToShare();
|
||||||
|
};
|
||||||
|
sendAbortBtn.onclick = () => {
|
||||||
|
rc.abortFileTransfer();
|
||||||
|
};
|
||||||
whiteboardButton.onclick = () => {
|
whiteboardButton.onclick = () => {
|
||||||
toggleWhiteboard();
|
toggleWhiteboard();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,6 +105,17 @@ class RoomClient {
|
|||||||
this.recScreenStream = null;
|
this.recScreenStream = null;
|
||||||
this._isRecording = false;
|
this._isRecording = false;
|
||||||
|
|
||||||
|
// file transfer settings
|
||||||
|
this.fileToSend = null;
|
||||||
|
this.fileReader = null;
|
||||||
|
this.receiveBuffer = [];
|
||||||
|
this.receivedSize = 0;
|
||||||
|
this.incomingFileInfo = null;
|
||||||
|
this.incomingFileData = null;
|
||||||
|
this.sendInProgress = false;
|
||||||
|
this.fileSharingInput = '*';
|
||||||
|
this.chunkSize = 1024 * 16; // 16kb/s
|
||||||
|
|
||||||
this.myVideoEl = null;
|
this.myVideoEl = null;
|
||||||
this.connectedRoom = null;
|
this.connectedRoom = null;
|
||||||
this.debug = false;
|
this.debug = false;
|
||||||
@@ -393,6 +404,28 @@ class RoomClient {
|
|||||||
}.bind(this),
|
}.bind(this),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.socket.on(
|
||||||
|
'fileInfo',
|
||||||
|
function (data) {
|
||||||
|
console.log('File info:', data);
|
||||||
|
this.handleFileInfo(data);
|
||||||
|
}.bind(this),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.socket.on(
|
||||||
|
'file',
|
||||||
|
function (data) {
|
||||||
|
this.handleFile(data);
|
||||||
|
}.bind(this),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.socket.on(
|
||||||
|
'fileAbort',
|
||||||
|
function (data) {
|
||||||
|
this.handleFileAbort(data);
|
||||||
|
}.bind(this),
|
||||||
|
);
|
||||||
|
|
||||||
this.socket.on(
|
this.socket.on(
|
||||||
'wbCanvasToJson',
|
'wbCanvasToJson',
|
||||||
function (data) {
|
function (data) {
|
||||||
@@ -1432,6 +1465,249 @@ class RoomClient {
|
|||||||
this.sound('recStop');
|
this.sound('recStop');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ####################################################
|
||||||
|
// FILE SHARING
|
||||||
|
// ####################################################
|
||||||
|
|
||||||
|
selectFileToShare() {
|
||||||
|
this.sound('open');
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
allowOutsideClick: false,
|
||||||
|
background: swalBackground,
|
||||||
|
position: 'center',
|
||||||
|
title: 'Share the file',
|
||||||
|
input: 'file',
|
||||||
|
inputAttributes: {
|
||||||
|
accept: this.fileSharingInput,
|
||||||
|
'aria-label': 'Select the file',
|
||||||
|
},
|
||||||
|
showDenyButton: true,
|
||||||
|
confirmButtonText: `Send`,
|
||||||
|
denyButtonText: `Cancel`,
|
||||||
|
showClass: {
|
||||||
|
popup: 'animate__animated animate__fadeInDown',
|
||||||
|
},
|
||||||
|
hideClass: {
|
||||||
|
popup: 'animate__animated animate__fadeOutUp',
|
||||||
|
},
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
this.fileToSend = result.value;
|
||||||
|
if (this.fileToSend && this.fileToSend.size > 0) {
|
||||||
|
if (!this.thereIsConsumers()) {
|
||||||
|
userLog('info', 'No participants detected', 'top-end');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// send some metadata about our file to peers in the room
|
||||||
|
this.socket.emit('fileInfo', {
|
||||||
|
peer_name: peer_name,
|
||||||
|
fileName: this.fileToSend.name,
|
||||||
|
fileSize: this.fileToSend.size,
|
||||||
|
fileType: this.fileToSend.type,
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
this.sendFileData();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
userLog('error', 'File not selected or empty.', 'top-end');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileInfo(data) {
|
||||||
|
this.incomingFileInfo = data;
|
||||||
|
this.incomingFileData = [];
|
||||||
|
this.receiveBuffer = [];
|
||||||
|
this.receivedSize = 0;
|
||||||
|
let fileToReceiveInfo =
|
||||||
|
' From: ' +
|
||||||
|
this.incomingFileInfo.peer_name +
|
||||||
|
'\n' +
|
||||||
|
' incoming file: ' +
|
||||||
|
this.incomingFileInfo.fileName +
|
||||||
|
'\n' +
|
||||||
|
' size: ' +
|
||||||
|
this.bytesToSize(this.incomingFileInfo.fileSize) +
|
||||||
|
'\n' +
|
||||||
|
' type: ' +
|
||||||
|
this.incomingFileInfo.fileType;
|
||||||
|
console.log(fileToReceiveInfo);
|
||||||
|
this.userLog('info', fileToReceiveInfo, 'top-end');
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFileData() {
|
||||||
|
console.log(
|
||||||
|
'Send file ' +
|
||||||
|
this.fileToSend.name +
|
||||||
|
' size ' +
|
||||||
|
this.bytesToSize(this.fileToSend.size) +
|
||||||
|
' type ' +
|
||||||
|
this.fileToSend.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.sendInProgress = true;
|
||||||
|
|
||||||
|
sendFileInfo.innerHTML =
|
||||||
|
'File name: ' +
|
||||||
|
this.fileToSend.name +
|
||||||
|
'<br>' +
|
||||||
|
'File type: ' +
|
||||||
|
this.fileToSend.type +
|
||||||
|
'<br>' +
|
||||||
|
'File size: ' +
|
||||||
|
this.bytesToSize(this.fileToSend.size) +
|
||||||
|
'<br>';
|
||||||
|
|
||||||
|
sendFileDiv.style.display = 'inline';
|
||||||
|
sendProgress.max = this.fileToSend.size;
|
||||||
|
|
||||||
|
this.fileReader = new FileReader();
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
this.fileReader.addEventListener('error', (err) => console.error('fileReader error', err));
|
||||||
|
this.fileReader.addEventListener('abort', (e) => console.log('fileReader aborted', e));
|
||||||
|
this.fileReader.addEventListener('load', (e) => {
|
||||||
|
if (!this.sendInProgress) return;
|
||||||
|
|
||||||
|
this.sendFSData(e.target.result);
|
||||||
|
offset += e.target.result.byteLength;
|
||||||
|
|
||||||
|
sendProgress.value = offset;
|
||||||
|
sendFilePercentage.innerHTML = 'Send progress: ' + ((offset / this.fileToSend.size) * 100).toFixed(2) + '%';
|
||||||
|
|
||||||
|
// send file completed
|
||||||
|
if (offset === this.fileToSend.size) {
|
||||||
|
this.sendInProgress = false;
|
||||||
|
sendFileDiv.style.display = 'none';
|
||||||
|
userLog('success', 'The file ' + this.fileToSend.name + ' was sent successfully.', 'top-end');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < this.fileToSend.size) readSlice(offset);
|
||||||
|
});
|
||||||
|
const readSlice = (o) => {
|
||||||
|
const slice = this.fileToSend.slice(offset, o + this.chunkSize);
|
||||||
|
this.fileReader.readAsArrayBuffer(slice);
|
||||||
|
};
|
||||||
|
readSlice(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFSData(data) {
|
||||||
|
if (data) this.socket.emit('file', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
abortFileTransfer() {
|
||||||
|
if (this.fileReader && this.fileReader.readyState === 1) {
|
||||||
|
this.fileReader.abort();
|
||||||
|
sendFileDiv.style.display = 'none';
|
||||||
|
this.sendInProgress = false;
|
||||||
|
this.socket.emit('fileAbort', {
|
||||||
|
peer_name: peer_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileAbort(data) {
|
||||||
|
this.receiveBuffer = [];
|
||||||
|
this.incomingFileData = [];
|
||||||
|
this.receivedSize = 0;
|
||||||
|
console.log(data.peer_name + ' aborted the file transfer');
|
||||||
|
userLog('info', data.peer_name + ' ⚠️ aborted the file transfer', 'top-end');
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFile(data) {
|
||||||
|
this.receiveBuffer.push(data);
|
||||||
|
this.receivedSize += data.byteLength;
|
||||||
|
// let getPercentage = ((receivedSize / this.incomingFileInfo.fileSize) * 100).toFixed(2);
|
||||||
|
// console.log("Received progress: " + getPercentage + "%");
|
||||||
|
if (this.receivedSize === this.incomingFileInfo.fileSize) {
|
||||||
|
this.incomingFileData = this.receiveBuffer;
|
||||||
|
this.receiveBuffer = [];
|
||||||
|
this.endFileDownload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endFileDownload() {
|
||||||
|
this.sound('download');
|
||||||
|
|
||||||
|
// save received file into Blob
|
||||||
|
const blob = new Blob(this.incomingFileData);
|
||||||
|
const file = this.incomingFileInfo.fileName;
|
||||||
|
|
||||||
|
this.incomingFileData = [];
|
||||||
|
|
||||||
|
// if file is image, show the preview
|
||||||
|
if (isImageURL(this.incomingFileInfo.fileName)) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
Swal.fire({
|
||||||
|
allowOutsideClick: false,
|
||||||
|
background: swalBackground,
|
||||||
|
position: 'center',
|
||||||
|
title: 'Received file',
|
||||||
|
text: this.incomingFileInfo.fileName + ' size ' + this.bytesToSize(this.incomingFileInfo.fileSize),
|
||||||
|
imageUrl: e.target.result,
|
||||||
|
imageAlt: 'mirotalksfu-file-img-download',
|
||||||
|
showDenyButton: true,
|
||||||
|
confirmButtonText: `Save`,
|
||||||
|
denyButtonText: `Cancel`,
|
||||||
|
showClass: {
|
||||||
|
popup: 'animate__animated animate__fadeInDown',
|
||||||
|
},
|
||||||
|
hideClass: {
|
||||||
|
popup: 'animate__animated animate__fadeOutUp',
|
||||||
|
},
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) this.saveBlobToFile(blob, file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// blob where is stored downloaded file
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
} else {
|
||||||
|
// not img file
|
||||||
|
Swal.fire({
|
||||||
|
allowOutsideClick: false,
|
||||||
|
background: swalBackground,
|
||||||
|
position: 'center',
|
||||||
|
title: 'Received file',
|
||||||
|
text: this.incomingFileInfo.fileName + ' size ' + this.bytesToSize(this.incomingFileInfo.fileSize),
|
||||||
|
showDenyButton: true,
|
||||||
|
confirmButtonText: `Save`,
|
||||||
|
denyButtonText: `Cancel`,
|
||||||
|
showClass: {
|
||||||
|
popup: 'animate__animated animate__fadeInDown',
|
||||||
|
},
|
||||||
|
hideClass: {
|
||||||
|
popup: 'animate__animated animate__fadeOutUp',
|
||||||
|
},
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) this.saveBlobToFile(blob, file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveBlobToFile(blob, file) {
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = url;
|
||||||
|
a.download = file;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesToSize(bytes) {
|
||||||
|
let sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
if (bytes == 0) return '0 Byte';
|
||||||
|
let i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||||
|
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
// ####################################################
|
// ####################################################
|
||||||
// ROOM ACTION
|
// ROOM ACTION
|
||||||
// ####################################################
|
// ####################################################
|
||||||
|
|||||||
ثنائية
public/sounds/download.wav
Normal file
ثنائية
public/sounds/download.wav
Normal file
ملف ثنائي غير معروض.
@@ -252,6 +252,19 @@ io.on('connection', (socket) => {
|
|||||||
roomList.get(socket.room_id).broadCast(socket.id, 'updatePeerInfo', data);
|
roomList.get(socket.room_id).broadCast(socket.id, 'updatePeerInfo', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('fileInfo', (data) => {
|
||||||
|
log.debug('Send File Info', data);
|
||||||
|
roomList.get(socket.room_id).broadCast(socket.id, 'fileInfo', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('file', (data) => {
|
||||||
|
roomList.get(socket.room_id).broadCast(socket.id, 'file', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('fileAbort', (data) => {
|
||||||
|
roomList.get(socket.room_id).broadCast(socket.id, 'fileAbort', data);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('wbCanvasToJson', (data) => {
|
socket.on('wbCanvasToJson', (data) => {
|
||||||
// let objLength = bytesToSize(Object.keys(data).length);
|
// let objLength = bytesToSize(Object.keys(data).length);
|
||||||
// log.debug('Send Whiteboard canvas JSON', { length: objLength });
|
// log.debug('Send Whiteboard canvas JSON', { length: objLength });
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم