├── .gitignore ├── README.md ├── controllee ├── css │ └── main.css ├── index.html ├── index.js ├── js │ └── main.js └── note.txt ├── controller ├── css │ └── main.css ├── index.html ├── js │ └── main.js └── note.pdf ├── package.json └── server └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TeamControl 2 | 3 | Visual Desktop Remote Control Application based on WebRTC 4 | 5 | 6 | ## On the computer to be controlled 7 | 8 | ``` 9 | 10 | npm install 11 | 12 | node controllee/ 13 | 14 | ``` 15 | Will auto open http://localhost:7890/controllee/ in your default browser. 16 | 17 | Then prompt you to type a room name, enter an arbitrary name you prefer. 18 | 19 | And prompt you to choose what screen part to share, choose the "your whole screen" and click "share" button. 20 | 21 | ## On the computer to controll 22 | 23 | Open browser (recommend Chrome) and access https://www.gonnavis.com/controller/ ( I'm sorry that the server is not maintened recently, maybe re-maintained in the future. But you can run the server yourself, see below ). 24 | 25 | Then enter the room name you named before. 26 | 27 | OK, you can remote controll the mouse position! 28 | 29 | 30 | 31 | 32 | ## If you want run the server yourself, on the server 33 | 34 | ``` 35 | 36 | npm install 37 | 38 | node server/ 39 | 40 | ``` 41 | May need some code replace from "gonnavis.com" to "your domain". And note that WebRTC need https server. 42 | 43 | ## Similar Projects 44 | 45 | https://github.com/simplewebrtc/SimpleWebRTC 46 | 47 | https://github.com/pavlobu/deskreen 48 | 49 | https://github.com/jeremija/remote-control-server 50 | 51 | https://github.com/mafintosh/signalhub 52 | 53 | https://github.com/mafintosh/webrtc-swarm 54 | 55 | https://github.com/yjs/y-webrtc 56 | 57 | ## Relative Tutorials 58 | 59 | https://mp.weixin.qq.com/s/97dY9kfeXASovn8k3rfy9A 60 | -------------------------------------------------------------------------------- /controllee/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | html, body{margin:0;} 3 | -------------------------------------------------------------------------------- /controllee/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Realtime communication with WebRTC 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /controllee/index.js: -------------------------------------------------------------------------------- 1 | var port = 7890 2 | var nodeStatic = require('node-static'); 3 | var http = require('http') 4 | const robot = require('robotjs') 5 | var screenSize = robot.getScreenSize(); 6 | const open = require('open'); 7 | var fileServer = new(nodeStatic.Server)(); 8 | var app = http.createServer(function (req, res) { 9 | fileServer.serve(req, res); 10 | }).listen(port); 11 | 12 | var io = require('socket.io')(app) 13 | 14 | io.on('connection', socket => { 15 | socket.on('input', inputStr => { 16 | if (inputStr) { 17 | let input = JSON.parse(inputStr) 18 | console.log('input', input) 19 | switch (input.type) { 20 | case 'mousemove': 21 | robot.moveMouse(input.x * screenSize.width, input.y * screenSize.height) 22 | break 23 | case 'mousedown': 24 | switch (input.button) { 25 | case 0: 26 | robot.mouseToggle('down') 27 | break 28 | case 1: 29 | robot.mouseToggle('down', 'middle') 30 | break 31 | case 2: 32 | robot.mouseToggle('down', 'right') 33 | break 34 | } 35 | break 36 | case 'mouseup': 37 | switch (input.button) { 38 | case 0: 39 | robot.mouseToggle('up') 40 | break 41 | case 1: 42 | robot.mouseToggle('up', 'middle') 43 | break 44 | case 2: 45 | robot.mouseToggle('up', 'right') 46 | break 47 | } 48 | break 49 | case 'mousewheel': 50 | robot.scrollMouse(input.x, input.y) 51 | break 52 | case 'keydown': 53 | try { 54 | mapKey(input) 55 | robot.keyToggle(input.key, 'down') 56 | } catch (e) { 57 | console.log(e) 58 | } 59 | break 60 | case 'keyup': 61 | try { 62 | mapKey(input) 63 | robot.keyToggle(input.key, 'up') 64 | } catch (e) { 65 | console.log(e) 66 | } 67 | break 68 | } 69 | } 70 | }) 71 | }) 72 | 73 | 74 | open(`http://localhost:${port}/controllee/`); 75 | 76 | 77 | 78 | function mapKey(input) { 79 | input.key = input.key.toLowerCase() 80 | switch (input.key) { 81 | case 'arrowup': 82 | input.key = 'up' 83 | break 84 | case 'arrowdown': 85 | input.key = 'down' 86 | break 87 | case 'arrowleft': 88 | input.key = 'left' 89 | break 90 | case 'arrowright': 91 | input.key = 'right' 92 | break 93 | } 94 | } -------------------------------------------------------------------------------- /controllee/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isChannelReady = false; 4 | var isInitiator = false; 5 | var isStarted = false; 6 | var localStream; 7 | var pc; 8 | var remoteStream; 9 | // var maxBandwidth = 64 10 | var videoWidth = 1024 11 | var isControllee = location.href.indexOf('controllee') >= 0 12 | var dataSendChannel 13 | var dataReceiveChannel 14 | 15 | var xhr = new XMLHttpRequest(); 16 | 17 | ///////////////////////////////////////////// 18 | 19 | var room = prompt('Enter room name:'); 20 | 21 | var socket = io('https://www.gonnavis.com/'); 22 | socket.connect() 23 | 24 | var socketLocal = io('/'); 25 | 26 | if (room !== '') { 27 | socket.emit('create or join', room); 28 | console.log('Attempted to create or join room', room); 29 | } 30 | 31 | socket.on('created', function (room) { 32 | console.log('Created room ' + room); 33 | isInitiator = true; 34 | }); 35 | 36 | socket.on('full', function (room) { 37 | console.log('Room ' + room + ' is full'); 38 | }); 39 | 40 | socket.on('join', function (room) { 41 | console.log('Another peer made a request to join room ' + room); 42 | console.log('This peer is the initiator of room ' + room + '!'); 43 | isChannelReady = true; 44 | }); 45 | 46 | socket.on('joined', function (room) { 47 | console.log('joined: ' + room); 48 | isChannelReady = true; 49 | }); 50 | 51 | socket.on('log', function (array) { 52 | console.log.apply(console, array); 53 | }); 54 | 55 | //////////////////////////////////////////////// 56 | 57 | function sendMessage(message) { 58 | console.log('Client sending message: ', message); 59 | socket.emit('message', message); 60 | } 61 | 62 | // This client receives a message 63 | socket.on('message', function (message) { 64 | console.log('Client received message:', message); 65 | if (message === 'got user media') { 66 | maybeStart(); 67 | } else if (message.type === 'offer') { 68 | if (!isInitiator && !isStarted) { 69 | maybeStart(); 70 | } 71 | let sessionDescription = new RTCSessionDescription(message) 72 | pc.setRemoteDescription(sessionDescription) 73 | doAnswer(); 74 | } else if (message.type === 'answer' && isStarted) { 75 | let sessionDescription = new RTCSessionDescription(message) 76 | pc.setRemoteDescription(sessionDescription) 77 | } else if (message.type === 'candidate' && isStarted) { 78 | var candidate = new RTCIceCandidate({ 79 | sdpMLineIndex: message.label, 80 | candidate: message.candidate 81 | }); 82 | pc.addIceCandidate(candidate); 83 | } else if (message === 'bye' && isStarted) { 84 | handleRemoteHangup(); 85 | } 86 | }); 87 | 88 | //////////////////////////////////////////////////// 89 | 90 | var localVideo = document.querySelector('#localVideo'); 91 | var remoteVideo = document.querySelector('#remoteVideo'); 92 | 93 | if (isControllee) { 94 | navigator.mediaDevices.getDisplayMedia({ 95 | audio: false, 96 | video: { 97 | width: videoWidth 98 | }, 99 | }) 100 | .then(gotStream) 101 | .catch(function (e) { 102 | console.log('getUserMedia() error: ' + e.name); 103 | }); 104 | } else { 105 | // createPeerConnection(); 106 | navigator.mediaDevices.getUserMedia({ 107 | audio: false, 108 | video: { 109 | width: videoWidth 110 | }, 111 | }) 112 | .then(gotStream) 113 | .catch(function (e) { 114 | console.log('getUserMedia() error: ' + e.name); 115 | }); 116 | } 117 | 118 | function gotStream(stream) { 119 | console.log('Adding local stream.'); 120 | localStream = stream; 121 | localVideo.srcObject = stream; 122 | sendMessage('got user media'); 123 | if (isInitiator) { 124 | maybeStart(); 125 | } 126 | } 127 | 128 | function maybeStart() { 129 | console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); 130 | if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { 131 | console.log('>>>>>> creating peer connection'); 132 | createPeerConnection(); 133 | pc.addStream(localStream); 134 | isStarted = true; 135 | console.log('isInitiator', isInitiator); 136 | if (isInitiator) { 137 | doCall(); 138 | } 139 | } 140 | } 141 | 142 | window.onbeforeunload = function () { 143 | sendMessage('bye'); 144 | }; 145 | 146 | ///////////////////////////////////////////////////////// 147 | 148 | function createPeerConnection() { 149 | try { 150 | pc = new RTCPeerConnection(null); 151 | dataSendChannel = pc.createDataChannel('sendDataChannel') 152 | pc.ondatachannel = function (event) { 153 | dataReceiveChannel = event.channel; 154 | dataReceiveChannel.onmessage = function (event) { 155 | // let mouse=JSON.parse(event.data) 156 | let inputStr=event.data 157 | // xhr.open("GET", "http://localhost:7890/?"+inputStr); 158 | // xhr.send() 159 | socketLocal.emit('input', inputStr) 160 | } 161 | } 162 | pc.onicecandidate = handleIceCandidate; 163 | pc.onaddstream = handleRemoteStreamAdded; 164 | pc.onremovestream = handleRemoteStreamRemoved; 165 | console.log('Created RTCPeerConnnection'); 166 | } catch (e) { 167 | console.log('Failed to create PeerConnection, exception: ' + e.message); 168 | console.log('Cannot create RTCPeerConnection object.'); 169 | return; 170 | } 171 | } 172 | 173 | function handleIceCandidate(event) { 174 | console.log('icecandidate event: ', event); 175 | if (event.candidate) { 176 | sendMessage({ 177 | type: 'candidate', 178 | label: event.candidate.sdpMLineIndex, 179 | id: event.candidate.sdpMid, 180 | candidate: event.candidate.candidate 181 | }); 182 | } else { 183 | console.log('End of candidates.'); 184 | } 185 | } 186 | 187 | function handleCreateOfferError(event) { 188 | console.log('createOffer() error: ', event); 189 | } 190 | 191 | function doCall() { 192 | console.log('Sending offer to peer'); 193 | pc.createOffer(setLocalAndSendMessage, handleCreateOfferError, { 194 | offerToReceiveAudio: 0, 195 | offerToReceiveVideo: 0 196 | }); 197 | } 198 | 199 | function doAnswer() { 200 | console.log('Sending answer to peer.'); 201 | pc.createAnswer({ 202 | offerToReceiveAudio: 0, 203 | offerToReceiveVideo: 1 204 | }).then( 205 | setLocalAndSendMessage, 206 | onCreateSessionDescriptionError 207 | ); 208 | } 209 | 210 | function setLocalAndSendMessage(sessionDescription) { 211 | pc.setLocalDescription(sessionDescription) 212 | console.log('setLocalAndSendMessage sending message', sessionDescription); 213 | sendMessage(sessionDescription); 214 | } 215 | 216 | function onCreateSessionDescriptionError(error) { 217 | trace('Failed to create session description: ' + error.toString()); 218 | } 219 | 220 | function handleRemoteStreamAdded(event) { 221 | console.log('Remote stream added.'); 222 | remoteStream = event.stream; 223 | remoteVideo.srcObject = remoteStream; 224 | } 225 | 226 | function handleRemoteStreamRemoved(event) { 227 | console.log('Remote stream removed. Event: ', event); 228 | } 229 | 230 | function hangup() { 231 | console.log('Hanging up.'); 232 | stop(); 233 | sendMessage('bye'); 234 | } 235 | 236 | function handleRemoteHangup() { 237 | console.log('Session terminated.'); 238 | stop(); 239 | isInitiator = false; 240 | } 241 | 242 | function stop() { 243 | isStarted = false; 244 | pc.close(); 245 | pc = null; 246 | } 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /controllee/note.txt: -------------------------------------------------------------------------------- 1 | main.js:28 Attempted to create or join room ttt 2 | main.js:52 Message from server: Received request to create or join room ttt 3 | main.js:52 Message from server: Room ttt now has 0 client(s) 4 | main.js:52 Message from server: Client ID Cjp59QPwZE5zvD_cAACk created room ttt 5 | main.js:32 Created room ttt 6 | main.js:119 Adding local stream. 7 | main.js:58 Client sending message: got user media 8 | main.js:129 >>>>>>> maybeStart() false MediaStream false 9 | main.js:52 Message from server: Client said: got user media 10 | main.js:41 Another peer made a request to join room ttt 11 | main.js:42 This peer is the initiator of room ttt! 12 | main.js:64 Client received message: got user media 13 | main.js:129 >>>>>>> maybeStart() false MediaStream {id: "Un0dYpCoupVMv4uqssyaXylfwftrUdCwABTI", active: true, onaddtrack: null, onremovetrack: null, onactive: null, …} true 14 | main.js:131 >>>>>> creating peer connection 15 | main.js:165 Created RTCPeerConnnection 16 | main.js:135 isInitiator true 17 | main.js:192 Sending offer to peer 18 | main.js:212 setLocalAndSendMessage sending message RTCSessionDescription {type: "offer", sdp: "v=0 19 | ↵o=- 8828008736651994600 2 IN IP4 127.0.0.1 20 | ↵s…a=mid:1 21 | ↵a=sctpmap:5000 webrtc-datachannel 1024 22 | ↵"} 23 | main.js:58 Client sending message: RTCSessionDescription {type: "offer", sdp: "v=0 24 | ↵o=- 8828008736651994600 2 IN IP4 127.0.0.1 25 | ↵s…a=mid:1 26 | ↵a=sctpmap:5000 webrtc-datachannel 1024 27 | ↵"} 28 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 29 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:2999745851 1 udp 2122260223 192.168.56.1…835 typ host generation 0 ufrag cqIc network-id 2"} 30 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 31 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:2534616982 1 udp 2122194687 192.168.230.…836 typ host generation 0 ufrag cqIc network-id 3"} 32 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 33 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:376141290 1 udp 2122129151 192.168.229.1…837 typ host generation 0 ufrag cqIc network-id 4"} 34 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 35 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:2588195999 1 udp 2122063615 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 36 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 37 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:2999745851 1 udp 2122260223 192.168.56.1…839 typ host generation 0 ufrag cqIc network-id 2"} 38 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 39 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:2534616982 1 udp 2122194687 192.168.230.…840 typ host generation 0 ufrag cqIc network-id 3"} 40 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 41 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:376141290 1 udp 2122129151 192.168.229.1…841 typ host generation 0 ufrag cqIc network-id 4"} 42 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 43 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:2588195999 1 udp 2122063615 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 44 | main.js:52 Message from server: Client said: {type: "offer", sdp: "v=0 45 | ↵o=- 8828008736651994600 2 IN IP4 127.0.0.1 46 | ↵s…a=mid:1 47 | ↵a=sctpmap:5000 webrtc-datachannel 1024 48 | ↵"} 49 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:2999745851 1 udp 2122260223 192.168.56.1…835 typ host generation 0 ufrag cqIc network-id 2"} 50 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:2534616982 1 udp 2122194687 192.168.230.…836 typ host generation 0 ufrag cqIc network-id 3"} 51 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:376141290 1 udp 2122129151 192.168.229.1…837 typ host generation 0 ufrag cqIc network-id 4"} 52 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:2588195999 1 udp 2122063615 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 53 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:2999745851 1 udp 2122260223 192.168.56.1…839 typ host generation 0 ufrag cqIc network-id 2"} 54 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:2534616982 1 udp 2122194687 192.168.230.…840 typ host generation 0 ufrag cqIc network-id 3"} 55 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:376141290 1 udp 2122129151 192.168.229.1…841 typ host generation 0 ufrag cqIc network-id 4"} 56 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:2588195999 1 udp 2122063615 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 57 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 58 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:4233069003 1 tcp 1518280447 192.168.56.1…ptype active generation 0 ufrag cqIc network-id 2"} 59 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 60 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:3650147174 1 tcp 1518214911 192.168.230.…ptype active generation 0 ufrag cqIc network-id 3"} 61 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 62 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:1491634458 1 tcp 1518149375 192.168.229.…ptype active generation 0 ufrag cqIc network-id 4"} 63 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 64 | main.js:58 Client sending message: {type: "candidate", label: 0, id: "0", candidate: "candidate:3569827951 1 tcp 1518083839 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 65 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 66 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:4233069003 1 tcp 1518280447 192.168.56.1…ptype active generation 0 ufrag cqIc network-id 2"} 67 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 68 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:3650147174 1 tcp 1518214911 192.168.230.…ptype active generation 0 ufrag cqIc network-id 3"} 69 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 70 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:1491634458 1 tcp 1518149375 192.168.229.…ptype active generation 0 ufrag cqIc network-id 4"} 71 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: RTCIceCandidate, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 72 | main.js:58 Client sending message: {type: "candidate", label: 1, id: "1", candidate: "candidate:3569827951 1 tcp 1518083839 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 73 | main.js:174 icecandidate event: RTCPeerConnectionIceEvent {isTrusted: true, candidate: null, type: "icecandidate", target: RTCPeerConnection, currentTarget: RTCPeerConnection, …} 74 | main.js:183 End of candidates. 75 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:4233069003 1 tcp 1518280447 192.168.56.1…ptype active generation 0 ufrag cqIc network-id 2"} 76 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:3650147174 1 tcp 1518214911 192.168.230.…ptype active generation 0 ufrag cqIc network-id 3"} 77 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:1491634458 1 tcp 1518149375 192.168.229.…ptype active generation 0 ufrag cqIc network-id 4"} 78 | main.js:52 Message from server: Client said: {type: "candidate", label: 0, id: "0", candidate: "candidate:3569827951 1 tcp 1518083839 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 79 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:4233069003 1 tcp 1518280447 192.168.56.1…ptype active generation 0 ufrag cqIc network-id 2"} 80 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:3650147174 1 tcp 1518214911 192.168.230.…ptype active generation 0 ufrag cqIc network-id 3"} 81 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:1491634458 1 tcp 1518149375 192.168.229.…ptype active generation 0 ufrag cqIc network-id 4"} 82 | main.js:52 Message from server: Client said: {type: "candidate", label: 1, id: "1", candidate: "candidate:3569827951 1 tcp 1518083839 172.30.78.14…eration 0 ufrag cqIc network-id 1 network-cost 10"} 83 | main.js:64 Client received message: {type: "answer", sdp: "v=0 84 | ↵o=- 5639793498432429295 2 IN IP4 127.0.0.1 85 | ↵s…a=mid:1 86 | ↵a=sctpmap:5000 webrtc-datachannel 1024 87 | ↵"} 88 | main.js:64 Client received message: {type: "candidate", label: 0, id: "0", candidate: "candidate:1940350144 1 udp 2122260223 172.30.67.80…eration 0 ufrag LRQU network-id 1 network-cost 10"} 89 | main.js:64 Client received message: {type: "candidate", label: 0, id: "0", candidate: "candidate:1025808432 1 tcp 1518280447 172.30.67.80…eration 0 ufrag LRQU network-id 1 network-cost 10"} -------------------------------------------------------------------------------- /controller/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | html, body{margin:0;} 3 | -------------------------------------------------------------------------------- /controller/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Realtime communication with WebRTC 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /controller/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var isChannelReady = false; 4 | var isInitiator = false; 5 | var isStarted = false; 6 | var localStream; 7 | var pc; 8 | var remoteStream; 9 | // var maxBandwidth = 64 10 | var videoWidth = 1024 11 | var isControllee = location.href.indexOf('controllee') >= 0 12 | var dataSendChannel 13 | var dataReceiveChannel 14 | var remoteVideo = document.getElementById('remoteVideo') 15 | 16 | ///////////////////////////////////////////// 17 | 18 | var room = prompt('Enter room name:'); 19 | 20 | var socket = io.connect(); 21 | 22 | if (room !== '') { 23 | socket.emit('create or join', room); 24 | console.log('Attempted to create or join room', room); 25 | } 26 | 27 | 28 | 29 | 30 | remoteVideo.addEventListener('mousemove', e => { 31 | if (!dataSendChannel) return 32 | e.preventDefault() 33 | dataSendChannel.send(JSON.stringify({ 34 | type: 'mousemove', 35 | button: e.button, 36 | x: e.offsetX / remoteVideo.offsetWidth, 37 | y: e.offsetY / remoteVideo.offsetHeight, 38 | })); 39 | }) 40 | 41 | remoteVideo.addEventListener('mousedown', e => { 42 | if (!dataSendChannel) return 43 | e.preventDefault() 44 | dataSendChannel.send(JSON.stringify({ 45 | type: 'mousedown', 46 | button: e.button, 47 | })); 48 | }) 49 | 50 | remoteVideo.addEventListener('mouseup', e => { 51 | if (!dataSendChannel) return 52 | e.preventDefault() 53 | dataSendChannel.send(JSON.stringify({ 54 | type: 'mouseup', 55 | button: e.button, 56 | })); 57 | }) 58 | 59 | remoteVideo.addEventListener('mousewheel', e => { 60 | if (!dataSendChannel) return 61 | e.preventDefault() 62 | dataSendChannel.send(JSON.stringify({ 63 | type: 'mousewheel', 64 | button: e.button, 65 | x: e.deltaX, 66 | y: e.deltaY, 67 | })); 68 | }) 69 | 70 | remoteVideo.addEventListener('contextmenu', e => { 71 | e.preventDefault() 72 | }) 73 | 74 | window.addEventListener('keydown', e => { 75 | if (!dataSendChannel) return 76 | e.preventDefault() 77 | dataSendChannel.send(JSON.stringify({ 78 | type: 'keydown', 79 | key: e.key, 80 | // alt: e.altKey ? 1 : 0, 81 | // ctrl: e.ctrlKey ? 1 : 0, 82 | // meta: e.metaKey ? 1 : 0, 83 | // shift: e.shiftKey ? 1 : 0, 84 | })); 85 | }) 86 | 87 | window.addEventListener('keyup', e => { 88 | if (!dataSendChannel) return 89 | e.preventDefault() 90 | dataSendChannel.send(JSON.stringify({ 91 | type: 'keyup', 92 | key: e.key, 93 | // alt: e.altKey ? 1 : 0, 94 | // ctrl: e.ctrlKey ? 1 : 0, 95 | // meta: e.metaKey ? 1 : 0, 96 | // shift: e.shiftKey ? 1 : 0, 97 | })); 98 | }) 99 | 100 | 101 | 102 | socket.on('created', function (room) { 103 | console.log('Created room ' + room); 104 | isInitiator = true; 105 | }); 106 | 107 | socket.on('full', function (room) { 108 | console.log('Room ' + room + ' is full'); 109 | }); 110 | 111 | socket.on('join', function (room) { 112 | console.log('Another peer made a request to join room ' + room); 113 | console.log('This peer is the initiator of room ' + room + '!'); 114 | isChannelReady = true; 115 | }); 116 | 117 | socket.on('joined', function (room) { 118 | console.log('joined: ' + room); 119 | isChannelReady = true; 120 | }); 121 | 122 | socket.on('log', function (array) { 123 | console.log.apply(console, array); 124 | }); 125 | 126 | //////////////////////////////////////////////// 127 | 128 | function sendMessage(message) { 129 | console.log('Client sending message: ', message); 130 | socket.emit('message', message); 131 | } 132 | 133 | // This client receives a message 134 | socket.on('message', function (message) { 135 | console.log('Client received message:', message); 136 | if (message === 'got user media') { 137 | maybeStart(); 138 | } else if (message.type === 'offer') { 139 | if (!isInitiator && !isStarted) { 140 | maybeStart(); 141 | } 142 | let sessionDescription = new RTCSessionDescription(message) 143 | pc.setRemoteDescription(sessionDescription) 144 | doAnswer(); 145 | } else if (message.type === 'answer' && isStarted) { 146 | let sessionDescription = new RTCSessionDescription(message) 147 | pc.setRemoteDescription(sessionDescription) 148 | } else if (message.type === 'candidate' && isStarted) { 149 | var candidate = new RTCIceCandidate({ 150 | sdpMLineIndex: message.label, 151 | candidate: message.candidate 152 | }); 153 | pc.addIceCandidate(candidate); 154 | } else if (message === 'bye' && isStarted) { 155 | handleRemoteHangup(); 156 | } 157 | }); 158 | 159 | //////////////////////////////////////////////////// 160 | 161 | var localVideo = document.querySelector('#localVideo'); 162 | var remoteVideo = document.querySelector('#remoteVideo'); 163 | 164 | if (isControllee) { 165 | navigator.mediaDevices.getDisplayMedia({ 166 | audio: false, 167 | video: { 168 | width: videoWidth 169 | }, 170 | }) 171 | .then(gotStream) 172 | .catch(function (e) { 173 | console.log('getUserMedia() error: ' + e.name); 174 | }); 175 | } else { 176 | 177 | gotStream(new MediaStream()) 178 | 179 | // createPeerConnection(); 180 | 181 | // navigator.mediaDevices.getUserMedia({ 182 | // audio: false, 183 | // video: { 184 | // width: videoWidth 185 | // }, 186 | // }) 187 | // .then(gotStream) 188 | // .catch(function (e) { 189 | // console.log('getUserMedia() error: ' + e.name); 190 | // }); 191 | } 192 | 193 | function gotStream(stream) { 194 | console.log('Adding local stream.'); 195 | localStream = stream; 196 | localVideo.srcObject = stream; 197 | sendMessage('got user media'); 198 | if (isInitiator) { 199 | maybeStart(); 200 | } 201 | } 202 | 203 | function maybeStart() { 204 | console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); 205 | if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { 206 | console.log('>>>>>> creating peer connection'); 207 | createPeerConnection(); 208 | pc.addStream(localStream); 209 | isStarted = true; 210 | console.log('isInitiator', isInitiator); 211 | if (isInitiator) { 212 | doCall(); 213 | } 214 | } 215 | } 216 | 217 | window.onbeforeunload = function () { 218 | sendMessage('bye'); 219 | }; 220 | 221 | ///////////////////////////////////////////////////////// 222 | 223 | function createPeerConnection() { 224 | try { 225 | pc = new RTCPeerConnection(null); 226 | dataSendChannel = pc.createDataChannel('sendDataChannel') 227 | pc.ondatachannel = function (event) { 228 | dataReceiveChannel = event.channel; 229 | dataReceiveChannel.onmessage = function (event) { 230 | // console.log(event.data) 231 | } 232 | } 233 | pc.onicecandidate = handleIceCandidate; 234 | pc.onaddstream = handleRemoteStreamAdded; 235 | pc.onremovestream = handleRemoteStreamRemoved; 236 | console.log('Created RTCPeerConnnection'); 237 | } catch (e) { 238 | console.log('Failed to create PeerConnection, exception: ' + e.message); 239 | console.log('Cannot create RTCPeerConnection object.'); 240 | return; 241 | } 242 | } 243 | 244 | function handleIceCandidate(event) { 245 | console.log('icecandidate event: ', event); 246 | if (event.candidate) { 247 | sendMessage({ 248 | type: 'candidate', 249 | label: event.candidate.sdpMLineIndex, 250 | id: event.candidate.sdpMid, 251 | candidate: event.candidate.candidate 252 | }); 253 | } else { 254 | console.log('End of candidates.'); 255 | } 256 | } 257 | 258 | function handleCreateOfferError(event) { 259 | console.log('createOffer() error: ', event); 260 | } 261 | 262 | function doCall() { 263 | console.log('Sending offer to peer'); 264 | pc.createOffer(setLocalAndSendMessage, handleCreateOfferError, { 265 | offerToReceiveAudio: 0, 266 | offerToReceiveVideo: 0 267 | }); 268 | } 269 | 270 | function doAnswer() { 271 | console.log('Sending answer to peer.'); 272 | pc.createAnswer({ 273 | offerToReceiveAudio: 0, 274 | offerToReceiveVideo: 1 275 | }).then( 276 | setLocalAndSendMessage, 277 | onCreateSessionDescriptionError 278 | ); 279 | } 280 | 281 | function setLocalAndSendMessage(sessionDescription) { 282 | pc.setLocalDescription(sessionDescription) 283 | console.log('setLocalAndSendMessage sending message', sessionDescription); 284 | sendMessage(sessionDescription); 285 | } 286 | 287 | function onCreateSessionDescriptionError(error) { 288 | trace('Failed to create session description: ' + error.toString()); 289 | } 290 | 291 | function handleRemoteStreamAdded(event) { 292 | console.log('Remote stream added.'); 293 | remoteStream = event.stream; 294 | remoteVideo.srcObject = remoteStream; 295 | } 296 | 297 | function handleRemoteStreamRemoved(event) { 298 | console.log('Remote stream removed. Event: ', event); 299 | } 300 | 301 | function hangup() { 302 | console.log('Hanging up.'); 303 | stop(); 304 | sendMessage('bye'); 305 | } 306 | 307 | function handleRemoteHangup() { 308 | console.log('Session terminated.'); 309 | stop(); 310 | isInitiator = false; 311 | } 312 | 313 | function stop() { 314 | isStarted = false; 315 | pc.close(); 316 | pc = null; 317 | } -------------------------------------------------------------------------------- /controller/note.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonnavis/TeamControl/2f1b2a7dd5c3fbbfc825e1b0d8f8456d536359d5/controller/note.pdf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc-codelab", 3 | "version": "0.0.1", 4 | "description": "WebRTC codelab", 5 | "dependencies": { 6 | "node-static": "^0.7.10", 7 | "open": "^6.4.0", 8 | "robotjs": "^0.5.1", 9 | "socket.io": "^2.0.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nodeStatic = require('node-static'); 4 | var https = require('https') 5 | var fs=require('fs') 6 | var socketIO = require('socket.io'); 7 | var options = { 8 | key: fs.readFileSync('2279791_gonnavis.com.key'), 9 | cert: fs.readFileSync('2279791_gonnavis.com.pem'), 10 | } 11 | var fileServer = new(nodeStatic.Server)(); 12 | var app = https.createServer(options,function(req, res) { 13 | fileServer.serve(req, res); 14 | }).listen(443); 15 | 16 | var io = socketIO.listen(app); 17 | io.sockets.on('connection', function(socket) { 18 | 19 | // convenience function to log server messages on the client 20 | function log() { 21 | var array = ['Message from server:']; 22 | array.push.apply(array, arguments); 23 | socket.emit('log', array); 24 | } 25 | 26 | socket.on('message', function(message) { 27 | log('Client said: ', message); 28 | // for a real app, would be room-only (not broadcast) 29 | socket.broadcast.emit('message', message); 30 | }); 31 | 32 | socket.on('create or join', function(room) { 33 | log('Received request to create or join room ' + room); 34 | 35 | var clientsInRoom = io.sockets.adapter.rooms[room]; 36 | var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; 37 | log('Room ' + room + ' now has ' + numClients + ' client(s)'); 38 | 39 | if (numClients === 0) { 40 | socket.join(room); 41 | log('Client ID ' + socket.id + ' created room ' + room); 42 | socket.emit('created', room, socket.id); 43 | 44 | } else if (numClients === 1) { 45 | log('Client ID ' + socket.id + ' joined room ' + room); 46 | io.sockets.in(room).emit('join', room); 47 | socket.join(room); 48 | socket.emit('joined', room, socket.id); 49 | io.sockets.in(room).emit('ready'); 50 | } else { // max two clients 51 | socket.emit('full', room); 52 | } 53 | }); 54 | 55 | socket.on('bye', function(){ 56 | console.log('received bye'); 57 | }); 58 | 59 | }); 60 | --------------------------------------------------------------------------------