12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943 |
- // Copyright Epic Games, Inc. All Rights Reserved.
- // Window events for a gamepad connecting
- let haveEvents = 'GamepadEvent' in window;
- let haveWebkitEvents = 'WebKitGamepadEvent' in window;
- let controllers = {};
- let rAF = window.mozRequestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.requestAnimationFrame;
- let kbEvent = document.createEvent("KeyboardEvent");
- let initMethod = typeof kbEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
- let webRtcPlayerObj = null;
- let print_stats = false;
- let print_inputs = false;
- let connect_on_load = true;
- let is_reconnection = false;
- let ws;
- const WS_OPEN_STATE = 1;
- let qualityControlOwnershipCheckBox;
- let matchViewportResolution;
- // 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 freezeFrameOverlay = null;
- let shouldShowPlayOverlay = true;
- // A freeze frame is a still JPEG image shown instead of the video.
- let freezeFrame = {
- receiving: false,
- size: 0,
- jpeg: undefined,
- height: 0,
- width: 0,
- valid: false
- };
- // Optionally detect if the user is not interacting (AFK) and disconnect them.
- let afk = {
- enabled: false, // Set to true to enable the AFK system.
- warnTimeout: 120, // The time to elapse before warning the user they are inactive.
- closeTimeout: 10, // The time after the warning when we disconnect the user.
- active: false, // Whether the AFK system is currently looking for inactivity.
- overlay: undefined, // The UI overlay warning the user that they are inactive.
- warnTimer: undefined, // The timer which waits to show the inactivity warning overlay.
- countdown: 0, // The inactivity warning overlay has a countdown to show time until disconnect.
- countdownTimer: undefined, // The timer used to tick the seconds shown on the inactivity warning overlay.
- }
- // If the user focuses on a UE4 input widget then we show them a button to open
- // the on-screen keyboard. JavaScript security means we can only show the
- // on-screen keyboard in response to a user interaction.
- let editTextButton = undefined;
- // 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 scanGamepads() {
- let gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
- for (let i = 0; i < gamepads.length; i++) {
- if (gamepads[i] && (gamepads[i].index in controllers)) {
- controllers[gamepads[i].index].currentState = gamepads[i];
- }
- }
- }
- function updateStatus() {
- scanGamepads();
- // Iterate over multiple controllers in the case the mutiple gamepads are connected
- for (j in controllers) {
- let controller = controllers[j];
- let currentState = controller.currentState;
- let prevState = controller.prevState;
- // Iterate over buttons
- for (let i = 0; i < currentState.buttons.length; i++) {
- let currButton = currentState.buttons[i];
- let prevButton = prevState.buttons[i];
- // Button 6 is actually the left trigger, send it to UE as an analog axis
- // Button 7 is actually the right trigger, send it to UE as an analog axis
- // The rest are normal buttons. Treat as such
- if (currButton.pressed && !prevButton.pressed) {
- // New press
- if (i == 6) {
- emitControllerAxisMove(j, 5, currButton.value);
- } else if (i == 7) {
- emitControllerAxisMove(j, 6, currButton.value);
- } else {
- emitControllerButtonPressed(j, i, 0);
- }
- } else if (!currButton.pressed && prevButton.pressed) {
- // release
- if (i == 6) {
- emitControllerAxisMove(j, 5, 0);
- } else if (i == 7) {
- emitControllerAxisMove(j, 6, 0);
- } else {
- emitControllerButtonReleased(j, i);
- }
- } else if (currButton.pressed && prevButton.pressed) {
- // repeat press / hold
- if (i == 6) {
- emitControllerAxisMove(j, 5, currButton.value);
- } else if (i == 7) {
- emitControllerAxisMove(j, 6, currButton.value);
- } else {
- emitControllerButtonPressed(j, i, 1);
- }
- }
- // Last case is button isn't currently pressed and wasn't pressed before. This doesn't need an else block
- }
- // Iterate over gamepad axes
- for (let i = 0; i < currentState.axes.length; i += 2) {
- let x = parseFloat(currentState.axes[i].toFixed(4));
- // https://w3c.github.io/gamepad/#remapping Gamepad broweser side standard mapping has positive down, negative up. This is downright disgusting. So we fix it.
- let y = -parseFloat(currentState.axes[i + 1].toFixed(4));
- if (i === 0) {
- // left stick
- // axis 1 = left horizontal
- emitControllerAxisMove(j, 1, x);
- // axis 2 = left vertical
- emitControllerAxisMove(j, 2, y);
- } else if (i === 2) {
- // right stick
- // axis 3 = right horizontal
- emitControllerAxisMove(j, 3, x);
- // axis 4 = right vertical
- emitControllerAxisMove(j, 4, y);
- }
- }
- controllers[j].prevState = currentState;
- }
- rAF(updateStatus);
- }
- function emitControllerButtonPressed(controllerIndex, buttonIndex, isRepeat) {
- Data = new DataView(new ArrayBuffer(4));
- Data.setUint8(0, MessageType.GamepadButtonPressed);
- Data.setUint8(1, controllerIndex);
- Data.setUint8(2, buttonIndex);
- Data.setUint8(3, isRepeat);
- }
- function emitControllerButtonReleased(controllerIndex, buttonIndex) {
- Data = new DataView(new ArrayBuffer(3));
- Data.setUint8(0, MessageType.GamepadButtonReleased);
- Data.setUint8(1, controllerIndex);
- Data.setUint8(2, buttonIndex);
- }
- function emitControllerAxisMove(controllerIndex, axisIndex, analogValue) {
- Data = new DataView(new ArrayBuffer(11));
- Data.setUint8(0, MessageType.GamepadAnalog);
- Data.setUint8(1, controllerIndex);
- Data.setUint8(2, axisIndex);
- Data.setFloat64(3, analogValue, true);
- sendInputData(Data.buffer);
- }
- function gamepadConnectHandler(e) {
- console.log("Gamepad connect handler");
- gamepad = e.gamepad;
- controllers[gamepad.index] = {};
- controllers[gamepad.index].currentState = gamepad;
- controllers[gamepad.index].prevState = gamepad;
- console.log("gamepad: " + gamepad.id + " connected");
- rAF(updateStatus);
- }
- function gamepadDisconnectHandler(e) {
- console.log("Gamepad disconnect handler");
- console.log("gamepad: " + e.gamepad.id + " disconnected");
- delete controllers[e.gamepad.index];
- }
- function setupHtmlEvents() {
- //Window events
- window.addEventListener('resize', resizePlayerStyle, true);
- window.addEventListener('orientationchange', onOrientationChange);
- //Gamepad events
- if (haveEvents) {
- window.addEventListener("gamepadconnected", gamepadConnectHandler);
- window.addEventListener("gamepaddisconnected", gamepadDisconnectHandler);
- } else if (haveWebkitEvents) {
- window.addEventListener("webkitgamepadconnected", gamepadConnectHandler);
- window.addEventListener("webkitgamepaddisconnected", gamepadDisconnectHandler);
- }
- //HTML elements controls
- let overlayButton = document.getElementById('overlayButton');
- overlayButton.addEventListener('click', onExpandOverlay_Click);
- let resizeCheckBox = document.getElementById('enlarge-display-to-fill-window-tgl');
- if (resizeCheckBox !== null) {
- resizeCheckBox.onchange = function (event) {
- resizePlayerStyle();
- };
- }
- qualityControlOwnershipCheckBox = document.getElementById('quality-control-ownership-tgl');
- if (qualityControlOwnershipCheckBox !== null) {
- qualityControlOwnershipCheckBox.onchange = function (event) {
- requestQualityControl();
- };
- }
- let encoderParamsSubmit = document.getElementById('encoder-params-submit');
- if (encoderParamsSubmit !== null) {
- encoderParamsSubmit.onclick = function (event) {
- let rateControl = document.getElementById('encoder-rate-control').value;
- let targetBitrate = document.getElementById('encoder-target-bitrate-text').value * 1000;
- let maxBitrate = document.getElementById('encoder-max-bitrate-text').value * 1000;
- let minQP = document.getElementById('encoder-min-qp-text').value;
- let maxQP = document.getElementById('encoder-max-qp-text').value;
- let fillerData = document.getElementById('encoder-filler-data-tgl').checked ? 1 : 0;
- let multipass = document.getElementById('encoder-multipass').value;
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.RateControl ' + rateControl });
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.TargetBitrate ' + targetBitrate > 0 ? targetBitrate : -1 });
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.MaxBitrateVBR ' + maxBitrate > 0 ? maxBitrate : -1 });
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.MinQP ' + minQP });
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.MaxQP ' + maxQP });
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.EnableFillerData ' + fillerData });
- emitUIInteraction({ Console: 'PixelStreaming.Encoder.Multipass ' + multipass });
- };
- }
- let webrtcParamsSubmit = document.getElementById('webrtc-params-submit');
- if (webrtcParamsSubmit !== null) {
- webrtcParamsSubmit.onclick = function (event) {
- let degradationPref = document.getElementById('webrtc-degradation-pref').value;
- let maxFPS = document.getElementById('webrtc-max-fps-text').value;
- let minBitrate = document.getElementById('webrtc-min-bitrate-text').value * 1000;
- let maxBitrate = document.getElementById('webrtc-max-bitrate-text').value * 1000;
- let lowQP = document.getElementById('webrtc-low-qp-text').value;
- let highQP = document.getElementById('webrtc-high-qp-text').value;
- emitUIInteraction({ Console: 'PixelStreaming.WebRTC.DegradationPreference ' + degradationPref });
- emitUIInteraction({ Console: 'PixelStreaming.WebRTC.MaxFps ' + maxFPS });
- emitUIInteraction({ Console: 'PixelStreaming.WebRTC.MinBitrate ' + minBitrate });
- emitUIInteraction({ Console: 'PixelStreaming.WebRTC.MaxBitrate ' + maxBitrate });
- emitUIInteraction({ Console: 'PixelStreaming.WebRTC.LowQpThreshold ' + lowQP });
- emitUIInteraction({ Console: 'PixelStreaming.WebRTC.HighQpThreshold ' + highQP });
- };
- }
- let showFPSButton = document.getElementById('show-fps-button');
- if (showFPSButton !== null) {
- showFPSButton.onclick = function (event) {
- let consoleDescriptor = {
- Console: 'stat fps'
- };
- emitUIInteraction(consoleDescriptor);
- };
- }
- let matchViewportResolutionCheckBox = document.getElementById('match-viewport-res-tgl');
- if (matchViewportResolutionCheckBox !== null) {
- matchViewportResolutionCheckBox.onchange = function (event) {
- matchViewportResolution = matchViewportResolutionCheckBox.checked;
- };
- }
- let statsCheckBox = document.getElementById('show-stats-tgl');
- if (statsCheckBox !== null) {
- statsCheckBox.onchange = function (event) {
- let stats = document.getElementById('statsContainer');
- stats.style.display = event.target.checked ? "block" : "none";
- };
- }
- let kickButton = document.getElementById('kick-other-players-button');
- if (kickButton) {
- kickButton.onclick = function (event) {
- console.log(`-> SS: kick`);
- ws.send(JSON.stringify({
- type: 'kick'
- }));
- };
- }
- let latencyButton = document.getElementById('test-latency-button');
- if (latencyButton) {
- latencyButton.onclick = () => {
- sendStartLatencyTest();
- };
- }
- }
- function sendStartLatencyTest() {
- // We need WebRTC to be active to do a latency test.
- if (!webRtcPlayerObj) {
- return;
- }
- let onTestStarted = function (StartTimeMs) {
- let descriptor = {
- StartTime: StartTimeMs
- };
- emitDescriptor(MessageType.LatencyTest, descriptor);
- };
- webRtcPlayerObj.startLatencyTest(onTestStarted);
- }
- 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();
- startAfkWarningTimer();
- });
- }
- function showTextOverlay(text) {
- let textOverlay = document.createElement('div');
- textOverlay.id = 'messageOverlay';
- textOverlay.innerHTML = text ? text : '';
- setOverlay('textDisplayState', textOverlay);
- }
- 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.")
- showPlayOverlay();
- });
- requestInitialSettings();
- requestQualityControl();
- showFreezeFrameOverlay();
- hideOverlay();
- } else {
- console.error("Could not player video stream because webRtcPlayerObj.video was not valid.")
- }
- }
- function showPlayOverlay() {
- let img = document.createElement('img');
- img.id = 'playButton';
- img.src = '/images/Play.png';
- img.alt = 'Start Streaming';
- setOverlay('clickableState', img, event => {
- playVideoStream();
- });
- shouldShowPlayOverlay = false;
- }
- function updateAfkOverlayText() {
- afk.overlay.innerHTML = '<center>No activity detected<br>Disconnecting in ' + afk.countdown + ' seconds<br>Click to continue<br></center>';
- }
- function showAfkOverlay() {
- // Pause the timer while the user is looking at the inactivity warning overlay.
- stopAfkWarningTimer();
- // Show the inactivity warning overlay.
- afk.overlay = document.createElement('div');
- afk.overlay.id = 'afkOverlay';
- setOverlay('clickableState', afk.overlay, event => {
- // The user clicked so start the timer again and carry on.
- hideOverlay();
- clearInterval(afk.countdownTimer);
- startAfkWarningTimer();
- });
- afk.countdown = afk.closeTimeout;
- updateAfkOverlayText();
- if (inputOptions.controlScheme == ControlSchemeType.HoveringMouse) {
- document.exitPointerLock();
- }
- inputOptions.controlScheme = ControlSchemeType.HoveringMouse;
- afk.countdownTimer = setInterval(function () {
- afk.countdown--;
- if (afk.countdown == 0) {
- // The user failed to click so disconnect them.
- hideOverlay();
- ws.close();
- } else {
- // Update the countdown message.
- updateAfkOverlayText();
- }
- }, 1000);
- }
- function hideOverlay() {
- setOverlay('hiddenState');
- }
- // Start a timer which when elapsed will warn the user they are inactive.
- function startAfkWarningTimer() {
- afk.active = afk.enabled;
- resetAfkWarningTimer();
- }
- // Stop the timer which when elapsed will warn the user they are inactive.
- function stopAfkWarningTimer() {
- afk.active = false;
- }
- // If the user interacts then reset the warning timer.
- function resetAfkWarningTimer() {
- if (afk.active) {
- clearTimeout(afk.warnTimer);
- afk.warnTimer = setTimeout(function () {
- showAfkOverlay();
- }, afk.warnTimeout * 1000);
- }
- }
- function createWebRtcOffer() {
- if (webRtcPlayerObj) {
- console.log('Creating offer');
- showTextOverlay('Starting connection to server, please wait');
- webRtcPlayerObj.createOffer();
- } else {
- console.log('WebRTC player not setup, cannot create offer');
- showTextOverlay('Unable to setup video');
- }
- }
- function sendInputData(data) {
- if (webRtcPlayerObj) {
- resetAfkWarningTimer();
- webRtcPlayerObj.send(data);
- }
- }
- function addResponseEventListener(name, listener) {
- responseEventListeners.set(name, listener);
- }
- function removeResponseEventListener(name) {
- // console.log('ssss',responseEventListeners);
- // responseEventListeners.set(name, function () { });
- 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);
- htmlElement.appendChild(freezeFrameOverlay);
- 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) {
- showPlayOverlay();
- resizePlayerStyle();
- }
- else {
- resizePlayerStyle();
- playVideoStream();
- }
- }
- };
- webRtcPlayerObj.onDataChannelConnected = function () {
- if (ws && ws.readyState === WS_OPEN_STATE) {
- showTextOverlay('WebRTC connected, waiting for video');
- if (webRtcPlayerObj.video && webRtcPlayerObj.video.srcObject && webRtcPlayerObj.onVideoInitialised) {
- webRtcPlayerObj.onVideoInitialised();
- }
- }
- };
- function showFreezeFrame() {
- let base64 = btoa(freezeFrame.jpeg.reduce((data, byte) => data + String.fromCharCode(byte), ''));
- let freezeFrameImage = document.getElementById("freezeFrameOverlay").childNodes[0];
- freezeFrameImage.src = 'data:image/jpeg;base64,' + base64;
- freezeFrameImage.onload = function () {
- freezeFrame.height = freezeFrameImage.naturalHeight;
- freezeFrame.width = freezeFrameImage.naturalWidth;
- resizeFreezeFrameOverlay();
- if (shouldShowPlayOverlay) {
- showPlayOverlay();
- resizePlayerStyle();
- } else {
- showFreezeFrameOverlay();
- }
- webRtcPlayerObj.setVideoEnabled(false);
- };
- }
- function processFreezeFrameMessage(view) {
- // Reset freeze frame if we got a freeze frame message and we are not "receiving" yet.
- if (!freezeFrame.receiving) {
- freezeFrame.receiving = true;
- freezeFrame.valid = false;
- freezeFrame.size = 0;
- freezeFrame.jpeg = undefined;
- }
- // Extract total size of freeze frame (across all chunks)
- freezeFrame.size = (new DataView(view.slice(1, 5).buffer)).getInt32(0, true);
- // Get the jpeg part of the payload
- let jpegBytes = view.slice(1 + 4);
- // Append to existing jpeg that holds the freeze frame
- if (freezeFrame.jpeg) {
- let jpeg = new Uint8Array(freezeFrame.jpeg.length + jpegBytes.length);
- jpeg.set(freezeFrame.jpeg, 0);
- jpeg.set(jpegBytes, freezeFrame.jpeg.length);
- freezeFrame.jpeg = jpeg;
- }
- // No existing freeze frame jpeg, make one
- else {
- freezeFrame.jpeg = jpegBytes;
- freezeFrame.receiving = true;
- console.log(`received first chunk of freeze frame: ${freezeFrame.jpeg.length}/${freezeFrame.size}`);
- }
- // Uncomment for debug
- //console.log(`Received freeze frame chunk: ${freezeFrame.jpeg.length}/${freezeFrame.size}`);
- // Finished receiving freeze frame, we can show it now
- if (freezeFrame.jpeg.length === freezeFrame.size) {
- freezeFrame.receiving = false;
- freezeFrame.valid = true;
- console.log(`received complete freeze frame ${freezeFrame.size}`);
- showFreezeFrame();
- }
- // We received more data than the freeze frame payload message indicate (this is an error)
- else if (freezeFrame.jpeg.length > freezeFrame.size) {
- console.error(`received bigger freeze frame than advertised: ${freezeFrame.jpeg.length}/${freezeFrame.size}`);
- freezeFrame.jpeg = undefined;
- freezeFrame.receiving = false;
- }
- }
- webRtcPlayerObj.onDataChannelMessage = function (data) {
- let view = new Uint8Array(data);
- if (view[0] === ToClientMessageType.QualityControlOwnership) {
- let ownership = view[1] === 0 ? false : true;
- console.log("Received quality controller message, will control quality: " + ownership);
- // If we own the quality control, we can't relenquish it. We only loose
- // quality control when another peer asks for it
- if (qualityControlOwnershipCheckBox !== null) {
- qualityControlOwnershipCheckBox.disabled = ownership;
- qualityControlOwnershipCheckBox.checked = ownership;
- }
- } else if (view[0] === ToClientMessageType.Response) {
- let response = new TextDecoder("utf-16").decode(data.slice(1));
- for (let listener of responseEventListeners.values()) {
- listener(response);
- }
- } else if (view[0] === ToClientMessageType.Command) {
- let commandAsString = new TextDecoder("utf-16").decode(data.slice(1));
- console.log(commandAsString);
- let command = JSON.parse(commandAsString);
- if (command.command === 'onScreenKeyboard') {
- showOnScreenKeyboard(command);
- }
- } else if (view[0] === ToClientMessageType.FreezeFrame) {
- processFreezeFrameMessage(view);
- } else if (view[0] === ToClientMessageType.UnfreezeFrame) {
- invalidateFreezeFrameOverlay();
- } else if (view[0] === ToClientMessageType.VideoEncoderAvgQP) {
- VideoEncoderQP = new TextDecoder("utf-16").decode(data.slice(1));
- //console.log(`received VideoEncoderAvgQP ${VideoEncoderQP}`);
- } else if (view[0] == ToClientMessageType.LatencyTest) {
- let latencyTimingsAsString = new TextDecoder("utf-16").decode(data.slice(1));
- console.log("Got latency timings from UE.")
- console.log(latencyTimingsAsString);
- let latencyTimingsFromUE = JSON.parse(latencyTimingsAsString);
- if (webRtcPlayerObj) {
- webRtcPlayerObj.latencyTestTimings.SetUETimings(latencyTimingsFromUE);
- }
- } else if (view[0] == ToClientMessageType.InitialSettings) {
- let settingsString = new TextDecoder("utf-16").decode(data.slice(1));
- let settingsJSON = JSON.parse(settingsString);
- // reminder bitrates are sent in bps but displayed in kbps
- if (settingsJSON.Encoder) {
- document.getElementById('encoder-rate-control').value = settingsJSON.Encoder.RateControl;
- document.getElementById('encoder-target-bitrate-text').value = settingsJSON.Encoder.TargetBitrate > 0 ? settingsJSON.Encoder.TargetBitrate / 1000 : settingsJSON.Encoder.TargetBitrate;
- document.getElementById('encoder-max-bitrate-text').value = settingsJSON.Encoder.MaxBitrate > 0 ? settingsJSON.Encoder.MaxBitrate / 1000 : settingsJSON.Encoder.MaxBitrate;
- document.getElementById('encoder-min-qp-text').value = settingsJSON.Encoder.MinQP;
- document.getElementById('encoder-max-qp-text').value = settingsJSON.Encoder.MaxQP;
- document.getElementById('encoder-filler-data-tgl').checked = settingsJSON.Encoder.FillerData == 1;
- document.getElementById('encoder-multipass').value = settingsJSON.Encoder.MultiPass;
- }
- if (settingsJSON.WebRTC) {
- document.getElementById('webrtc-degradation-pref').value = settingsJSON.WebRTC.DegradationPref;
- document.getElementById("webrtc-max-fps-text").value = settingsJSON.WebRTC.MaxFPS;
- document.getElementById("webrtc-min-bitrate-text").value = settingsJSON.WebRTC.MinBitrate / 1000;
- document.getElementById("webrtc-max-bitrate-text").value = settingsJSON.WebRTC.MaxBitrate / 1000;
- document.getElementById("webrtc-low-qp-text").value = settingsJSON.WebRTC.LowQP;
- document.getElementById("webrtc-high-qp-text").value = settingsJSON.WebRTC.HighQP;
- }
- } else {
- console.error(`unrecognized data received, packet ID ${view[0]}`);
- }
- };
- registerInputs(webRtcPlayerObj.video);
- // On a touch device we will need special ways to show the on-screen keyboard.
- if ('ontouchstart' in document.documentElement) {
- createOnScreenKeyboardHelpers(htmlElement);
- }
- createWebRtcOffer();
- return webRtcPlayerObj.video;
- }
- function onWebRtcAnswer(webRTCData) {
- webRtcPlayerObj.receiveAnswer(webRTCData);
- let printInterval = 5 * 60 * 1000; /*Print every 5 minutes*/
- let nextPrintDuration = printInterval;
- webRtcPlayerObj.onAggregatedStats = (aggregatedStats) => {
- let numberFormat = new Intl.NumberFormat(window.navigator.language, {
- maximumFractionDigits: 0
- });
- let timeFormat = new Intl.NumberFormat(window.navigator.language, {
- maximumFractionDigits: 0,
- minimumIntegerDigits: 2
- });
- // Calculate duration of run
- let runTime = (aggregatedStats.timestamp - aggregatedStats.timestampStart) / 1000;
- let timeValues = [];
- let timeDurations = [60, 60];
- for (let timeIndex = 0; timeIndex < timeDurations.length; timeIndex++) {
- timeValues.push(runTime % timeDurations[timeIndex]);
- runTime = runTime / timeDurations[timeIndex];
- }
- timeValues.push(runTime);
- let runTimeSeconds = timeValues[0];
- let runTimeMinutes = Math.floor(timeValues[1]);
- let runTimeHours = Math.floor([timeValues[2]]);
- receivedBytesMeasurement = 'B';
- receivedBytes = aggregatedStats.hasOwnProperty('bytesReceived') ? aggregatedStats.bytesReceived : 0;
- let dataMeasurements = ['kB', 'MB', 'GB'];
- for (let index = 0; index < dataMeasurements.length; index++) {
- if (receivedBytes < 100 * 1000)
- break;
- receivedBytes = receivedBytes / 1000;
- receivedBytesMeasurement = dataMeasurements[index];
- }
- let qualityStatus = document.getElementById("qualityStatus");
- // "blinks" quality status element for 1 sec by making it transparent, speed = number of blinks
- let blinkQualityStatus = function (speed) {
- let iter = speed;
- let opacity = 1; // [0..1]
- let tickId = setInterval(
- function () {
- opacity -= 0.1;
- // map `opacity` to [-0.5..0.5] range, decrement by 0.2 per step and take `abs` to make it blink: 1 -> 0 -> 1
- qualityStatus.style = `opacity: ${Math.abs((opacity - 0.5) * 2)}`;
- if (opacity <= 0.1) {
- if (--iter == 0) {
- clearInterval(tickId);
- } else { // next blink
- opacity = 1;
- }
- }
- },
- 100 / speed // msecs
- );
- };
- const orangeQP = 26;
- const redQP = 35;
- let statsText = '';
- let color = "lime";
- if (VideoEncoderQP > redQP) {
- color = "red";
- blinkQualityStatus(2);
- statsText += `<div style="color: ${color}">Bad network connection</div>`;
- } else if (VideoEncoderQP > orangeQP) {
- color = "orange";
- blinkQualityStatus(1);
- statsText += `<div style="color: ${color}">Spotty network connection</div>`;
- }
- qualityStatus.className = `${color}Status`;
- statsText += `<div>Duration: ${timeFormat.format(runTimeHours)}:${timeFormat.format(runTimeMinutes)}:${timeFormat.format(runTimeSeconds)}</div>`;
- statsText += `<div>Video Resolution: ${aggregatedStats.hasOwnProperty('frameWidth') && aggregatedStats.frameWidth && aggregatedStats.hasOwnProperty('frameHeight') && aggregatedStats.frameHeight ?
- aggregatedStats.frameWidth + 'x' + aggregatedStats.frameHeight : 'Chrome only'
- }</div>`;
- statsText += `<div>Received (${receivedBytesMeasurement}): ${numberFormat.format(receivedBytes)}</div>`;
- statsText += `<div>Frames Decoded: ${aggregatedStats.hasOwnProperty('framesDecoded') ? numberFormat.format(aggregatedStats.framesDecoded) : 'Chrome only'}</div>`;
- statsText += `<div>Packets Lost: ${aggregatedStats.hasOwnProperty('packetsLost') ? numberFormat.format(aggregatedStats.packetsLost) : 'Chrome only'}</div>`;
- statsText += `<div style="color: ${color}">Bitrate (kbps): ${aggregatedStats.hasOwnProperty('bitrate') ? numberFormat.format(aggregatedStats.bitrate) : 'Chrome only'}</div>`;
- statsText += `<div>Framerate: ${aggregatedStats.hasOwnProperty('framerate') ? numberFormat.format(aggregatedStats.framerate) : 'Chrome only'}</div>`;
- statsText += `<div>Frames dropped: ${aggregatedStats.hasOwnProperty('framesDropped') ? numberFormat.format(aggregatedStats.framesDropped) : 'Chrome only'}</div>`;
- statsText += `<div>Net RTT (ms): ${aggregatedStats.hasOwnProperty('currentRoundTripTime') ? numberFormat.format(aggregatedStats.currentRoundTripTime * 1000) : 'Can\'t calculate'}</div>`;
- statsText += `<div>Browser receive to composite (ms): ${aggregatedStats.hasOwnProperty('receiveToCompositeMs') ? numberFormat.format(aggregatedStats.receiveToCompositeMs) : 'Chrome only'}</div>`;
- statsText += `<div style="color: ${color}">Video Quantization Parameter: ${VideoEncoderQP}</div>`;
- let statsDiv = document.getElementById("stats");
- statsDiv.innerHTML = statsText;
- if (print_stats) {
- if (aggregatedStats.timestampStart) {
- if ((aggregatedStats.timestamp - aggregatedStats.timestampStart) > nextPrintDuration) {
- if (ws && ws.readyState === WS_OPEN_STATE) {
- console.log(`-> SS: stats\n${JSON.stringify(aggregatedStats)}`);
- ws.send(JSON.stringify({
- type: 'stats',
- data: aggregatedStats
- }));
- }
- nextPrintDuration += printInterval;
- }
- }
- }
- };
- webRtcPlayerObj.aggregateStats(1 * 1000 /*Check every 1 second*/);
- webRtcPlayerObj.latencyTestTimings.OnAllLatencyTimingsReady = function (timings) {
- if (!timings.BrowserReceiptTimeMs) {
- return;
- }
- let latencyExcludingDecode = timings.BrowserReceiptTimeMs - timings.TestStartTimeMs;
- let uePixelStreamLatency = timings.UEPreEncodeTimeMs == 0 || timings.UEPreCaptureTimeMs == 0 ? "???" : timings.UEPostEncodeTimeMs - timings.UEPreCaptureTimeMs;
- let captureLatency = timings.UEPostCaptureTimeMs - timings.UEPreCaptureTimeMs;
- let encodeLatency = timings.UEPostEncodeTimeMs - timings.UEPreEncodeTimeMs;
- let ueLatency = timings.UETransmissionTimeMs - timings.UEReceiptTimeMs;
- let networkLatency = latencyExcludingDecode - ueLatency;
- let browserSendLatency = latencyExcludingDecode - networkLatency - ueLatency;
- //these ones depend on FrameDisplayDeltaTimeMs
- let endToEndLatency = null;
- let browserSideLatency = null;
- if (timings.FrameDisplayDeltaTimeMs && timings.BrowserReceiptTimeMs) {
- endToEndLatency = timings.FrameDisplayDeltaTimeMs + latencyExcludingDecode;
- browserSideLatency = endToEndLatency - networkLatency - ueLatency;
- }
- let latencyStatsInnerHTML = '';
- latencyStatsInnerHTML += `<div>Net latency RTT (ms): ${networkLatency}</div>`;
- latencyStatsInnerHTML += `<div>UE Capture+Encode (ms): ${uePixelStreamLatency}</div>`;
- latencyStatsInnerHTML += `<div>UE Capture (ms): ${captureLatency}</div>`;
- latencyStatsInnerHTML += `<div>UE Encode (ms): ${encodeLatency}</div>`;
- latencyStatsInnerHTML += `<div>Total UE latency (ms): ${ueLatency}</div>`;
- latencyStatsInnerHTML += `<div>Browser send latency (ms): ${browserSendLatency}</div>`
- latencyStatsInnerHTML += timings.FrameDisplayDeltaTimeMs && timings.BrowserReceiptTimeMs ? `<div>Browser receive latency (ms): ${timings.FrameDisplayDeltaTimeMs}</div>` : "";
- latencyStatsInnerHTML += browserSideLatency ? `<div>Total browser latency (ms): ${browserSideLatency}</div>` : "";
- latencyStatsInnerHTML += `<div>Total latency (excluding browser) (ms): ${latencyExcludingDecode}</div>`;
- latencyStatsInnerHTML += endToEndLatency ? `<div>Total latency (ms): ${endToEndLatency}</div>` : "";
- document.getElementById("LatencyStats").innerHTML = latencyStatsInnerHTML;
- }
- }
- 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.HoveringMouse,
- // 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: false,
- // 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: " + 0 + "px; left: " + 0 + "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: " + 0 + "px; left: " + 0 + "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: " + 0 + "px; left: " + 0 + "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: " + 0 + "px; left: " + 0 + "px; width: 100% ; height: 100%; cursor: " + styleCursor + "; " + styleAdditional;
- }
- }
- function resizePlayerStyleToArbitrarySize(playerElement) {
- let videoElement = playerElement.getElementsByTagName("VIDEO");
- //Video is now 100% of the playerElement, so set the playerElement style
- playerElement.style = "top: 0px; left: 0px; width: " + styleWidth + "px; height: " + styleHeight + "px; cursor: " + styleCursor + "; " + styleAdditional;
- }
- function setupFreezeFrameOverlay() {
- freezeFrameOverlay = document.createElement('div');
- freezeFrameOverlay.id = 'freezeFrameOverlay';
- freezeFrameOverlay.style.display = 'none';
- freezeFrameOverlay.style.pointerEvents = 'none';
- freezeFrameOverlay.style.position = 'absolute';
- freezeFrameOverlay.style.zIndex = '20';
- let freezeFrameImage = document.createElement('img');
- freezeFrameImage.style.position = 'absolute';
- freezeFrameOverlay.appendChild(freezeFrameImage);
- }
- function showFreezeFrameOverlay() {
- if (freezeFrame.valid) {
- freezeFrameOverlay.classList.add("freezeframeBackground");
- freezeFrameOverlay.style.display = 'block';
- }
- }
- function invalidateFreezeFrameOverlay() {
- freezeFrameOverlay.style.display = 'none';
- freezeFrame.valid = false;
- freezeFrameOverlay.classList.remove("freezeframeBackground");
- if (webRtcPlayerObj) {
- webRtcPlayerObj.setVideoEnabled(true);
- }
- }
- function resizeFreezeFrameOverlay() {
- if (freezeFrame.width !== 0 && freezeFrame.height !== 0) {
- let displayWidth = 0;
- let displayHeight = 0;
- let displayTop = 0;
- let displayLeft = 0;
- let checkBox = document.getElementById('enlarge-display-to-fill-window-tgl');
- let playerElement = document.getElementById('player');
- if (checkBox !== null && checkBox.checked) {
- // We are fitting video to screen, we care about the screen (window) size
- let windowAspectRatio = window.innerWidth / window.innerHeight;
- let videoAspectRatio = freezeFrame.width / freezeFrame.height;
- if (windowAspectRatio < videoAspectRatio) {
- displayWidth = window.innerWidth;
- displayHeight = Math.floor(window.innerWidth / videoAspectRatio);
- displayTop = Math.floor((window.innerHeight - displayHeight) * 0.5);
- displayLeft = 0;
- } else {
- displayWidth = Math.floor(window.innerHeight * videoAspectRatio);
- displayHeight = window.innerHeight;
- displayTop = 0;
- displayLeft = Math.floor((window.innerWidth - displayWidth) * 0.5);
- }
- } else {
- // Video is coming in at native resolution, we care more about the player size
- let playerAspectRatio = playerElement.offsetWidth / playerElement.offsetHeight;
- let videoAspectRatio = freezeFrame.width / freezeFrame.height;
- if (playerAspectRatio < videoAspectRatio) {
- displayWidth = playerElement.offsetWidth;
- displayHeight = Math.floor(playerElement.offsetWidth / videoAspectRatio);
- displayTop = Math.floor((playerElement.offsetHeight - displayHeight) * 0.5);
- displayLeft = 0;
- } else {
- displayWidth = Math.floor(playerElement.offsetHeight * videoAspectRatio);
- displayHeight = playerElement.offsetHeight;
- displayTop = 0;
- displayLeft = Math.floor((playerElement.offsetWidth - displayWidth) * 0.5);
- }
- }
- let freezeFrameImage = document.getElementById("freezeFrameOverlay").childNodes[0];
- freezeFrameOverlay.style.width = playerElement.offsetWidth + 'px';
- freezeFrameOverlay.style.height = playerElement.offsetHeight + 'px';
- freezeFrameOverlay.style.left = 0 + 'px';
- freezeFrameOverlay.style.top = 0 + 'px';
- freezeFrameImage.style.width = displayWidth + 'px';
- freezeFrameImage.style.height = displayHeight + 'px';
- freezeFrameImage.style.left = displayLeft + 'px';
- freezeFrameImage.style.top = displayTop + 'px';
- }
- }
- function resizePlayerStyle(event) {
- let playerElement = document.getElementById('player');
- if (!playerElement)
- return;
- updateVideoStreamSize();
- if (playerElement.classList.contains('fixed-size')) {
- setupMouseAndFreezeFrame(playerElement)
- return;
- }
- let checkBox = document.getElementById('enlarge-display-to-fill-window-tgl');
- let windowSmallerThanPlayer = window.innerWidth < playerElement.videoWidth || window.innerHeight < playerElement.videoHeight;
- if (checkBox !== null) {
- if (checkBox.checked || windowSmallerThanPlayer) {
- resizePlayerStyleToFillWindow(playerElement);
- } else {
- resizePlayerStyleToActualSize(playerElement);
- }
- } else {
- resizePlayerStyleToArbitrarySize(playerElement);
- }
- setupMouseAndFreezeFrame(playerElement)
- }
- function setupMouseAndFreezeFrame(playerElement) {
- // Calculating and normalizing positions depends on the width and height of
- // the player.
- playerElementClientRect = playerElement.getBoundingClientRect();
- setupNormalizeAndQuantize();
- resizeFreezeFrameOverlay();
- }
- function updateVideoStreamSize() {
- if (!matchViewportResolution) {
- return;
- }
- let now = new Date().getTime();
- if (now - lastTimeResized > 1000) {
- let playerElement = document.getElementById('player');
- if (!playerElement)
- return;
- let descriptor = {
- Console: 'setres ' + playerElement.clientWidth + 'x' + playerElement.clientHeight
- };
- emitUIInteraction(descriptor);
- /* let descriptor = {
- "PageId":"0",
- "ExcuteName":"SetResolutionSize",
- "x":window.innerWidth,
- "y":window.innerHeight,
- };
- emitUIInteraction(descriptor); */
- console.log(descriptor);
- lastTimeResized = new Date().getTime();
- } else {
- console.log('Resizing too often - skipping');
- clearTimeout(resizeTimeout);
- resizeTimeout = setTimeout(updateVideoStreamSize, 1000);
- }
- }
- // Fix for bug in iOS where windowsize is not correct at instance or orientation change
- // https://github.com/dimsemenov/PhotoSwipe/issues/1315
- let _orientationChangeTimeout;
- function onOrientationChange(event) {
- clearTimeout(_orientationChangeTimeout);
- _orientationChangeTimeout = setTimeout(function () {
- resizePlayerStyle();
- }, 500);
- }
- // 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
- /**************************************************************************/
- };
- // A generic message has a type and a descriptor.
- function emitDescriptor(messageType, descriptor) {
- // Convert the dscriptor object into a JSON string.
- let descriptorAsString = JSON.stringify(descriptor);
- // Add the UTF-16 JSON string to the array byte buffer, going two bytes at
- // a time.
- let data = new DataView(new ArrayBuffer(1 + 2 + 2 * descriptorAsString.length));
- let byteIdx = 0;
- data.setUint8(byteIdx, messageType);
- byteIdx++;
- data.setUint16(byteIdx, descriptorAsString.length, true);
- byteIdx += 2;
- for (i = 0; i < descriptorAsString.length; i++) {
- data.setUint16(byteIdx, descriptorAsString.charCodeAt(i), true);
- byteIdx += 2;
- }
- sendInputData(data.buffer);
- }
- // A UI interation will occur when the user presses a button powered by
- // JavaScript as opposed to pressing a button which is part of the pixel
- // streamed UI from the UE4 client.
- function emitUIInteraction(descriptor) {
- emitDescriptor(MessageType.UIInteraction, descriptor);
- }
- // A build-in command can be sent to UE4 client. The commands are defined by a
- // JSON descriptor and will be executed automatically.
- // The currently supported commands are:
- //
- // 1. A command to run any console command:
- // "{ ConsoleCommand: <string> }"
- //
- // 2. A command to change the resolution to the given width and height.
- // "{ Resolution.Width: <value>, Resolution.Height: <value> } }"
- //
- function emitCommand(descriptor) {
- emitDescriptor(MessageType.Command, descriptor);
- }
- 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;
- // Unsigned XY positions are the ratio (0.0..1.0) along a viewport axis,
- // quantized into an uint16 (0..65536).
- // Signed XY deltas are the ratio (-1.0..1.0) along a viewport axis,
- // quantized into an int16 (-32767..32767).
- // This allows the browser viewport and client viewport to have a different
- // size.
- // Hack: Currently we set an out-of-range position to an extreme (65535)
- // as we can't yet accurately detect mouse enter and leave events
- // precisely inside a video with an aspect ratio which causes mattes.
- if (playerAspectRatio > videoAspectRatio) {
- if (print_inputs) {
- console.log('Setup Normalize and Quantize for 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 {
- if (print_inputs) {
- console.log('Setup Normalize and Quantize for playerAspectRatio <= videoAspectRatio');
- }
- 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) {
- if (print_inputs) {
- console.log(`x: ${x}, y:${y}, dX: ${deltaX}, dY: ${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) {
- if (print_inputs) {
- console.log(`mouse button ${button} down at (${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) {
- if (print_inputs) {
- console.log(`mouse button ${button} up at (${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) {
- if (print_inputs) {
- console.log(`mouse wheel with delta ${delta} at (${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);
- registerTouchEvents(playerElement);
- }
- function createOnScreenKeyboardHelpers(htmlElement) {
- if (document.getElementById('hiddenInput') === null) {
- hiddenInput = document.createElement('input');
- hiddenInput.id = 'hiddenInput';
- hiddenInput.maxLength = 0;
- htmlElement.appendChild(hiddenInput);
- }
- if (document.getElementById('editTextButton') === null) {
- editTextButton = document.createElement('button');
- editTextButton.id = 'editTextButton';
- editTextButton.innerHTML = 'edit text';
- htmlElement.appendChild(editTextButton);
- // Hide the 'edit text' button.
- editTextButton.classList.add('hiddenState');
- editTextButton.addEventListener('click', function () {
- // Show the on-screen keyboard.
- hiddenInput.focus();
- });
- }
- }
- function showOnScreenKeyboard(command) {
- if (command.showOnScreenKeyboard) {
- // Show the 'edit text' button.
- editTextButton.classList.remove('hiddenState');
- // Place the 'edit text' button near the UE4 input widget.
- let pos = unquantizeAndDenormalizeUnsigned(command.x, command.y);
- editTextButton.style.top = pos.y.toString() + 'px';
- editTextButton.style.left = (pos.x - 40).toString() + 'px';
- } else {
- // Hide the 'edit text' button.
- editTextButton.classList.add('hiddenState');
- // Hide the on-screen keyboard.
- hiddenInput.blur();
- }
- }
- function registerMouseEnterAndLeaveEvents(playerElement) {
- playerElement.onmouseenter = function (e) {
- if (print_inputs) {
- console.log('mouse enter');
- }
- let Data = new DataView(new ArrayBuffer(1));
- Data.setUint8(0, MessageType.MouseEnter);
- sendInputData(Data.buffer);
- playerElement.pressMouseButtons(e);
- };
- playerElement.onmouseleave = function (e) {
- if (print_inputs) {
- console.log('mouse leave');
- }
- 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 = 'false'; // 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);
- };
- }
- function registerTouchEvents(playerElement) {
- // We need to assign a unique identifier to each finger.
- // We do this by mapping each Touch object to the identifier.
- let fingers = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
- let fingerIds = {};
- function rememberTouch(touch) {
- let finger = fingers.pop();
- if (finger === undefined) {
- console.log('exhausted touch indentifiers');
- }
- fingerIds[touch.identifier] = finger;
- }
- function forgetTouch(touch) {
- fingers.push(fingerIds[touch.identifier]);
- delete fingerIds[touch.identifier];
- }
- function emitTouchData(type, touches) {
- let data = new DataView(new ArrayBuffer(2 + 7 * touches.length));
- data.setUint8(0, type);
- data.setUint8(1, touches.length);
- let byte = 2;
- for (let t = 0; t < touches.length; t++) {
- let touch = touches[t];
- let x = touch.clientX - playerElement.offsetLeft;
- let y = touch.clientY - playerElement.offsetTop;
- if (print_inputs) {
- console.log(`F${fingerIds[touch.identifier]}=(${x}, ${y})`);
- }
- let coord = normalizeAndQuantizeUnsigned(x, y);
- data.setUint16(byte, coord.x, true);
- byte += 2;
- data.setUint16(byte, coord.y, true);
- byte += 2;
- data.setUint8(byte, fingerIds[touch.identifier], true);
- byte += 1;
- data.setUint8(byte, 255 * touch.force, true); // force is between 0.0 and 1.0 so quantize into byte.
- byte += 1;
- data.setUint8(byte, coord.inRange ? 1 : 0, true); // mark the touch as in the player or not
- byte += 1;
- }
- sendInputData(data.buffer);
- }
- if (inputOptions.fakeMouseWithTouches) {
- let finger = undefined;
- playerElement.ontouchstart = function (e) {
- if (finger === undefined) {
- let firstTouch = e.changedTouches[0];
- finger = {
- id: firstTouch.identifier,
- x: firstTouch.clientX - playerElementClientRect.left,
- y: firstTouch.clientY - playerElementClientRect.top
- };
- // Hack: Mouse events require an enter and leave so we just
- // enter and leave manually with each touch as this event
- // is not fired with a touch device.
- playerElement.onmouseenter(e);
- emitMouseDown(MouseButton.MainButton, finger.x, finger.y);
- }
- e.preventDefault();
- };
- playerElement.ontouchend = function (e) {
- for (let t = 0; t < e.changedTouches.length; t++) {
- let touch = e.changedTouches[t];
- if (touch.identifier === finger.id) {
- let x = touch.clientX - playerElementClientRect.left;
- let y = touch.clientY - playerElementClientRect.top;
- emitMouseUp(MouseButton.MainButton, x, y);
- // Hack: Manual mouse leave event.
- playerElement.onmouseleave(e);
- finger = undefined;
- break;
- }
- }
- e.preventDefault();
- };
- playerElement.ontouchmove = function (e) {
- for (let t = 0; t < e.touches.length; t++) {
- let touch = e.touches[t];
- if (touch.identifier === finger.id) {
- let x = touch.clientX - playerElementClientRect.left;
- let y = touch.clientY - playerElementClientRect.top;
- emitMouseMove(x, y, x - finger.x, y - finger.y);
- finger.x = x;
- finger.y = y;
- break;
- }
- }
- e.preventDefault();
- };
- } else {
- playerElement.ontouchstart = function (e) {
- // Assign a unique identifier to each touch.
- for (let t = 0; t < e.changedTouches.length; t++) {
- rememberTouch(e.changedTouches[t]);
- }
- if (print_inputs) {
- console.log('touch start');
- }
- emitTouchData(MessageType.TouchStart, e.changedTouches);
- e.preventDefault();
- };
- playerElement.ontouchend = function (e) {
- if (print_inputs) {
- console.log('touch end');
- }
- emitTouchData(MessageType.TouchEnd, e.changedTouches);
- // Re-cycle unique identifiers previously assigned to each touch.
- for (let t = 0; t < e.changedTouches.length; t++) {
- forgetTouch(e.changedTouches[t]);
- }
- e.preventDefault();
- };
- playerElement.ontouchmove = function (e) {
- if (print_inputs) {
- console.log('touch move');
- }
- emitTouchData(MessageType.TouchMove, e.touches);
- e.preventDefault();
- };
- }
- }
- // 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) {
- if (print_inputs) {
- console.log(`key down ${e.keyCode}, repeat = ${e.repeat}`);
- }
- 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) {
- if (print_inputs) {
- console.log(`key up ${e.keyCode}`);
- }
- sendInputData(new Uint8Array([MessageType.KeyUp, getKeyCode(e)]).buffer);
- if (inputOptions.suppressBrowserKeys && isKeyCodeBrowserKey(e.keyCode)) {
- e.preventDefault();
- }
- };
- document.onkeypress = function (e) {
- if (print_inputs) {
- console.log(`key press ${e.charCode}`);
- }
- let data = new DataView(new ArrayBuffer(3));
- data.setUint8(0, MessageType.KeyPress);
- data.setUint16(1, e.charCode, true);
- sendInputData(data.buffer);
- };
- }
- function onExpandOverlay_Click( /* e */) {
- let overlay = document.getElementById('overlay');
- overlay.classList.toggle("overlay-shown");
- }
- function start() {
- // update "quality status" to "disconnected" state
- let qualityStatus = document.getElementById("qualityStatus");
- if (qualityStatus) {
- qualityStatus.className = "grey-status";
- }
- let statsDiv = document.getElementById("stats");
- if (statsDiv) {
- statsDiv.innerHTML = 'Not connected';
- }
- if (!connect_on_load || is_reconnection) {
- showConnectOverlay();
- invalidateFreezeFrameOverlay();
- shouldShowPlayOverlay = true;
- resizePlayerStyle();
- } else {
- connect();
- }
- updateKickButton(0);
- }
- function updateKickButton(playersCount) {
- let kickButton = document.getElementById('kick-other-players-button');
- if (kickButton)
- kickButton.value = `Kick (${playersCount})`;
- }
- 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.g.UE_IP);
- // ws = new WebSocket('ws://192.168.4.137:82/');
- 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 === 'playerCount') {
- updateKickButton(msg.count - 1);
- } 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;
- }
- showTextOverlay(`Disconnected: ${event.reason}`);
- let reclickToStart = setTimeout(start, 4000);
- };
- }
- // Config data received from WebRTC sender via the Cirrus web server
- function onConfig(config) {
- let playerDiv = document.getElementById('player');
- console.log('asjdkasd', playerDiv);
- 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(type) {
- console.log("type", type);
- if (type == 0) {
- registerHoveringMouseEvents = function () { }
- registerLockedMouseEvents = function () { }
- registerTouchEvents = function () { }
- registerKeyboardEvents = function () { }
- }
- setupHtmlEvents();
- setupFreezeFrameOverlay();
- registerKeyboardEvents();
- start();
- onExpandOverlay_Click()
- }
|