- commit
- df5b534ec16e4b909191bbe0152f0d0c713715eb
- parent
- c70cf4759cc9c03e36362c13844dfe312f2f060d
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2020-04-03 19:23
use js modules
Diffstat
M | www/index.html | 3 | +-- |
M | www/rtc.js | 418 | ++++++++++++++++++++++++++++++------------------------------ |
M | www/signal.js | 53 | ++++++++++++++++++++++------------------------------- |
3 files changed, 232 insertions, 242 deletions
diff --git a/www/index.html b/www/index.html
@@ -36,7 +36,6 @@ 36 36 <button>Send</button> 37 37 </form> 38 38 </div>39 -1 <script src="signal.js"></script>40 -1 <script src="rtc.js"></script>-1 39 <script type="module" src="rtc.js"></script> 41 40 </body> 42 41 </html>
diff --git a/www/rtc.js b/www/rtc.js
@@ -1,244 +1,244 @@1 -1 /* global signal */2 1 /* eslint no-console: "off" */3 -1 (function() {4 -1 // https://webrtc.github.io/samples/5 -1 // https://www.html5rocks.com/en/tutorials/webrtc/basics/6 27 -1 // ICE -- networking information8 -1 // offer/answer -- media stream information-1 3 import * as signal from './signal.js'; 9 410 -1 var room = location.hash.substr(1);11 -1 var queue = signal.randomString(10);12 -1 var queuePassword = signal.randomString(10);13 -1 console.log('own queue', queue);-1 5 // https://webrtc.github.io/samples/ -1 6 // https://www.html5rocks.com/en/tutorials/webrtc/basics/ 14 715 -1 var container = document.querySelector('.rtc-videos');16 -1 var cons = {};-1 8 // ICE -- networking information -1 9 // offer/answer -- media stream information 17 1018 -1 var localVideo = document.querySelector('.rtc-videos video.local');19 -1 localVideo.srcObject = new MediaStream();-1 11 var room = location.hash.substr(1); -1 12 var queue = signal.randomString(10); -1 13 var queuePassword = signal.randomString(10); -1 14 console.log('own queue', queue); 20 1521 -1 var closeConnection = function(sender) {22 -1 if (sender in cons) {23 -1 cons[sender].video.remove();24 -1 cons[sender].con.close();25 -1 delete cons[sender];26 -1 }27 -1 };-1 16 var container = document.querySelector('.rtc-videos'); -1 17 var cons = {}; 28 1829 -1 var getConnection = function(sender) {30 -1 if (sender in cons) {31 -1 return cons[sender].con;32 -1 }-1 19 var localVideo = document.querySelector('.rtc-videos video.local'); -1 20 localVideo.srcObject = new MediaStream(); 33 2134 -1 var video = document.createElement('video');35 -1 video.autoplay = true;36 -1 container.append(video);-1 22 var closeConnection = function(sender) { -1 23 if (sender in cons) { -1 24 cons[sender].video.remove(); -1 25 cons[sender].con.close(); -1 26 delete cons[sender]; -1 27 } -1 28 }; 37 2938 -1 var con = new RTCPeerConnection({39 -1 iceServers: [{urls: 'stun:ce9e.org:3478'}],40 -1 });41 -1 con.addEventListener('icecandidate', function(event) {42 -1 signal.post(sender, {sender: queue, type: 'candidate', data: event.candidate});43 -1 });44 -1 con.addEventListener('negotiationneeded', function() {45 -1 makeOffer(sender);46 -1 });47 -1 con.addEventListener('iceconnectionstatechange', function() {48 -1 if (con.iceConnectionState === 'disconnected') {49 -1 closeConnection(sender);50 -1 }51 -1 });52 -1 con.addEventListener('track', function(event) {53 -1 // TODO: maybe check if already equal?54 -1 video.srcObject = event.streams[0];55 -1 });-1 30 var getConnection = function(sender) { -1 31 if (sender in cons) { -1 32 return cons[sender].con; -1 33 } 56 3457 -1 localVideo.srcObject.getTracks().forEach(track => {58 -1 con.addTrack(track, localVideo.srcObject);59 -1 });60 -161 -1 cons[sender] = {62 -1 'con': con,63 -1 'video': video,64 -1 };-1 35 var video = document.createElement('video'); -1 36 video.autoplay = true; -1 37 container.append(video); 65 3866 -1 return con;67 -1 };68 -169 -1 var makeOffer = function(sender) {70 -1 if (sender !== queue) {71 -1 var con = getConnection(sender);72 -1 con.createOffer().then(offer => {73 -1 con.setLocalDescription(offer).then(() => {74 -1 signal.post(sender, {sender: queue, type: 'offer', data: offer});75 -1 });76 -1 });77 -1 }78 -1 };79 -180 -1 var handleOffer = function(sender, offer) {81 -1 var con = getConnection(sender);82 -1 con.setRemoteDescription(offer).then(() => {83 -1 con.createAnswer().then(answer => {84 -1 con.setLocalDescription(answer).then(() => {85 -1 signal.post(sender, {sender: queue, type: 'answer', data: answer});86 -1 });87 -1 });88 -1 });89 -1 };90 -191 -1 var handleAnswer = function(sender, answer) {92 -1 if (sender in cons) {93 -1 var con = cons[sender].con;94 -1 con.setRemoteDescription(answer);95 -1 }96 -1 };97 -198 -1 var handleCandidate = function(sender, candidate) {99 -1 if (sender in cons) {100 -1 var con = cons[sender].con;101 -1 con.addIceCandidate(candidate);102 -1 }103 -1 };104 -1105 -1 signal.listen(queue + ':' + queuePassword, function(msg) {106 -1 if (msg.type === 'offer') {107 -1 handleOffer(msg.sender, msg.data);108 -1 } else if (msg.type === 'answer') {109 -1 handleAnswer(msg.sender, msg.data);110 -1 } else if (msg.type === 'candidate') {111 -1 handleCandidate(msg.sender, msg.data);112 -1 } else {113 -1 console.log('unknown message', msg);-1 39 var con = new RTCPeerConnection({ -1 40 iceServers: [{urls: 'stun:ce9e.org:3478'}], -1 41 }); -1 42 con.addEventListener('icecandidate', function(event) { -1 43 signal.post(sender, {sender: queue, type: 'candidate', data: event.candidate}); -1 44 }); -1 45 con.addEventListener('negotiationneeded', function() { -1 46 makeOffer(sender); -1 47 }); -1 48 con.addEventListener('iceconnectionstatechange', function() { -1 49 if (con.iceConnectionState === 'disconnected') { -1 50 closeConnection(sender); 114 51 } 115 52 }); -1 53 con.addEventListener('track', function(event) { -1 54 // TODO: maybe check if already equal? -1 55 video.srcObject = event.streams[0]; -1 56 }); 116 57117 -1 signal.post(room, {sender: queue, type: 'announce'});118 -1119 -1 window.addEventListener('beforeunload', function() {120 -1 signal.beacon(room, {sender: queue, type: 'leave'});-1 58 localVideo.srcObject.getTracks().forEach(track => { -1 59 con.addTrack(track, localVideo.srcObject); 121 60 }); 122 61123 -1 var updateConnections = function() {124 -1 var sender;125 -1 var tracks = localVideo.srcObject.getTracks();-1 62 cons[sender] = { -1 63 'con': con, -1 64 'video': video, -1 65 }; 126 66127 -1 for (sender in cons) {128 -1 var con = cons[sender].con;-1 67 return con; -1 68 }; 129 69130 -1 con.getSenders().forEach(s => {131 -1 con.removeTrack(s);-1 70 var makeOffer = function(sender) { -1 71 if (sender !== queue) { -1 72 var con = getConnection(sender); -1 73 con.createOffer().then(offer => { -1 74 con.setLocalDescription(offer).then(() => { -1 75 signal.post(sender, {sender: queue, type: 'offer', data: offer}); 132 76 });133 -1134 -1 tracks.forEach(track => {135 -1 con.addTrack(track, localVideo.srcObject);-1 77 }); -1 78 } -1 79 }; -1 80 -1 81 var handleOffer = function(sender, offer) { -1 82 var con = getConnection(sender); -1 83 con.setRemoteDescription(offer).then(() => { -1 84 con.createAnswer().then(answer => { -1 85 con.setLocalDescription(answer).then(() => { -1 86 signal.post(sender, {sender: queue, type: 'answer', data: answer}); 136 87 });137 -1 }138 -1 };-1 88 }); -1 89 }); -1 90 }; -1 91 -1 92 var handleAnswer = function(sender, answer) { -1 93 if (sender in cons) { -1 94 var con = cons[sender].con; -1 95 con.setRemoteDescription(answer); -1 96 } -1 97 }; -1 98 -1 99 var handleCandidate = function(sender, candidate) { -1 100 if (sender in cons) { -1 101 var con = cons[sender].con; -1 102 con.addIceCandidate(candidate); -1 103 } -1 104 }; -1 105 -1 106 signal.listen(queue + ':' + queuePassword, function(msg) { -1 107 if (msg.type === 'offer') { -1 108 handleOffer(msg.sender, msg.data); -1 109 } else if (msg.type === 'answer') { -1 110 handleAnswer(msg.sender, msg.data); -1 111 } else if (msg.type === 'candidate') { -1 112 handleCandidate(msg.sender, msg.data); -1 113 } else { -1 114 console.log('unknown message', msg); -1 115 } -1 116 }); -1 117 -1 118 signal.post(room, {sender: queue, type: 'announce'}); -1 119 -1 120 window.addEventListener('beforeunload', function() { -1 121 signal.beacon(room, {sender: queue, type: 'leave'}); -1 122 }); -1 123 -1 124 var updateConnections = function() { -1 125 var sender; -1 126 var tracks = localVideo.srcObject.getTracks(); -1 127 -1 128 for (sender in cons) { -1 129 var con = cons[sender].con; -1 130 -1 131 con.getSenders().forEach(s => { -1 132 con.removeTrack(s); -1 133 }); 139 134140 -1 var controls = document.querySelector('.rtc-controls');-1 135 tracks.forEach(track => { -1 136 con.addTrack(track, localVideo.srcObject); -1 137 }); -1 138 } -1 139 }; 141 140142 -1 var updateStreams = async function(event) {143 -1 var tracks = localVideo.srcObject.getTracks();144 -1 var kind = event.target.name === 'audio' ? 'audio' : 'video';-1 141 var controls = document.querySelector('.rtc-controls'); 145 142146 -1 if (!event.target.checked) {147 -1 tracks.forEach(track => {148 -1 if (track.kind === kind) {149 -1 track.enabled = false;150 -1 }151 -1 });152 -1 } else if (153 -1 tracks.filter(t => t.kind === kind).length &&154 -1 !(controls.video.checked && controls.screen.checked)155 -1 ) {156 -1 tracks.forEach(track => {157 -1 if (track.kind === kind) {158 -1 track.enabled = true;159 -1 }160 -1 });161 -1 } else {162 -1 var newStream = new MediaStream();163 -1 var stream;164 -1 try {165 -1 if (event.target.name === 'screen') {166 -1 stream = await navigator.mediaDevices.getDisplayMedia();167 -1 } else {168 -1 stream = await navigator.mediaDevices.getUserMedia({[kind]: true});169 -1 }170 -1 } catch (err) {171 -1 event.target.checked = false;172 -1 return;173 -1 }-1 143 var updateStreams = async function(event) { -1 144 var tracks = localVideo.srcObject.getTracks(); -1 145 var kind = event.target.name === 'audio' ? 'audio' : 'video'; 174 146175 -1 if (event.target.name === 'video') {176 -1 controls.screen.checked = false;177 -1 localVideo.classList.add('mirrored');178 -1 } else if (event.target.name === 'screen') {179 -1 controls.video.checked = false;180 -1 localVideo.classList.remove('mirrored');-1 147 if (!event.target.checked) { -1 148 tracks.forEach(track => { -1 149 if (track.kind === kind) { -1 150 track.enabled = false; 181 151 } -1 152 }); -1 153 } else if ( -1 154 tracks.filter(t => t.kind === kind).length && -1 155 !(controls.video.checked && controls.screen.checked) -1 156 ) { -1 157 tracks.forEach(track => { -1 158 if (track.kind === kind) { -1 159 track.enabled = true; -1 160 } -1 161 }); -1 162 } else { -1 163 var newStream = new MediaStream(); -1 164 var stream; -1 165 try { -1 166 if (event.target.name === 'screen') { -1 167 stream = await navigator.mediaDevices.getDisplayMedia(); -1 168 } else { -1 169 stream = await navigator.mediaDevices.getUserMedia({[kind]: true}); -1 170 } -1 171 } catch (err) { -1 172 event.target.checked = false; -1 173 return; -1 174 } 182 175183 -1 tracks.forEach(track => {184 -1 if (track.kind === kind) {185 -1 track.stop();186 -1 } else {187 -1 newStream.addTrack(track);188 -1 }189 -1 });190 -1 stream.getTracks().forEach(track => {191 -1 newStream.addTrack(track);192 -1 });193 -1194 -1 localVideo.srcObject = newStream;195 -1 updateConnections();-1 176 if (event.target.name === 'video') { -1 177 controls.screen.checked = false; -1 178 localVideo.classList.add('mirrored'); -1 179 } else if (event.target.name === 'screen') { -1 180 controls.video.checked = false; -1 181 localVideo.classList.remove('mirrored'); 196 182 }197 -1 };198 183199 -1 document.querySelector('.rtc-controls').addEventListener('change', updateStreams);-1 184 tracks.forEach(track => { -1 185 if (track.kind === kind) { -1 186 track.stop(); -1 187 } else { -1 188 newStream.addTrack(track); -1 189 } -1 190 }); -1 191 stream.getTracks().forEach(track => { -1 192 newStream.addTrack(track); -1 193 }); 200 194 -1 195 localVideo.srcObject = newStream; -1 196 updateConnections(); -1 197 } -1 198 }; 201 199202 -1 var history = document.querySelector('.chat-history');203 -1 var form = document.querySelector('.chat-form');-1 200 document.querySelector('.rtc-controls').addEventListener('change', updateStreams); 204 201205 -1 form.addEventListener('submit', function(event) {206 -1 var input = event.target.msg;207 -1 event.preventDefault();208 -1 if (!input.value) {209 -1 return;210 -1 }211 -1 signal.post(room, {sender: queue, type: 'chat', text: input.value}).then(function() {212 -1 input.value = '';213 -1 });214 -1 });215 202216 -1 var addChatMsg = function(msg) {217 -1 var li = document.createElement('li');218 -1 li.textContent = msg;219 -1 history.append(li);220 -1 history.scrollTop = history.scrollHeight;221 -1 };-1 203 var history = document.querySelector('.chat-history'); -1 204 var form = document.querySelector('.chat-form'); 222 205223 -1 signal.listen(room, function(msg) {224 -1 if (msg.type === 'chat') {225 -1 addChatMsg(msg.text);226 -1 } else if (msg.type === 'announce') {227 -1 makeOffer(msg.sender);228 -1 } else if (msg.type === 'leave') {229 -1 closeConnection(msg.sender);230 -1 } else {231 -1 console.log('unknown message', msg);232 -1 }-1 206 form.addEventListener('submit', function(event) { -1 207 var input = event.target.msg; -1 208 event.preventDefault(); -1 209 if (!input.value) { -1 210 return; -1 211 } -1 212 signal.post(room, {sender: queue, type: 'chat', text: input.value}).then(function() { -1 213 input.value = ''; 233 214 });234 -1235 -1 navigator.mediaDevices.enumerateDevices().then(devices => {236 -1 devices.forEach(device => {237 -1 if (device.kind === 'audioinput') {238 -1 document.querySelector('.rtc-controls [name="audio"]').disabled = false;239 -1 } else if (device.kind === 'videoinput') {240 -1 document.querySelector('.rtc-controls [name="video"]').disabled = false;241 -1 }242 -1 });-1 215 }); -1 216 -1 217 var addChatMsg = function(msg) { -1 218 var li = document.createElement('li'); -1 219 li.textContent = msg; -1 220 history.append(li); -1 221 history.scrollTop = history.scrollHeight; -1 222 }; -1 223 -1 224 signal.listen(room, function(msg) { -1 225 if (msg.type === 'chat') { -1 226 addChatMsg(msg.text); -1 227 } else if (msg.type === 'announce') { -1 228 makeOffer(msg.sender); -1 229 } else if (msg.type === 'leave') { -1 230 closeConnection(msg.sender); -1 231 } else { -1 232 console.log('unknown message', msg); -1 233 } -1 234 }); -1 235 -1 236 navigator.mediaDevices.enumerateDevices().then(devices => { -1 237 devices.forEach(device => { -1 238 if (device.kind === 'audioinput') { -1 239 document.querySelector('.rtc-controls [name="audio"]').disabled = false; -1 240 } else if (device.kind === 'videoinput') { -1 241 document.querySelector('.rtc-controls [name="video"]').disabled = false; -1 242 } 243 243 });244 -1 })();-1 244 });
diff --git a/www/signal.js b/www/signal.js
@@ -1,37 +1,28 @@1 -1 (function() {2 -1 var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';3 -1 var baseUrl = 'https://duct.ce9e.org/';-1 1 var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; -1 2 var baseUrl = 'https://duct.ce9e.org/'; 4 35 -1 var randomString = function(length) {6 -1 var result = [];7 -1 for (var i = 0; i < length; i++) {8 -1 var k = Math.floor(Math.random() * chars.length);9 -1 result.push(chars[k]);10 -1 }11 -1 return result.join('');12 -1 };13 -114 -1 if (!location.hash) {15 -1 location.hash = randomString(10);-1 4 export var randomString = function(length) { -1 5 var result = []; -1 6 for (var i = 0; i < length; i++) { -1 7 var k = Math.floor(Math.random() * chars.length); -1 8 result.push(chars[k]); 16 9 } -1 10 return result.join(''); -1 11 }; 17 1218 -1 var post = function(key, data) {19 -1 return fetch(baseUrl + key, {method: 'POST', body: JSON.stringify(data)});20 -1 };-1 13 export var post = function(key, data) { -1 14 return fetch(baseUrl + key, {method: 'POST', body: JSON.stringify(data)}); -1 15 }; 21 1622 -1 var beacon = function(key, data) {23 -1 return navigator.sendBeacon(baseUrl + key, JSON.stringify(data));24 -1 };-1 17 export var beacon = function(key, data) { -1 18 return navigator.sendBeacon(baseUrl + key, JSON.stringify(data)); -1 19 }; 25 2026 -1 var listen = function(key, fn) {27 -1 var evtSource = new EventSource(baseUrl + key + '?sse');28 -1 evtSource.onmessage = msg => fn(JSON.parse(msg.data));29 -1 };-1 21 export var listen = function(key, fn) { -1 22 var evtSource = new EventSource(baseUrl + key + '?sse'); -1 23 evtSource.onmessage = msg => fn(JSON.parse(msg.data)); -1 24 }; 30 2531 -1 window.signal = {32 -1 'post': post,33 -1 'beacon': beacon,34 -1 'listen': listen,35 -1 'randomString': randomString,36 -1 };37 -1 })();-1 26 if (!location.hash) { -1 27 location.hash = randomString(10); -1 28 }