blob: d47566cb8711f6cfa9c63c4d16adf905ad4fbde0 [file] [log] [blame]
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -08001'use strict';
2
3const receiveButton = document.getElementById('receiveButton');
4receiveButton.addEventListener('click', onReceive);
Jorge E. Moreiracfd08402020-01-13 18:36:33 -08005const keyboardCaptureButton = document.getElementById('keyboardCaptureBtn');
6keyboardCaptureButton.addEventListener('click', onKeyboardCaptureClick);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -08007
Roger Ellisaeec7402020-02-18 17:24:04 -08008const deviceScreen = document.getElementById('deviceScreen');
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -08009
Roger Ellisaeec7402020-02-18 17:24:04 -080010deviceScreen.addEventListener("click", onInitialClick);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080011
12function onInitialClick(e) {
13 // This stupid thing makes sure that we disable controls after the first click...
14 // Why not just disable controls altogether you ask? Because then audio won't play
15 // because these days user-interaction is required to enable audio playback...
16 console.log("onInitialClick");
17
Roger Ellisaeec7402020-02-18 17:24:04 -080018 deviceScreen.controls = false;
19 deviceScreen.removeEventListener("click", onInitialClick);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080020}
21
22let pc1;
23let pc2;
24
25let dataChannel;
26
27let ws;
28
29let offerResolve;
30let iceCandidateResolve;
31
32let videoStream;
33
34let mouseIsDown = false;
35
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080036const is_chrome = navigator.userAgent.indexOf("Chrome") !== -1;
37
38function handleDataChannelStatusChange(event) {
39 console.log('handleDataChannelStatusChange state=' + dataChannel.readyState);
40
41 if (dataChannel.readyState == "open") {
42 dataChannel.send("Hello, world!");
43 }
44}
45
46function handleDataChannelMessage(event) {
47 console.log('handleDataChannelMessage data="' + event.data + '"');
48}
49
Jorge E. Moreiracfd08402020-01-13 18:36:33 -080050function onKeyboardCaptureClick(e) {
51 const selectedClass = 'selected';
52 if (keyboardCaptureButton.classList.contains(selectedClass)) {
53 stopKeyboardTracking();
54 keyboardCaptureButton.classList.remove(selectedClass);
55 } else {
56 startKeyboardTracking();
57 keyboardCaptureButton.classList.add(selectedClass);
58 }
59}
60
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080061async function onReceive() {
62 console.log('onReceive');
63 receiveButton.disabled = true;
64
65 init_logcat();
66
Jorge E. Moreiracccac172020-01-22 14:24:22 -080067 const wsProtocol = (location.protocol == "http:") ? "ws:" : "wss:";
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080068
Jorge E. Moreiracccac172020-01-22 14:24:22 -080069 ws = new WebSocket(wsProtocol + "//" + location.host + "/control");
Jorge E. Moreirab04746d2020-01-22 14:25:59 -080070 // temporarily disable audio to free ports in the server since it's only
71 // producing silence anyways.
72 var search = location.search + "&disable_audio=1";
73 search = '?' + search.substr(1);
74
Jorge E. Moreiracccac172020-01-22 14:24:22 -080075 ws.onopen = function() {
76 console.log("onopen");
77 ws.send('{\r\n'
78 + '"type": "greeting",\r\n'
79 + '"message": "Hello, world!",\r\n'
Jorge E. Moreirab04746d2020-01-22 14:25:59 -080080 + '"path": "' + location.pathname + search + '"\r\n'
Jorge E. Moreiracccac172020-01-22 14:24:22 -080081 + '}');
82 };
83 ws.onmessage = function(e) {
84 console.log("onmessage " + e.data);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080085
Jorge E. Moreiracccac172020-01-22 14:24:22 -080086 let data = JSON.parse(e.data);
87 if (data.type == "hello") {
88 kickoff();
89 } else if (data.type == "offer" && offerResolve) {
90 offerResolve(data.sdp);
91 offerResolve = undefined;
92 } else if (data.type == "ice-candidate" && iceCandidateResolve) {
93 iceCandidateResolve(data);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080094
Jorge E. Moreiracccac172020-01-22 14:24:22 -080095 iceCandidateResolve = undefined;
96 }
97 };
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -080098
99 pc2 = new RTCPeerConnection();
100 console.log('got pc2=' + pc2);
101
102 pc2.addEventListener(
103 'icecandidate', e => onIceCandidate(pc2, e));
104
105 pc2.addEventListener(
106 'iceconnectionstatechange', e => onIceStateChange(pc2, e));
107
108 pc2.addEventListener(
109 'connectionstatechange', e => {
110 console.log("connection state = " + pc2.connectionState);
111 });
112
113 pc2.addEventListener('track', onGotRemoteStream);
114
115 dataChannel = pc2.createDataChannel("data-channel");
116 dataChannel.onopen = handleDataChannelStatusChange;
117 dataChannel.onclose = handleDataChannelStatusChange;
118 dataChannel.onmessage = handleDataChannelMessage;
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800119}
120
121async function kickoff() {
122 console.log('createOffer start');
123
124 try {
Jorge E. Moreiracccac172020-01-22 14:24:22 -0800125 var offer = await getWsOffer();
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800126 await onCreateOfferSuccess(offer);
127 } catch (e) {
128 console.log('createOffer FAILED ');
129 }
130}
131
132async function onCreateOfferSuccess(desc) {
133 console.log(`Offer ${desc.sdp}`);
134
135 try {
136 pc2.setRemoteDescription(desc);
137 } catch (e) {
138 console.log('setRemoteDescription pc2 FAILED');
139 return;
140 }
141
142 console.log('setRemoteDescription pc2 successful.');
143
144 try {
Jorge E. Moreiracccac172020-01-22 14:24:22 -0800145 setWsLocalDescription(desc);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800146 } catch (e) {
147 console.log('setLocalDescription pc1 FAILED');
148 return;
149 }
150
151 console.log('setLocalDescription pc1 successful.');
152
153 try {
154 const answer = await pc2.createAnswer();
155
156 await onCreateAnswerSuccess(answer);
157 } catch (e) {
158 console.log('createAnswer FAILED');
159 }
160}
161
162function setWsRemoteDescription(desc) {
163 ws.send('{\r\n'
164 + '"type": "set-remote-desc",\r\n'
165 + '"sdp": "' + desc.sdp + '"\r\n'
166 + '}');
167}
168
169function setWsLocalDescription(desc) {
170 ws.send('{\r\n'
171 + '"type": "set-local-desc",\r\n'
172 + '"sdp": "' + desc.sdp + '"\r\n'
173 + '}');
174}
175
176async function getWsOffer() {
177 const offerPromise = new Promise(function(resolve, reject) {
178 offerResolve = resolve;
179 });
180
181 ws.send('{\r\n'
182 + '"type": "request-offer",\r\n'
183 + (is_chrome ? '"is_chrome": 1\r\n'
184 : '"is_chrome": 0\r\n')
185 + '}');
186
187 const sdp = await offerPromise;
188
189 return { type: "offer", sdp: sdp };
190}
191
192async function getWsIceCandidate(mid) {
193 console.log("getWsIceCandidate (mid=" + mid + ")");
194
195 const answerPromise = new Promise(function(resolve, reject) {
196 iceCandidateResolve = resolve;
197 });
198
199 ws.send('{\r\n'
200 + '"type": "get-ice-candidate",\r\n'
201 + '"mid": ' + mid + ',\r\n'
202 + '}');
203
204 const replyInfo = await answerPromise;
205
206 console.log("got replyInfo '" + replyInfo + "'");
207
208 if (replyInfo == undefined || replyInfo.candidate == undefined) {
209 return null;
210 }
211
212 const replyCandidate = replyInfo.candidate;
213 const mlineIndex = replyInfo.mlineIndex;
214
215 let result;
216 try {
217 result = new RTCIceCandidate(
218 {
219 sdpMid: mid,
220 sdpMLineIndex: mlineIndex,
221 candidate: replyCandidate
222 });
223 }
224 catch (e) {
225 console.log("new RTCIceCandidate FAILED. " + e);
226 return undefined;
227 }
228
229 console.log("got result " + result);
230
231 return result;
232}
233
234async function addRemoteIceCandidate(mid) {
235 const candidate = await getWsIceCandidate(mid);
236
237 if (!candidate) {
238 return false;
239 }
240
241 try {
242 await pc2.addIceCandidate(candidate);
243 } catch (e) {
244 console.log("addIceCandidate pc2 FAILED w/ " + e);
245 return false;
246 }
247
248 console.log("addIceCandidate pc2 successful. (mid="
249 + mid + ", mlineIndex=" + candidate.sdpMLineIndex + ")");
250
251 return true;
252}
253
254async function onCreateAnswerSuccess(desc) {
255 console.log(`Answer ${desc.sdp}`);
256
257 try {
258 await pc2.setLocalDescription(desc);
259 } catch (e) {
260 console.log('setLocalDescription pc2 FAILED ' + e);
261 return;
262 }
263
264 console.log('setLocalDescription pc2 successful.');
265
266 try {
Jorge E. Moreiracccac172020-01-22 14:24:22 -0800267 setWsRemoteDescription(desc);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800268 } catch (e) {
269 console.log('setRemoteDescription pc1 FAILED');
270 return;
271 }
272
273 console.log('setRemoteDescription pc1 successful.');
274
Jorge E. Moreiracccac172020-01-22 14:24:22 -0800275 if (!await addRemoteIceCandidate(0)) {
276 return;
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800277 }
Jorge E. Moreiracccac172020-01-22 14:24:22 -0800278 await addRemoteIceCandidate(1);
279 await addRemoteIceCandidate(2);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800280}
281
282function getPcName(pc) {
283 return ((pc == pc2) ? "pc2" : "pc1");
284}
285
286async function onIceCandidate(pc, e) {
287 console.log(
288 getPcName(pc)
289 + ' onIceCandidate '
290 + (e.candidate ? ('"' + e.candidate.candidate + '"') : '(null)')
291 + " "
292 + (e.candidate ? ('sdmMid: ' + e.candidate.sdpMid) : '(null)')
293 + " "
294 + (e.candidate ? ('sdpMLineIndex: ' + e.candidate.sdpMLineIndex) : '(null)'));
295
296 if (!e.candidate) {
297 return;
298 }
299
300 let other_pc = (pc == pc2) ? pc1 : pc2;
301
302 if (other_pc) {
303 try {
304 await other_pc.addIceCandidate(e.candidate);
305 } catch (e) {
306 console.log('addIceCandidate FAILED ' + e);
307 return;
308 }
309
310 console.log('addIceCandidate successful.');
311 }
312}
313
314async function onIceStateChange(pc, e) {
315 console.log(
316 'onIceStateChange ' + getPcName(pc) + " '" + pc.iceConnectionState + "'");
317
318 if (pc.iceConnectionState == "connected") {
Roger Ellisaeec7402020-02-18 17:24:04 -0800319 deviceScreen.srcObject = videoStream;
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800320
321 startMouseTracking()
322 } else if (pc.iceConnectionState == "disconnected") {
323 stopMouseTracking()
324 }
325}
326
327async function onGotRemoteStream(e) {
328 console.log('onGotRemoteStream ' + e);
329
330 const track = e.track;
331
332 console.log('track = ' + track);
333 console.log('track.kind = ' + track.kind);
334 console.log('track.readyState = ' + track.readyState);
335 console.log('track.enabled = ' + track.enabled);
336
337 if (track.kind == "video") {
338 videoStream = e.streams[0];
339 }
340}
341
342function startMouseTracking() {
343 if (window.PointerEvent) {
Roger Ellisaeec7402020-02-18 17:24:04 -0800344 deviceScreen.addEventListener("pointerdown", onStartDrag);
345 deviceScreen.addEventListener("pointermove", onContinueDrag);
346 deviceScreen.addEventListener("pointerup", onEndDrag);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800347 } else if (window.TouchEvent) {
Roger Ellisaeec7402020-02-18 17:24:04 -0800348 deviceScreen.addEventListener("touchstart", onStartDrag);
349 deviceScreen.addEventListener("touchmove", onContinueDrag);
350 deviceScreen.addEventListener("touchend", onEndDrag);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800351 } else if (window.MouseEvent) {
Roger Ellisaeec7402020-02-18 17:24:04 -0800352 deviceScreen.addEventListener("mousedown", onStartDrag);
353 deviceScreen.addEventListener("mousemove", onContinueDrag);
354 deviceScreen.addEventListener("mouseup", onEndDrag);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800355 }
356}
357
358function stopMouseTracking() {
359 if (window.PointerEvent) {
Roger Ellisaeec7402020-02-18 17:24:04 -0800360 deviceScreen.removeEventListener("pointerdown", onStartDrag);
361 deviceScreen.removeEventListener("pointermove", onContinueDrag);
362 deviceScreen.removeEventListener("pointerup", onEndDrag);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800363 } else if (window.TouchEvent) {
Roger Ellisaeec7402020-02-18 17:24:04 -0800364 deviceScreen.removeEventListener("touchstart", onStartDrag);
365 deviceScreen.removeEventListener("touchmove", onContinueDrag);
366 deviceScreen.removeEventListener("touchend", onEndDrag);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800367 } else if (window.MouseEvent) {
Roger Ellisaeec7402020-02-18 17:24:04 -0800368 deviceScreen.removeEventListener("mousedown", onStartDrag);
369 deviceScreen.removeEventListener("mousemove", onContinueDrag);
370 deviceScreen.removeEventListener("mouseup", onEndDrag);
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800371 }
372}
373
Jorge E. Moreiracfd08402020-01-13 18:36:33 -0800374function startKeyboardTracking() {
375 document.addEventListener('keydown', onKeyEvent);
376 document.addEventListener('keyup', onKeyEvent);
377}
378
379function stopKeyboardTracking() {
380 document.removeEventListener('keydown', onKeyEvent);
381 document.removeEventListener('keyup', onKeyEvent);
382}
383
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800384function onStartDrag(e) {
385 e.preventDefault();
386
387 // console.log("mousedown at " + e.pageX + " / " + e.pageY);
388 mouseIsDown = true;
389
390 sendMouseUpdate(true, e);
391}
392
393function onEndDrag(e) {
394 e.preventDefault();
395
396 // console.log("mouseup at " + e.pageX + " / " + e.pageY);
397 mouseIsDown = false;
398
399 sendMouseUpdate(false, e);
400}
401
402function onContinueDrag(e) {
403 e.preventDefault();
404
405 // console.log("mousemove at " + e.pageX + " / " + e.pageY + ", down=" + mouseIsDown);
406 if (mouseIsDown) {
407 sendMouseUpdate(true, e);
408 }
409}
410
411function sendMouseUpdate(down, e) {
Jorge E. Moreira19bfe072019-11-20 15:25:52 -0800412 var x = e.offsetX;
413 var y = e.offsetY;
414
Roger Ellisaeec7402020-02-18 17:24:04 -0800415 const videoWidth = deviceScreen.videoWidth;
416 const videoHeight = deviceScreen.videoHeight;
417 const elementWidth = deviceScreen.width;
418 const elementHeight = deviceScreen.height;
Jorge E. Moreira19bfe072019-11-20 15:25:52 -0800419
420 // vh*ew > eh*vw? then scale h instead of w
421 const scaleHeight = videoHeight * elementWidth > videoWidth * elementHeight;
422 var elementScaling = 0, videoScaling = 0;
423 if (scaleHeight) {
424 elementScaling = elementHeight;
425 videoScaling = videoHeight;
426 } else {
427 elementScaling = elementWidth;
428 videoScaling = videoWidth;
429 }
430
431 // Substract the offset produced by the difference in aspect ratio if any.
432 if (scaleHeight) {
433 x -= (elementWidth - elementScaling * videoWidth / videoScaling) / 2;
434 } else {
435 y -= (elementHeight - elementScaling * videoHeight / videoScaling) / 2;
436 }
437
438 // Convert to coordinates relative to the video
439 x = videoScaling * x / elementScaling;
440 y = videoScaling * y / elementScaling;
Jorge E. Moreiraa18ff1a2019-12-17 18:20:56 -0800441
442 ws.send('{\r\n'
443 + '"type": "set-mouse-position",\r\n'
444 + '"down": ' + (down ? "1" : "0") + ',\r\n'
445 + '"x": ' + Math.trunc(x) + ',\r\n'
446 + '"y": ' + Math.trunc(y) + '\r\n'
447 + '}');
448}
449
Jorge E. Moreiracfd08402020-01-13 18:36:33 -0800450function onKeyEvent(e) {
451 e.preventDefault();
452 ws.send('{"type": "key-event","keycode": "'+e.code+'", "event_type": "'+e.type+'"}');
453}