Added partial file transfers in MeshMessenger

This commit is contained in:
Ylian Saint-Hilaire 2018-12-17 14:11:54 -08:00
parent de76f812d5
commit 13fffd4f02
6 changed files with 203 additions and 8 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -14,7 +14,7 @@
<div id="fileButton" class="icon4 topButton" title="Share a file" style="display:none" onclick="fileButtonClick()"></div> <div id="fileButton" class="icon4 topButton" title="Share a file" style="display:none" onclick="fileButtonClick()"></div>
<div id="camButton" class="icon2 topButton" title="Activate camera & microphone" style="display:none" onclick="camButtonClick()"></div> <div id="camButton" class="icon2 topButton" title="Activate camera & microphone" style="display:none" onclick="camButtonClick()"></div>
<div id="micButton" class="icon6 topButton" title="Activate microphone" style="display:none" onclick="micButtonClick()"></div> <div id="micButton" class="icon6 topButton" title="Activate microphone" style="display:none" onclick="micButtonClick()"></div>
<div id="hangupButton" class="icon11 topButton" title="Hang up" style="display:none" onclick="hangUpButtonClick(1)"></div> <div id="hangupButton" class="icon11 topRedButton" title="Hang up" style="display:none" onclick="hangUpButtonClick(1)"></div>
<div style="display:inline-block;width:2px"></div> <div style="display:inline-block;width:2px"></div>
<div style="padding-top:9px;padding-left:6px;font-size:20px;display:inline-block"><b>MeshMessenger<span id="xtitle"></span></b></div> <div style="padding-top:9px;padding-left:6px;font-size:20px;display:inline-block"><b>MeshMessenger<span id="xtitle"></span></b></div>
</div> </div>
@ -36,6 +36,7 @@
<div style="position:absolute;right:0;left:0;top:2.5px;text-align:center">Local</div> <div style="position:absolute;right:0;left:0;top:2.5px;text-align:center">Local</div>
<video id="localVideoCanvas" autoplay muted style="position:absolute;top:20px;left:0;width:100%;height:calc(100% - 30px);background-color:black"></video> <video id="localVideoCanvas" autoplay muted style="position:absolute;top:20px;left:0;width:100%;height:calc(100% - 30px);background-color:black"></video>
</div> </div>
<input id="uploadFileInput" type="file" style="display:none">
<script type="text/javascript"> <script type="text/javascript">
var userInputFocus = 0; var userInputFocus = 0;
var args = parseUriArgs(); var args = parseUriArgs();
@ -47,11 +48,18 @@
var webrtcconfiguration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] }; var webrtcconfiguration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
var localStream = null; var localStream = null;
var remoteStream = null; var remoteStream = null;
var senders = null; var fileUploads = [];
var fileDownloads = {};
var currentFileUpload = null;
var currentFileDownload = null;
// Set the title // Set the title
if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; } if (args.title) { QH('xtitle', ' - ' + args.title); document.title = document.title + ' - ' + args.title; }
document.addEventListener('dragover', haltEvent, false);
document.addEventListener('dragleave', haltEvent, false);
document.addEventListener('drop', fileDrop, false);
document.onkeyup = function ondockeypress(e) { document.onkeyup = function ondockeypress(e) {
if (state == 2) { if (state == 2) {
if ((e.keyCode == 8) && (userInputFocus == 0)) { if ((e.keyCode == 8) && (userInputFocus == 0)) {
@ -119,6 +127,7 @@
QE('sendButton', state == 2); QE('sendButton', state == 2);
QE('clearButton', state == 2); QE('clearButton', state == 2);
QE('xouttext', state == 2); QE('xouttext', state == 2);
QV('fileButton', state == 2);
QV('camButton', webchannel && webchannel.ok && !localStream); QV('camButton', webchannel && webchannel.ok && !localStream);
QV('micButton', webchannel && webchannel.ok && !localStream); QV('micButton', webchannel && webchannel.ok && !localStream);
QV('hangupButton', webchannel && webchannel.ok && localStream); QV('hangupButton', webchannel && webchannel.ok && localStream);
@ -195,6 +204,7 @@
function disconnect() { function disconnect() {
if (state > 0) { displayControl('Connection closed.'); } if (state > 0) { displayControl('Connection closed.'); }
if (state > 1) { setTimeout(start, 500); } if (state > 1) { setTimeout(start, 500); }
cancelAllFileTransfers();
hangUpButtonClick(0, true); // Data channel hangUpButtonClick(0, true); // Data channel
hangUpButtonClick(1, true); // Local audio/video hangUpButtonClick(1, true); // Local audio/video
hangUpButtonClick(2, true); // Remote audio/video hangUpButtonClick(2, true); // Remote audio/video
@ -208,7 +218,7 @@
if (state != 2) return; // If not in connected state, ignore this. if (state != 2) return; // If not in connected state, ignore this.
if (typeof data == 'object') { data = JSON.stringify(data); } // If this is an object, convert it to a string. if (typeof data == 'object') { data = JSON.stringify(data); } // If this is an object, convert it to a string.
if (webchannel && webchannel.ok) { if (webchannel.xoutBuffer != null) { webchannel.xoutBuffer.push(data); } else { webchannel.send(data); } } // If WebRTC channel is possible, use it or hold until we can use it. if (webchannel && webchannel.ok) { if (webchannel.xoutBuffer != null) { webchannel.xoutBuffer.push(data); } else { webchannel.send(data); } } // If WebRTC channel is possible, use it or hold until we can use it.
else { if (socket != null) { socket.send(data); } } // If a websocket channel is present, use that. else { if (socket != null) { try { socket.send(data); } catch (ex) { } } } // If a websocket channel is present, use that.
} }
// Send data over the websocket transport (WebSocket only) // Send data over the websocket transport (WebSocket only)
@ -240,6 +250,39 @@
} }
break; break;
} }
case 'file': { startFileDownload(data); break; }
case 'fileUploadCancel': { cancelFileTransfer(data.id); break; }
case 'fileUploadStart': {
if (fileDownloads[data.id]) {
currentFileDownload = fileDownloads[data.id];
currentFileDownload.data = '';
changeFileInfo(data.id, 2, 0);
continueFileDownload(data);
send({ action: 'fileUploadAck', id: data.id });
} break;
}
case 'fileUploadEnd': {
if (currentFileDownload && (currentFileDownload.id == data.id)) {
changeFileInfo(data.id, 3, 200);
currentFileDownload.done = 1;
currentFileDownload = null;
send({ action: 'fileUploadAck', id: data.id });
}
currentFileDownload = null;
break;
}
case 'fileUploadAck': {
continueFileUpload();
break;
}
case 'fileData': {
if (currentFileDownload && (currentFileDownload.id == data.id)) {
currentFileDownload.data += data.data;
changeFileInfo(data.id, 2, (currentFileDownload.data.length * 200 / currentFileDownload.size));
send({ action: 'fileUploadAck', id: data.id });
}
break;
}
default: { console.log('Unhandled object data', data); break; } default: { console.log('Unhandled object data', data); break; }
} }
} else { } else {
@ -249,7 +292,122 @@
// File sharing button // File sharing button
function fileButtonClick() { function fileButtonClick() {
var chooser = Q('uploadFileInput');
if (chooser.getAttribute("eventset") != 1) {
chooser.setAttribute("eventset", "1");
chooser.addEventListener("change", fileSelect, false);
}
chooser.value = null;
chooser.click();
}
function fileSelect() {
if (state != 2) return;
var x = Q('uploadFileInput');
if (x.files.length != 1) return;
var reader = new FileReader();
reader.onload = function (e) { this.xfile.data = e.target.result; startFileUpload(this.xfile); };
reader.xfile = x.files[0];
reader.readAsBinaryString(x.files[0]);
}
function fileDrop(e) {
haltEvent(e);
if (state != 2) return;
if (e.dataTransfer == null || e.dataTransfer.files.length != 1) return;
var reader = new FileReader();
reader.onload = function (e) { this.xfile.data = e.target.result; startFileUpload(this.xfile); };
reader.xfile = e.dataTransfer.files[0];
reader.readAsBinaryString(e.dataTransfer.files[0]);
}
function startFileUpload(file) {
if (state != 2) return;
file.id = Math.random();
fileUploads.push(file);
//console.log('XXX', file);
QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="localBubble" style="width:240px;cursor:pointer" onclick="cancelFileTransfer(\'' + file.id + '\')"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
send({ action: 'file', size: file.size, id: file.id, type: file.type, name: file.name });
if (currentFileUpload == null) continueFileUpload();
}
function startFileDownload(file) {
if (state != 2) return;
fileDownloads[file.id] = file;
QA('xmsg', '<div style="clear:both"></div><div id="FILEUP-' + file.id + '" class="remoteBubble" style="width:240px;cursor:pointer"><div id="FILEUP-ICON-' + file.id + '" class="fileicon" style="float:left;width:32px;height:32px"></div><div><div id="FILEUP-NAME-' + file.id + '" style="height:16px;overflow:hidden">' + file.name + '</div><div style="width:200px;background-color:lightgray;margin-left:32px;border-radius:3px;margin-top:3px;height:11px"><div id="FILEUP-PROGRESS-' + file.id + '" style="width:0px;background-color:green;border-radius:3px;height:11px">&nbsp;</div></div></div></div>');
Q('xmsg').scrollTop = Q('xmsg').scrollHeight;
}
// Change the file icon and progress
function changeFileInfo(id, icon, progress, progressColor) {
if (icon) {
Q('FILEUP-ICON-' + id).classList.remove('fileicon');
Q('FILEUP-ICON-' + id).classList.remove('fileiconx');
Q('FILEUP-ICON-' + id).classList.remove('fileicontransfer');
Q('FILEUP-ICON-' + id).classList.remove('fileicondone');
Q('FILEUP-ICON-' + id).classList.add(['fileicon', 'fileiconx', 'fileicontransfer', 'fileicondone'][icon]);
}
if (progress) { QS('FILEUP-PROGRESS-' + id)['width'] = progress + 'px'; }
if (progressColor) { QS('FILEUP-PROGRESS-' + id)['background-color'] = progressColor; }
}
function cancelFileTransfer(id) {
if ((currentFileUpload != null) && (currentFileUpload.id == id)) { currentFileUpload = null; }
if ((currentFileDownload != null) && (currentFileDownload.id == id)) { currentFileDownload = null; }
var found = false;
if (fileDownloads[id] && (fileDownloads[id].done != 1)) {
delete fileDownloads[id];
found = true;
} else {
for (var i in fileUploads) {
if (fileUploads[i].id == id) {
send({ action: 'fileUploadCancel', id: id });
fileUploads.splice(i, 1);
found = true;
break;
}
}
}
if (found) { changeFileInfo(id, 1, 200, 'gray'); } // Only cancel a file if it was in the file queue.
}
function cancelAllFileTransfers() {
for (var i in fileDownloads) { cancelFileTransfer(fileDownloads[i].id); }
for (var i in fileUploads) { cancelFileTransfer(fileUploads[i].id); }
}
function continueFileUpload() {
if (currentFileUpload == null) {
// Select the next file to upload
if (fileUploads.length == 0) { return; } // Nothing to do
currentFileUpload = fileUploads[0];
currentFileUpload.ptr = 0;
// Indicate that we are sending this file
send({ action: 'fileUploadStart', size: currentFileUpload.size, id: currentFileUpload.id, type: currentFileUpload.type, name: currentFileUpload.name });
} else {
if (currentFileUpload.size <= currentFileUpload.ptr) {
// If we are done, send the end marker
send({ action: 'fileUploadEnd', size: currentFileUpload.size, id: currentFileUpload.id, type: currentFileUpload.type, name: currentFileUpload.name });
changeFileInfo(currentFileUpload.id, 3, 200);
fileUploads.splice(0, 1);
currentFileUpload = null;
continueFileUpload(); // Send the next file
} else {
// Send the next block
var nextBlockLen = Math.min(4000, currentFileUpload.data.length - currentFileUpload.ptr);
var data = currentFileUpload.data.substring(currentFileUpload.ptr, currentFileUpload.ptr + nextBlockLen);
send({ action: 'fileData', id: currentFileUpload.id, data: data });
currentFileUpload.ptr += nextBlockLen;
changeFileInfo(currentFileUpload.id, 0, (currentFileUpload.ptr * 200 / currentFileUpload.size));
}
}
}
function continueFileDownload(msg) {
send({ action: 'fileUploadAck', id: msg.id });
} }
// Camera button // Camera button
@ -337,9 +495,9 @@
socket.onclose = function () { disconnect(); } socket.onclose = function () { disconnect(); }
socket.onmessage = function (msg) { socket.onmessage = function (msg) {
if ((state < 2) && (typeof msg.data == 'string') && (msg.data == 'c')) { if ((state < 2) && (typeof msg.data == 'string') && (msg.data == 'c')) {
hangUpButtonClick(0); hangUpButtonClick(0, true);
hangUpButtonClick(1); hangUpButtonClick(1, true);
hangUpButtonClick(2); hangUpButtonClick(2, true);
displayControl('Connected.'); displayControl('Connected.');
state = 2; state = 2;
updateControls(); updateControls();

View File

@ -1,7 +1,7 @@
.topButton { .topButton {
cursor: pointer; cursor: pointer;
border: none; border: none;
margin: 3px; margin: 2px;
float: right; float: right;
border-radius: 3px; border-radius: 3px;
height: 32px; height: 32px;
@ -12,6 +12,19 @@
background-color: lightgray; background-color: lightgray;
} }
.topRedButton {
cursor: pointer;
border: none;
margin: 2px;
float: right;
border-radius: 3px;
height: 32px;
width: 32px;
}
.topRedButton:hover {
background-color: orangered;
}
.remoteBubble { .remoteBubble {
background-color: #00cc99; background-color: #00cc99;
@ -87,5 +100,29 @@
.icon11 { .icon11 {
background: url(../images/messenger32.png) -320px 0px; background: url(../images/messenger32.png) -320px 0px;
background-color: gray; background-color: orange;
}
.fileicon {
background: url(../images/messenger32.png) -96px 0px;
height: 32px;
width: 32px;
}
.fileiconx {
background: url(../images/messenger32.png) -352px 0px;
height: 32px;
width: 32px;
}
.fileicontransfer {
background: url(../images/messenger32.png) -288px 0px;
height: 32px;
width: 32px;
}
.fileicondone {
background: url(../images/messenger32.png) -256px 0px;
height: 32px;
width: 32px;
} }