// Copyright Epic Games, Inc. All Rights Reserved. let webRtcPlayerObj = null; let is_reconnection = false; let connect_on_load = false; let ws; const WS_OPEN_STATE = 1; // TODO: Remove this - workaround because of bug causing UE to crash when switching resolutions too quickly let lastTimeResized = new Date().getTime(); let resizeTimeout; let onDataChannelConnected; let responseEventListeners = new Map(); let shouldShowPlayOverlay = true; // A hidden input text box which is used only for focusing and opening the // on-screen keyboard. let hiddenInput = undefined; let t0 = Date.now(); function log(str) { console.log(`${Math.floor(Date.now() - t0)}: ` + str); } function setOverlay(htmlClass, htmlElement, onClickFunction) { let videoPlayOverlay = document.getElementById('videoPlayOverlay'); if (!videoPlayOverlay) { let playerDiv = document.getElementById('player'); videoPlayOverlay = document.createElement('div'); videoPlayOverlay.id = 'videoPlayOverlay'; playerDiv.appendChild(videoPlayOverlay); } // Remove existing html child elements so we can add the new one while (videoPlayOverlay.lastChild) { videoPlayOverlay.removeChild(videoPlayOverlay.lastChild); } if (htmlElement) videoPlayOverlay.appendChild(htmlElement); if (onClickFunction) { videoPlayOverlay.addEventListener('click', function onOverlayClick(event) { onClickFunction(event); videoPlayOverlay.removeEventListener('click', onOverlayClick); }); } // Remove existing html classes so we can set the new one let cl = videoPlayOverlay.classList; for (let i = cl.length - 1; i >= 0; i--) { cl.remove(cl[i]); } videoPlayOverlay.classList.add(htmlClass); } function showConnectOverlay() { let startText = document.createElement('div'); startText.id = 'playButton'; startText.innerHTML = 'Click to start'; setOverlay('clickableState', startText, event => { connect(); }); } function playVideoStream() { if (webRtcPlayerObj && webRtcPlayerObj.video) { webRtcPlayerObj.video.play().catch(function(onRejectedReason){ console.error(onRejectedReason); console.log("Browser does not support autoplaying video without interaction - to resolve this we are going to show the play button overlay.") shouldShowPlayOverlay = false; }); requestInitialSettings(); requestQualityControl(); hideOverlay(); } else { console.error("Could not player video stream because webRtcPlayerObj.video was not valid.") } } function updateAfkOverlayText() { afk.overlay.innerHTML = '
No activity detected
Disconnecting in ' + afk.countdown + ' seconds
Click to continue
'; } function hideOverlay() { setOverlay('hiddenState'); } function createWebRtcOffer() { if (webRtcPlayerObj) { console.log('Creating offer'); webRtcPlayerObj.createOffer(); } else { console.log('WebRTC player not setup, cannot create offer'); } } function sendInputData(data) { if (webRtcPlayerObj) { webRtcPlayerObj.send(data); } } function addResponseEventListener(name, listener) { responseEventListeners.set(name, listener); } function removeResponseEventListener(name) { responseEventListeners.remove(name); } // Must be kept in sync with PixelStreamingProtocol::EToPlayerMsg C++ enum. const ToClientMessageType = { QualityControlOwnership: 0, Response: 1, Command: 2, FreezeFrame: 3, UnfreezeFrame: 4, VideoEncoderAvgQP: 5, LatencyTest: 6, InitialSettings: 7 }; let VideoEncoderQP = "N/A"; function setupWebRtcPlayer(htmlElement, config) { webRtcPlayerObj = new webRtcPlayer(config); htmlElement.appendChild(webRtcPlayerObj.video); webRtcPlayerObj.onWebRtcOffer = function(offer) { if (ws && ws.readyState === WS_OPEN_STATE) { let offerStr = JSON.stringify(offer); console.log(`-> SS: offer:\n${offerStr}`); ws.send(offerStr); } }; webRtcPlayerObj.onWebRtcCandidate = function(candidate) { if (ws && ws.readyState === WS_OPEN_STATE) { console.log(`-> SS: iceCandidate\n${JSON.stringify(candidate, undefined, 4)}`); ws.send(JSON.stringify({ type: 'iceCandidate', candidate: candidate })); } }; webRtcPlayerObj.onVideoInitialised = function() { if (ws && ws.readyState === WS_OPEN_STATE) { if (shouldShowPlayOverlay) { shouldShowPlayOverlay = false; resizePlayerStyle(); } else { resizePlayerStyle(); playVideoStream(); } } }; webRtcPlayerObj.onDataChannelConnected = function() { if (ws && ws.readyState === WS_OPEN_STATE) { if (webRtcPlayerObj.video && webRtcPlayerObj.video.srcObject && webRtcPlayerObj.onVideoInitialised) { webRtcPlayerObj.onVideoInitialised(); } } }; registerInputs(webRtcPlayerObj.video); createWebRtcOffer(); return webRtcPlayerObj.video; } function onWebRtcAnswer(webRTCData) { webRtcPlayerObj.receiveAnswer(webRTCData); webRtcPlayerObj.aggregateStats(1 * 1000 ); } function onWebRtcIce(iceCandidate) { if (webRtcPlayerObj) webRtcPlayerObj.handleCandidateFromServer(iceCandidate); } let styleWidth; let styleHeight; let styleTop; let styleLeft; let styleCursor = 'default'; let styleAdditional; const ControlSchemeType = { // A mouse can lock inside the WebRTC player so the user can simply move the // mouse to control the orientation of the camera. The user presses the // Escape key to unlock the mouse. LockedMouse: 0, // A mouse can hover over the WebRTC player so the user needs to click and // drag to control the orientation of the camera. HoveringMouse: 1 }; let inputOptions = { // The control scheme controls the behaviour of the mouse when it interacts // with the WebRTC player. controlScheme: ControlSchemeType.LockedMouse, // Browser keys are those which are typically used by the browser UI. We // usually want to suppress these to allow, for example, UE4 to show shader // complexity with the F5 key without the web page refreshing. suppressBrowserKeys: true, // UE4 has a faketouches option which fakes a single finger touch when the // user drags with their mouse. We may perform the reverse; a single finger // touch may be converted into a mouse drag UE4 side. This allows a // non-touch application to be controlled partially via a touch device. fakeMouseWithTouches: false }; function resizePlayerStyleToFillWindow(playerElement) { let videoElement = playerElement.getElementsByTagName("VIDEO"); // Fill the player display in window, keeping picture's aspect ratio. let windowAspectRatio = window.innerHeight / window.innerWidth; let playerAspectRatio = playerElement.clientHeight / playerElement.clientWidth; // We want to keep the video ratio correct for the video stream let videoAspectRatio = videoElement.videoHeight / videoElement.videoWidth; if (isNaN(videoAspectRatio)) { //Video is not initialised yet so set playerElement to size of window styleWidth = window.innerWidth; styleHeight = window.innerHeight; styleTop = 0; styleLeft = 0; playerElement.style = "top: " + styleTop + "px; left: " + styleLeft + "px; width: " + styleWidth + "px; height: " + styleHeight + "px; cursor: " + styleCursor + "; " + styleAdditional; } else if (windowAspectRatio < playerAspectRatio) { // Window height is the constraining factor so to keep aspect ratio change width appropriately styleWidth = Math.floor(window.innerHeight / videoAspectRatio); styleHeight = window.innerHeight; styleTop = 0; styleLeft = Math.floor((window.innerWidth - styleWidth) * 0.5); //Video is now 100% of the playerElement, so set the playerElement style playerElement.style = "top: " + styleTop + "px; left: " + styleLeft + "px; width: " + styleWidth + "px; height: " + styleHeight + "px; cursor: " + styleCursor + "; " + styleAdditional; } else { // Window width is the constraining factor so to keep aspect ratio change height appropriately styleWidth = window.innerWidth; styleHeight = Math.floor(window.innerWidth * videoAspectRatio); styleTop = Math.floor((window.innerHeight - styleHeight) * 0.5); styleLeft = 0; //Video is now 100% of the playerElement, so set the playerElement style playerElement.style = "top: " + styleTop + "px; left: " + styleLeft + "px; width: " + styleWidth + "px; height: " + styleHeight + "px; cursor: " + styleCursor + "; " + styleAdditional; } } function resizePlayerStyleToActualSize(playerElement) { let videoElement = playerElement.getElementsByTagName("VIDEO"); if (videoElement.length > 0) { // Display image in its actual size styleWidth = videoElement[0].videoWidth; styleHeight = videoElement[0].videoHeight; let Top = Math.floor((window.innerHeight - styleHeight) * 0.5); let Left = Math.floor((window.innerWidth - styleWidth) * 0.5); styleTop = (Top > 0) ? Top : 0; styleLeft = (Left > 0) ? Left : 0; //Video is now 100% of the playerElement, so set the playerElement style playerElement.style = "top: " + styleTop + "px; left: " + styleLeft + "px; width: " + styleWidth + "px; height: " + styleHeight + "px; cursor: " + styleCursor + "; " + styleAdditional; } } function resizePlayerStyle(event) { let playerElement = document.getElementById('player'); if (!playerElement) return; resizePlayerStyleToFillWindow(playerElement); //resizePlayerStyleToActualSize(playerElement); setupMouseAndFreezeFrame(playerElement) } function setupMouseAndFreezeFrame(playerElement) { // Calculating and normalizing positions depends on the width and height of // the player. playerElementClientRect = playerElement.getBoundingClientRect(); setupNormalizeAndQuantize(); } // Must be kept in sync with PixelStreamingProtocol::EToUE4Msg C++ enum. const MessageType = { /**********************************************************************/ /* * Control Messages. Range = 0..49. */ IFrameRequest: 0, RequestQualityControl: 1, MaxFpsRequest: 2, AverageBitrateRequest: 3, StartStreaming: 4, StopStreaming: 5, LatencyTest: 6, RequestInitialSettings: 7, /**********************************************************************/ /* * Input Messages. Range = 50..89. */ // Generic Input Messages. Range = 50..59. UIInteraction: 50, Command: 51, // Keyboard Input Message. Range = 60..69. KeyDown: 60, KeyUp: 61, KeyPress: 62, // Mouse Input Messages. Range = 70..79. MouseEnter: 70, MouseLeave: 71, MouseDown: 72, MouseUp: 73, MouseMove: 74, MouseWheel: 75, // Touch Input Messages. Range = 80..89. TouchStart: 80, TouchEnd: 81, TouchMove: 82, // Gamepad Input Messages. Range = 90..99 GamepadButtonPressed: 90, GamepadButtonReleased: 91, GamepadAnalog: 92 /**************************************************************************/ }; function requestInitialSettings() { sendInputData(new Uint8Array([MessageType.RequestInitialSettings]).buffer); } function requestQualityControl() { sendInputData(new Uint8Array([MessageType.RequestQualityControl]).buffer); } let playerElementClientRect = undefined; let normalizeAndQuantizeUnsigned = undefined; let normalizeAndQuantizeSigned = undefined; function setupNormalizeAndQuantize() { let playerElement = document.getElementById('player'); let videoElement = playerElement.getElementsByTagName("video"); if (playerElement && videoElement.length > 0) { let playerAspectRatio = playerElement.clientHeight / playerElement.clientWidth; let videoAspectRatio = videoElement[0].videoHeight / videoElement[0].videoWidth; if (playerAspectRatio > videoAspectRatio) { let ratio = playerAspectRatio / videoAspectRatio; // Unsigned. normalizeAndQuantizeUnsigned = (x, y) => { let normalizedX = x / playerElement.clientWidth; let normalizedY = ratio * (y / playerElement.clientHeight - 0.5) + 0.5; if (normalizedX < 0.0 || normalizedX > 1.0 || normalizedY < 0.0 || normalizedY > 1.0) { return { inRange: false, x: 65535, y: 65535 }; } else { return { inRange: true, x: normalizedX * 65536, y: normalizedY * 65536 }; } }; unquantizeAndDenormalizeUnsigned = (x, y) => { let normalizedX = x / 65536; let normalizedY = (y / 65536 - 0.5) / ratio + 0.5; return { x: normalizedX * playerElement.clientWidth, y: normalizedY * playerElement.clientHeight }; }; // Signed. normalizeAndQuantizeSigned = (x, y) => { let normalizedX = x / (0.5 * playerElement.clientWidth); let normalizedY = (ratio * y) / (0.5 * playerElement.clientHeight); return { x: normalizedX * 32767, y: normalizedY * 32767 }; }; } else { let ratio = videoAspectRatio / playerAspectRatio; // Unsigned. normalizeAndQuantizeUnsigned = (x, y) => { let normalizedX = ratio * (x / playerElement.clientWidth - 0.5) + 0.5; let normalizedY = y / playerElement.clientHeight; if (normalizedX < 0.0 || normalizedX > 1.0 || normalizedY < 0.0 || normalizedY > 1.0) { return { inRange: false, x: 65535, y: 65535 }; } else { return { inRange: true, x: normalizedX * 65536, y: normalizedY * 65536 }; } }; unquantizeAndDenormalizeUnsigned = (x, y) => { let normalizedX = (x / 65536 - 0.5) / ratio + 0.5; let normalizedY = y / 65536; return { x: normalizedX * playerElement.clientWidth, y: normalizedY * playerElement.clientHeight }; }; // Signed. normalizeAndQuantizeSigned = (x, y) => { let normalizedX = (ratio * x) / (0.5 * playerElement.clientWidth); let normalizedY = y / (0.5 * playerElement.clientHeight); return { x: normalizedX * 32767, y: normalizedY * 32767 }; }; } } } function emitMouseMove(x, y, deltaX, deltaY) { let coord = normalizeAndQuantizeUnsigned(x, y); let delta = normalizeAndQuantizeSigned(deltaX, deltaY); let Data = new DataView(new ArrayBuffer(9)); Data.setUint8(0, MessageType.MouseMove); Data.setUint16(1, coord.x, true); Data.setUint16(3, coord.y, true); Data.setInt16(5, delta.x, true); Data.setInt16(7, delta.y, true); sendInputData(Data.buffer); } function emitMouseDown(button, x, y) { let coord = normalizeAndQuantizeUnsigned(x, y); let Data = new DataView(new ArrayBuffer(6)); Data.setUint8(0, MessageType.MouseDown); Data.setUint8(1, button); Data.setUint16(2, coord.x, true); Data.setUint16(4, coord.y, true); sendInputData(Data.buffer); } function emitMouseUp(button, x, y) { let coord = normalizeAndQuantizeUnsigned(x, y); let Data = new DataView(new ArrayBuffer(6)); Data.setUint8(0, MessageType.MouseUp); Data.setUint8(1, button); Data.setUint16(2, coord.x, true); Data.setUint16(4, coord.y, true); sendInputData(Data.buffer); } function emitMouseWheel(delta, x, y) { let coord = normalizeAndQuantizeUnsigned(x, y); let Data = new DataView(new ArrayBuffer(7)); Data.setUint8(0, MessageType.MouseWheel); Data.setInt16(1, delta, true); Data.setUint16(3, coord.x, true); Data.setUint16(5, coord.y, true); sendInputData(Data.buffer); } // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button const MouseButton = { MainButton: 0, // Left button. AuxiliaryButton: 1, // Wheel button. SecondaryButton: 2, // Right button. FourthButton: 3, // Browser Back button. FifthButton: 4 // Browser Forward button. }; // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons const MouseButtonsMask = { PrimaryButton: 1, // Left button. SecondaryButton: 2, // Right button. AuxiliaryButton: 4, // Wheel button. FourthButton: 8, // Browser Back button. FifthButton: 16 // Browser Forward button. }; // If the user has any mouse buttons pressed then release them. function releaseMouseButtons(buttons, x, y) { if (buttons & MouseButtonsMask.PrimaryButton) { emitMouseUp(MouseButton.MainButton, x, y); } if (buttons & MouseButtonsMask.SecondaryButton) { emitMouseUp(MouseButton.SecondaryButton, x, y); } if (buttons & MouseButtonsMask.AuxiliaryButton) { emitMouseUp(MouseButton.AuxiliaryButton, x, y); } if (buttons & MouseButtonsMask.FourthButton) { emitMouseUp(MouseButton.FourthButton, x, y); } if (buttons & MouseButtonsMask.FifthButton) { emitMouseUp(MouseButton.FifthButton, x, y); } } // If the user has any mouse buttons pressed then press them again. function pressMouseButtons(buttons, x, y) { if (buttons & MouseButtonsMask.PrimaryButton) { emitMouseDown(MouseButton.MainButton, x, y); } if (buttons & MouseButtonsMask.SecondaryButton) { emitMouseDown(MouseButton.SecondaryButton, x, y); } if (buttons & MouseButtonsMask.AuxiliaryButton) { emitMouseDown(MouseButton.AuxiliaryButton, x, y); } if (buttons & MouseButtonsMask.FourthButton) { emitMouseDown(MouseButton.FourthButton, x, y); } if (buttons & MouseButtonsMask.FifthButton) { emitMouseDown(MouseButton.FifthButton, x, y); } } function registerInputs(playerElement) { if (!playerElement) return; registerMouseEnterAndLeaveEvents(playerElement); } function registerMouseEnterAndLeaveEvents(playerElement) { playerElement.onmouseenter = function(e) { let Data = new DataView(new ArrayBuffer(1)); Data.setUint8(0, MessageType.MouseEnter); sendInputData(Data.buffer); playerElement.pressMouseButtons(e); }; playerElement.onmouseleave = function(e) { let Data = new DataView(new ArrayBuffer(1)); Data.setUint8(0, MessageType.MouseLeave); sendInputData(Data.buffer); playerElement.releaseMouseButtons(e); }; } // A locked mouse works by the user clicking in the browser player and the // cursor disappears and is locked. The user moves the cursor and the camera // moves, for example. The user presses escape to free the mouse. function registerLockedMouseEvents(playerElement) { let x = playerElement.width / 2; let y = playerElement.height / 2; playerElement.requestPointerLock = playerElement.requestPointerLock || playerElement.mozRequestPointerLock; document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock; playerElement.onclick = function() { playerElement.requestPointerLock(); }; // Respond to lock state change events document.addEventListener('pointerlockchange', lockStateChange, false); document.addEventListener('mozpointerlockchange', lockStateChange, false); function lockStateChange() { if (document.pointerLockElement === playerElement || document.mozPointerLockElement === playerElement) { console.log('Pointer locked'); document.addEventListener("mousemove", updatePosition, false); } else { console.log('The pointer lock status is now unlocked'); document.removeEventListener("mousemove", updatePosition, false); } } function updatePosition(e) { x += e.movementX; y += e.movementY; if (x > styleWidth) { x -= styleWidth; } if (y > styleHeight) { y -= styleHeight; } if (x < 0) { x = styleWidth + x; } if (y < 0) { y = styleHeight - y; } emitMouseMove(x, y, e.movementX, e.movementY); } playerElement.onmousedown = function(e) { emitMouseDown(e.button, x, y); }; playerElement.onmouseup = function(e) { emitMouseUp(e.button, x, y); }; playerElement.onmousewheel = function(e) { emitMouseWheel(e.wheelDelta, x, y); }; playerElement.pressMouseButtons = function(e) { pressMouseButtons(e.buttons, x, y); }; playerElement.releaseMouseButtons = function(e) { releaseMouseButtons(e.buttons, x, y); }; } // A hovering mouse works by the user clicking the mouse button when they want // the cursor to have an effect over the video. Otherwise the cursor just // passes over the browser. function registerHoveringMouseEvents(playerElement) { styleCursor = 'none'; // We will rely on UE4 client's software cursor. //styleCursor = 'default'; // Showing cursor playerElement.onmousemove = function(e) { emitMouseMove(e.offsetX, e.offsetY, e.movementX, e.movementY); e.preventDefault(); }; playerElement.onmousedown = function(e) { emitMouseDown(e.button, e.offsetX, e.offsetY); e.preventDefault(); }; playerElement.onmouseup = function(e) { emitMouseUp(e.button, e.offsetX, e.offsetY); e.preventDefault(); }; // When the context menu is shown then it is safest to release the button // which was pressed when the event happened. This will guarantee we will // get at least one mouse up corresponding to a mouse down event. Otherwise // the mouse can get stuck. // https://github.com/facebook/react/issues/5531 playerElement.oncontextmenu = function(e) { emitMouseUp(e.button, e.offsetX, e.offsetY); e.preventDefault(); }; if ('onmousewheel' in playerElement) { playerElement.onmousewheel = function(e) { emitMouseWheel(e.wheelDelta, e.offsetX, e.offsetY); e.preventDefault(); }; } else { playerElement.addEventListener('DOMMouseScroll', function(e) { emitMouseWheel(e.detail * -120, e.offsetX, e.offsetY); e.preventDefault(); }, false); } playerElement.pressMouseButtons = function(e) { pressMouseButtons(e.buttons, e.offsetX, e.offsetY); }; playerElement.releaseMouseButtons = function(e) { releaseMouseButtons(e.buttons, e.offsetX, e.offsetY); }; } // Browser keys do not have a charCode so we only need to test keyCode. function isKeyCodeBrowserKey(keyCode) { // Function keys or tab key. return keyCode >= 112 && keyCode <= 123 || keyCode === 9; } // Must be kept in sync with JavaScriptKeyCodeToFKey C++ array. The index of the // entry in the array is the special key code given below. const SpecialKeyCodes = { BackSpace: 8, Shift: 16, Control: 17, Alt: 18, RightShift: 253, RightControl: 254, RightAlt: 255 }; // We want to be able to differentiate between left and right versions of some // keys. function getKeyCode(e) { if (e.keyCode === SpecialKeyCodes.Shift && e.code === 'ShiftRight') return SpecialKeyCodes.RightShift; else if (e.keyCode === SpecialKeyCodes.Control && e.code === 'ControlRight') return SpecialKeyCodes.RightControl; else if (e.keyCode === SpecialKeyCodes.Alt && e.code === 'AltRight') return SpecialKeyCodes.RightAlt; else return e.keyCode; } function registerKeyboardEvents() { document.onkeydown = function(e) { sendInputData(new Uint8Array([MessageType.KeyDown, getKeyCode(e), e.repeat]).buffer); // Backspace is not considered a keypress in JavaScript but we need it // to be so characters may be deleted in a UE4 text entry field. if (e.keyCode === SpecialKeyCodes.BackSpace) { document.onkeypress({ charCode: SpecialKeyCodes.BackSpace }); } if (inputOptions.suppressBrowserKeys && isKeyCodeBrowserKey(e.keyCode)) { e.preventDefault(); } }; document.onkeyup = function(e) { sendInputData(new Uint8Array([MessageType.KeyUp, getKeyCode(e)]).buffer); if (inputOptions.suppressBrowserKeys && isKeyCodeBrowserKey(e.keyCode)) { e.preventDefault(); } }; document.onkeypress = function(e) { let data = new DataView(new ArrayBuffer(3)); data.setUint8(0, MessageType.KeyPress); data.setUint16(1, e.charCode, true); sendInputData(data.buffer); }; } function start() { showConnectOverlay(); if (webRtcPlayerObj) { webRtcPlayerObj.setVideoEnabled(true); } if (!connect_on_load || is_reconnection) { shouldShowPlayOverlay = true; resizePlayerStyle(); } else { connect(); } // connect(); setTimeout(connect, 2000); } function connect() { "use strict"; window.WebSocket = window.WebSocket || window.MozWebSocket; if (!window.WebSocket) { alert('Your browser doesn\'t support WebSocket'); return; } // ws = new WebSocket(window.location.href.replace('http://', 'ws://').replace('https://', 'wss://')); // ws = new WebSocket(window.location.href.replace('http://', 'ws://').replace('https://', 'wss://')); let connectionUrl = window.location.href.replace('http://', 'ws://').replace('https://', 'wss://').replace(/:\d+/, ''); console.log(`Creating a websocket connection to: ${connectionUrl}`); ws = new WebSocket(connectionUrl); ws.attemptStreamReconnection = true; ws.onmessage = function(event) { console.log(`<- SS: ${event.data}`); let msg = JSON.parse(event.data); if (msg.type === 'config') { onConfig(msg); } else if (msg.type === 'answer') { onWebRtcAnswer(msg); } else if (msg.type === 'iceCandidate') { onWebRtcIce(msg.candidate); } else { console.log(`invalid SS message type: ${msg.type}`); } }; ws.onerror = function(event) { console.log(`WS error: ${JSON.stringify(event)}`); }; ws.onclose = function(event) { console.log(`WS closed: ${JSON.stringify(event.code)} - ${event.reason}`); ws = undefined; is_reconnection = true; // destroy `webRtcPlayerObj` if any let playerDiv = document.getElementById('player'); if (webRtcPlayerObj) { playerDiv.removeChild(webRtcPlayerObj.video); webRtcPlayerObj.close(); webRtcPlayerObj = undefined; } setTimeout(start, 4000); }; } // Config data received from WebRTC sender via the Cirrus web server function onConfig(config) { let playerDiv = document.getElementById('player'); let playerElement = setupWebRtcPlayer(playerDiv, config); resizePlayerStyle(); switch (inputOptions.controlScheme) { case ControlSchemeType.HoveringMouse: registerHoveringMouseEvents(playerElement); break; case ControlSchemeType.LockedMouse: registerLockedMouseEvents(playerElement); break; default: console.log(`ERROR: Unknown control scheme ${inputOptions.controlScheme}`); registerLockedMouseEvents(playerElement); break; } } function load() { registerKeyboardEvents(); start(); }