Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
RTCMultiConnection/demos/file-sharing.html
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
769 lines (627 sloc)
26.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en" itemscope itemtype="http://schema.org/WebPage"> | |
<head> | |
<title>File Sharing using RTCMultiConnection</title> | |
<script> | |
if(!location.hash.replace('#', '').length) { | |
location.href = location.href.split('#')[0] + '#' + (Math.random() * 100).toString().replace('.', ''); | |
location.reload(); | |
} | |
</script> | |
<meta name="description" content="Share files with multiple users using RTCMultiConnection" /> | |
<meta name="keywords" content="WebRTC,RTCMultiConnection,Demos,Experiments,Samples,Examples" /> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | |
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300,600' rel='stylesheet' type='text/css'> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
} | |
::-webkit-scrollbar { | |
height: 0; | |
overflow: visible; | |
width: 10px; | |
border-left:1px solid rgb(229, 229, 229); | |
} | |
::-webkit-scrollbar-thumb { | |
background-color: rgba(0, 0, 0, .2); | |
background-clip: padding-box; | |
min-height: 28px; | |
padding: 100px 0 0; | |
box-shadow: inset 1px 1px 0 rgba(0,0,0,.1),inset 0 -1px 0 rgba(0,0,0,.07); | |
border-width: 1px 1px 1px 6px; | |
} | |
::-webkit-scrollbar-button { | |
height: 0; | |
width: 0; | |
} | |
::-webkit-scrollbar-track { | |
background-clip: padding-box; | |
border: solid transparent; | |
border-width: 0 0 0 4px; | |
} | |
::-webkit-scrollbar-corner { | |
background: transparent; | |
} | |
html, body { | |
background-color: black; | |
font-family: 'Open Sans', 'Segoe UI Light','Segoe UI',Verdana,Arial; | |
font-size: 1.1em; | |
overflow: hidden; | |
} | |
.overlay { | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: black; | |
z-index: 1; | |
} | |
.btn-select-file { | |
position: absolute; | |
z-index: 2; | |
left: 40%; | |
top: 40%; | |
width: 100px; | |
height: 100px; | |
-webkit-user-select: none; | |
border-radius: 50%; | |
text-shadow: 2px 2px white; | |
border: 1px solid #1B1B1B; | |
background-color: #D5D8D8; | |
cursor: pointer; | |
background-image: url(https://www.webrtc-experiment.com/images/btn-select-file.png); | |
outline: none; | |
} | |
.btn-select-file:hover { | |
background-color: #BBBBBB; | |
} | |
.btn-select-file:active { | |
background-color: #7F7B7B; | |
} | |
iframe { | |
position: absolute; | |
width: 80%; | |
height: 96%; | |
top: 87px; | |
left: 0; | |
right: 20; | |
bottom: 0; | |
border: 0; | |
outline: 0; | |
z-index: 5; | |
display: none; | |
} | |
#logs { | |
position: absolute; | |
background: white; | |
border-left: 1px solid black; | |
width: 20%; | |
top: 87px; | |
right: 0; | |
bottom: 0; | |
z-index: 6; | |
font-size: medium; | |
overflow: auto; | |
height: 100%; | |
border-top-left-radius: 5px; | |
font-size: 22px; | |
} | |
#logs p { | |
padding: 2px 4px; | |
border-bottom: 1px solid black; | |
} | |
header { | |
position: absolute; | |
text-align: center; | |
width: 100%; | |
height: 61px; | |
top: 0; | |
right: 0; | |
left: 0; | |
z-index: 7; | |
padding-top: 8px; | |
background: #EFEBEB; | |
border-bottom: 1px solid white; | |
} | |
#number-of-users { | |
position: absolute; | |
right: 60px; | |
top: 16px; | |
width: 36px; | |
height: 30px; | |
-webkit-user-select: none; | |
border-radius: 5px; | |
border: 1px solid #FFFFFF; | |
background-color: #E83930; | |
z-index: 8; | |
text-align: center; | |
padding-top: 6px; | |
color: white; | |
} | |
#room-id { | |
outline: none; | |
border: 1px solid black; | |
padding: 1px 3px; | |
font-size: 100%; | |
background: rgba(255, 255, 255, 0.28); | |
border-top-left-radius: 5px; | |
border-bottom-left-radius: 5px; | |
} | |
#join-room { | |
border: 1px solid black; | |
background: rgba(255, 255, 255, 0.28); | |
padding: 1px 3px; | |
border-left: 0; | |
font-size: 100%; | |
border-top-right-radius: 5px; | |
border-bottom-right-radius: 5px; | |
} | |
.android-app-icon { | |
background-repeat: no-repeat; | |
background-position: center center; | |
background-size: 64px 64px; | |
background-image: url(https://www.webrtc-experiment.com/images/android-app-icon.png); | |
width: 64px; | |
height: 64px; | |
position: absolute; | |
bottom: 5px; | |
left: 15px; | |
z-index: 9999; | |
} | |
.chrome-web-store-icon { | |
background-repeat: no-repeat; | |
background-position: center center; | |
background-size: 64px 64px; | |
background-image: url(https://www.webrtc-experiment.com/images/chrome-web-store-icon.png); | |
width: 64px; | |
height: 64px; | |
position: absolute; | |
bottom: 5px; | |
left: 90px; | |
z-index: 9999; | |
} | |
@media all and (max-width: 500px) { | |
#logs { | |
position: fixed; | |
bottom: 0; | |
left: 0; | |
width: 100%; | |
top: auto; | |
height: 40%; | |
border-left: 0; | |
border-top: 1px solid black; | |
font-size: 18px; | |
} | |
.btn-select-file { | |
top: 20%; | |
left: 40%; | |
} | |
#room-id { | |
width: 40%; | |
} | |
.ribbon{ | |
height: 150%; | |
width: 80%; | |
} | |
.ribbon h1 { | |
font-size: 20px!important; | |
} | |
#number-of-users { | |
right: 50px; | |
} | |
.android-app-icon, .chrome-web-store-icon { | |
display: none; | |
} | |
} | |
hr {border:0;} | |
</style> | |
</head> | |
<body> | |
<div class="overlay"></div> | |
<button class="btn-select-file"></button> | |
<iframe></iframe> | |
<header> | |
<div class="ribbon"><div class="ribbon-stitches-top"></div><strong class="ribbon-content"><h1> | |
<input type="text" id="room-id" placeholder="room-id"><button id="join-room">Join</button> | |
</h1></strong><div class="ribbon-stitches-bottom"></div></div> | |
</header> | |
<div id="number-of-users" title="Number of online users.">0</div> | |
<div id="logs"> | |
<p> | |
Peer-to-Peer (private) file sharing. | |
</p> | |
<p> | |
You can share/receive files from any platform/device e.g. destkop operating systems, Android, iOS etc. | |
</p> | |
<p> | |
Create or join a room & select file using "+" button. | |
</p> | |
</div> | |
<a class="chrome-web-store-icon" href="https://chrome.google.com/webstore/detail/webrtc-file-sharing/nbnncbdkhpmbnkfngmkdbepoemljbnfo" target="_blank" title="Install Chrome Desktop Extension"></a> | |
<a class="android-app-icon" href="https://play.google.com/store/apps/details?id=com.webrtc.experiment" target="_blank" title="Install Android App"></a> | |
<script> | |
// to get STUN/TURN URIs from xirsys.com | |
window.getExternalIceServers = true; | |
</script> | |
<script src="/dist/RTCMultiConnection.min.js"></script> | |
<script src="/node_modules/webrtc-adapter/out/adapter.js"></script> | |
<script src="/socket.io/socket.io.js"></script> | |
<script src="/node_modules/fbr/FileBufferReader.js"></script> | |
<script> | |
window.onerror = console.error = function() { | |
var error = JSON.stringify(arguments); | |
if(error.indexOf('Blocked a frame with origin') !== -1) { | |
return; | |
} | |
alert('Error:\n' + error); | |
}; | |
// Muaz Khan - https://github.com/muaz-khan | |
// MIT License - https://www.WebRTC-Experiment.com/licence/ | |
// Source Code - https://github.com/muaz-khan/RTCMultiConnection | |
window.addEventListener('load', function() { | |
if (window.localStorage.getItem('room-id')) { | |
document.getElementById('room-id').value = window.localStorage.getItem('room-id'); | |
} else { | |
document.getElementById('room-id').value = (Math.random() * 100).toString().replace('.', ''); | |
} | |
if(location.hash.length > 1) { | |
document.getElementById('room-id').value = location.hash.replace('#', ''); | |
} | |
document.getElementById('join-room').onclick = document.querySelector('.btn-select-file').onclick = function() { | |
document.getElementById('join-room').disabled = true; | |
document.getElementById('room-id').disabled = true; | |
var roomId = document.getElementById('room-id').value; | |
window.localStorage.setItem('room-id', roomId); | |
joinARoom(roomId); | |
}; | |
if(location.hash.length > 1) { | |
document.getElementById('join-room').disabled = true; | |
document.getElementById('room-id').disabled = true; | |
joinARoom(location.hash.replace('#', '')); | |
} | |
}); | |
var btnSelectFile = document.querySelector('.btn-select-file'); | |
var connection; | |
function joinARoom(roomId) { | |
var iframe = document.querySelector('iframe'); | |
btnSelectFile.onclick = function(file) { | |
if(file && (file instanceof File || file instanceof Blob) && file.size) { | |
previewFile(file); | |
onFileSelected(file); | |
return; | |
} | |
var fileSelector = new FileSelector(); | |
fileSelector.selectSingleFile(function(file) { | |
previewFile(file); | |
onFileSelected(file); | |
}); | |
}; | |
var lastSelectedFile; | |
var room_id = ''; | |
// 60k -- assuming receiving client is chrome | |
var chunk_size = 60 * 1000; | |
function setupWebRTCConnection() { | |
if (connection) { | |
return; | |
} | |
// www.RTCMultiConnection.org/docs/ | |
connection = new RTCMultiConnection(); | |
// to make sure, "connection-reconnect" doesn't sends files again | |
connection.fileReceived = {}; | |
// by default, socket.io server is assumed to be deployed on your own URL | |
connection.socketURL = '/'; | |
// comment-out below line if you do not have your own socket.io server | |
// connection.socketURL = 'https://muazkhan.com:9001/'; | |
connection.socketMessageEvent = 'file-sharing-demo'; | |
connection.chunkSize = chunk_size; | |
connection.sdpConstraints.mandatory = { | |
OfferToReceiveAudio: false, | |
OfferToReceiveVideo: false | |
}; | |
connection.enableFileSharing = true; | |
if (room_id && room_id.length) { | |
connection.userid = room_id; | |
} | |
connection.channel = connection.sessionid = roomId; | |
connection.session = { | |
data: true, | |
// oneway: true --- to make it one-to-many | |
}; | |
// https://www.rtcmulticonnection.org/docs/iceServers/ | |
// use your own TURN-server here! | |
connection.iceServers = [{ | |
'urls': [ | |
'stun:stun.l.google.com:19302', | |
'stun:stun1.l.google.com:19302', | |
'stun:stun2.l.google.com:19302', | |
'stun:stun.l.google.com:19302?transport=udp', | |
] | |
}]; | |
connection.filesContainer = logsDiv; | |
connection.connectedWith = {}; | |
connection.onmessage = function(event) { | |
if(event.data.doYouWannaReceiveThisFile) { | |
if(!connection.fileReceived[event.data.fileName]) { | |
connection.send({ | |
yesIWannaReceive:true, | |
fileName: event.data.fileName | |
}); | |
} | |
} | |
if(event.data.yesIWannaReceive && !!lastSelectedFile) { | |
connection.shareFile(lastSelectedFile, event.userid); | |
} | |
}; | |
connection.onopen = function(e) { | |
try { | |
chrome.power.requestKeepAwake('display'); | |
} | |
catch(e) {} | |
if (connection.connectedWith[e.userid]) return; | |
connection.connectedWith[e.userid] = true; | |
var message = '<b>' + e.userid + '</b><br>is connected.'; | |
appendLog(message); | |
if (!lastSelectedFile) return; | |
// already shared the file | |
var file = lastSelectedFile; | |
setTimeout(function() { | |
appendLog('Sharing file<br><b>' + file.name + '</b><br>Size: <b>' + bytesToSize(file.size) + '<b><br>With <b>' + connection.getAllParticipants().length + '</b> users'); | |
connection.send({ | |
doYouWannaReceiveThisFile: true, | |
fileName: file.size + file.name | |
}); | |
}, 500); | |
}; | |
connection.onclose = function(e) { | |
incrementOrDecrementUsers(); | |
if (connection.connectedWith[e.userid]) return; | |
appendLog('Data connection has been closed between you and <b>' + e.userid + '</b>. Re-Connecting..'); | |
connection.join(roomId); | |
}; | |
connection.onerror = function(e) { | |
if (connection.connectedWith[e.userid]) return; | |
appendLog('Data connection failed. between you and <b>' + e.userid + '</b>. Retrying..'); | |
}; | |
setFileProgressBarHandlers(connection); | |
connection.onUserStatusChanged = function(user) { | |
incrementOrDecrementUsers(); | |
}; | |
connection.onleave = function(user) { | |
user.status = 'offline'; | |
connection.onUserStatusChanged(user); | |
incrementOrDecrementUsers(); | |
}; | |
var message = 'Connecting room:<br><b>' + connection.channel + '</b>'; | |
appendLog(message); | |
connection.openOrJoin(connection.channel, function(isRoomExist, roomid) { | |
var message = 'Successfully connected to room: <b>' + roomid + '</b><hr>Other users can join you on iPhone/Android using "' + roomid + '" or desktop (Windows/MacOSX/Ubuntu) users can join using this (secure/private) URL: <a href="./file-sharing.html#' + roomid + '" target="_blank">file-sharing.html#' + roomid + '</a>'; | |
// if (isRoomEists) { } | |
appendLog(message); | |
if(document.getElementById('room-id')) { | |
if(innerWidth > 500) { | |
document.getElementById('room-id').parentNode.innerHTML = 'Joined room: ' + roomid; | |
} | |
else { | |
document.getElementById('room-id').parentNode.innerHTML = 'Joined room:<br>' + roomid; | |
} | |
} | |
connection.getSocket(function(socket) { | |
socket.on('disconnect', function() { | |
appendLog('Seems disconnected.', 'red'); | |
}); | |
socket.on('connect', function() { | |
location.reload(); | |
}); | |
socket.on('error', function() { | |
location.reload(); | |
}); | |
}); | |
window.addEventListener('offline', function() { | |
appendLog('Seems disconnected.', 'red'); | |
}, false); | |
}); | |
window.connection = connection; | |
} | |
function setFileProgressBarHandlers(connection) { | |
var progressHelper = {}; | |
// www.RTCMultiConnection.org/docs/onFileStart/ | |
connection.onFileStart = function(file) { | |
if (connection.fileReceived[file.size + file.name]) return; | |
var div = document.createElement('div'); | |
div.style.borderBottom = '1px solid black'; | |
div.style.padding = '2px 4px'; | |
div.id = file.uuid; | |
var message = ''; | |
if (file.userid == connection.userid) { | |
message += 'Sharing with:' + file.remoteUserId; | |
} else { | |
message += 'Receiving from:' + file.userid; | |
} | |
message += '<br><b>' + file.name + '</b>.'; | |
message += '<br>Size: <b>' + bytesToSize(file.size) + '</b>'; | |
message += '<br><label>0%</label> <progress></progress>'; | |
if(file.userid !== connection.userid) { | |
message += '<br><button id="resend">Receive Again?</button>'; | |
} | |
div.innerHTML = message; | |
connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild); | |
if(file.userid !== connection.userid && div.querySelector('#resend')) { | |
div.querySelector('#resend').onclick = function(e) { | |
e.preventDefault(); | |
this.onclick = function() {}; | |
if(connection.fileReceived[file.size + file.name]) { | |
delete connection.fileReceived[file.size + file.name]; | |
} | |
connection.send({ | |
yesIWannaReceive: true, | |
fileName: file.name | |
}, file.userid); | |
div.parentNode.removeChild(div); | |
}; | |
} | |
if (!file.remoteUserId) { | |
progressHelper[file.uuid] = { | |
div: div, | |
progress: div.querySelector('progress'), | |
label: div.querySelector('label') | |
}; | |
progressHelper[file.uuid].progress.max = file.maxChunks; | |
return; | |
} | |
if (!progressHelper[file.uuid]) { | |
progressHelper[file.uuid] = {}; | |
} | |
progressHelper[file.uuid][file.remoteUserId] = { | |
div: div, | |
progress: div.querySelector('progress'), | |
label: div.querySelector('label') | |
}; | |
progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks; | |
}; | |
// www.RTCMultiConnection.org/docs/onFileProgress/ | |
connection.onFileProgress = function(chunk) { | |
if (connection.fileReceived[chunk.size + chunk.name]) return; | |
var helper = progressHelper[chunk.uuid]; | |
if (!helper) { | |
return; | |
} | |
if (chunk.remoteUserId) { | |
helper = progressHelper[chunk.uuid][chunk.remoteUserId]; | |
if (!helper) { | |
return; | |
} | |
} | |
helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max; | |
updateLabel(helper.progress, helper.label); | |
}; | |
// www.RTCMultiConnection.org/docs/onFileEnd/ | |
connection.onFileEnd = function(file) { | |
if (connection.fileReceived[file.size + file.name]) return; | |
var div = document.getElementById(file.uuid); | |
if (div) { | |
div.parentNode.removeChild(div); | |
} | |
if (file.remoteUserId === connection.userid) { | |
previewFile(file); | |
connection.fileReceived[file.size + file.name] = file; | |
var message = 'Successfully received file'; | |
message += '<br><b>' + file.name + '</b>.'; | |
message += '<br>Size: <b>' + bytesToSize(file.size) + '</b>.'; | |
message += '<br><a href="' + file.url + '" target="_blank" download="' + file.name + '">Download</a>'; | |
var div = appendLog(message); | |
return; | |
} | |
var message = 'Successfully shared file'; | |
message += '<br><b>' + file.name + '</b>.'; | |
message += '<br>With: <b>' + file.remoteUserId + '</b>.'; | |
message += '<br>Size: <b>' + bytesToSize(file.size) + '</b>.'; | |
appendLog(message); | |
}; | |
function updateLabel(progress, label) { | |
if (progress.position === -1) { | |
return; | |
} | |
var position = +progress.position.toFixed(2).split('.')[1] || 100; | |
label.innerHTML = position + '%'; | |
} | |
} | |
function bytesToSize(bytes) { | |
var k = 1000; | |
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; | |
if (bytes === 0) { | |
return '0 Bytes'; | |
} | |
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10); | |
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; | |
} | |
function onFileSelected(file) { | |
var innerHTML = 'You selected:<br><b>' + file.name + '</b><br>Size: <b>' + bytesToSize(file.size) + '</b>'; | |
appendLog(innerHTML); | |
lastSelectedFile = file; | |
if (connection) { | |
connection.send({ | |
doYouWannaReceiveThisFile: true, | |
fileName: file.size + file.name | |
}); | |
} | |
} | |
var numberOfUsers = document.getElementById('number-of-users'); | |
function incrementOrDecrementUsers() { | |
numberOfUsers.innerHTML = connection ? connection.getAllParticipants().length : 0; | |
} | |
var logsDiv = document.getElementById('logs'); | |
function appendLog(html, color) { | |
var div = document.createElement('div'); | |
div.innerHTML = '<p>' + html + '</p>'; | |
logsDiv.insertBefore(div, logsDiv.firstChild); | |
if(color) { | |
div.style.color = color; | |
} | |
return div; | |
} | |
window.onerror = console.error = function() { | |
var error = JSON.stringify(arguments); | |
if(error.indexOf('Blocked a frame with origin') !== -1) { | |
return; | |
} | |
appendLog('Error:<br>' + error, 'red') | |
}; | |
function previewFile(file) { | |
btnSelectFile.style.left = '5px'; | |
btnSelectFile.style.right = 'auto'; | |
btnSelectFile.style.zIndex = 10; | |
btnSelectFile.style.top = '5px'; | |
btnSelectFile.style.outline = 'none'; | |
document.querySelector('.overlay').style.display = 'none'; | |
iframe.style.display = 'block'; | |
iframe.onload = function() { | |
Array.prototype.slice.call(iframe.contentWindow.document.body.querySelectorAll('*')).forEach(function(element) { | |
element.style.maxWidth = '100%'; | |
}); | |
if (!file.type || fileNameMatches || file.type.match(/image|video|audio|pdf/g) || iframe.src.indexOf('data:image/png') !== -1 || iframe.src.toLowerCase().search(/.png|.jpeg|.jpg|.gif/g) !== -1) { | |
iframe.contentWindow.document.body.style.textAlign = 'center'; | |
iframe.contentWindow.document.body.style.background = 'black'; | |
iframe.contentWindow.document.body.style.color = 'white'; | |
return; | |
} | |
iframe.contentWindow.document.body.style.textAlign = 'left'; | |
iframe.contentWindow.document.body.style.background = 'white'; | |
iframe.contentWindow.document.body.style.color = 'black'; | |
}; | |
var fileNameMatches = (file.name || '').toLowerCase().match(/.webm|.wav|.pdf|.txt|.js|.css|.cs|.png|.jpg|.jpeg|.gif/g); | |
if (fileNameMatches) { | |
iframe.src = URL.createObjectURL(file); | |
} else { | |
iframe.src = 'https://www.webrtc-experiment.com/images/unknown-file.png'; | |
} | |
} | |
setupWebRTCConnection(); | |
} | |
window.addEventListener('online', function() { | |
location.reload(); | |
}, false); | |
// drag-drop support | |
document.addEventListener('dragover', function(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
e.dataTransfer.dropEffect = 'copy'; | |
}, false); | |
document.addEventListener('drop', function(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
if(!e.dataTransfer.files || !e.dataTransfer.files.length) { | |
return; | |
} | |
var file = e.dataTransfer.files[0]; | |
if(!connection) { | |
document.getElementById('join-room').onclick(); | |
} | |
btnSelectFile.onclick(file); | |
}, false); | |
</script> | |
</body> | |
</html> |