├── .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 |
--------------------------------------------------------------------------------