// Host and Client (hC) Module
// Version 1.3 (7:50 PM Fri October 27, 2017)
// Written by: James D. Miller
// This module is dependent on gwModule.js (referenced here as gW).
varhC=(function(){// To insist on tighter code: e.g. globals, etc...
"use strict";// A few globals within hC. /////////////////////////////////////////////////
varsocket=null;varnodeServerURL,serverArray;varchatStyleToggle=true;vartimer={};timer.start=null;timer.end=null;timer.pingArray=[];varclientCanvas,ctx;varvideoMirror,videoStream;varchkRequestStream,chkLocalCursor;// Key values.
varkeyMap={'49':'1','50':'2','51':'3','52':'4','53':'5','54':'6','55':'7','56':'8','70':'f','65':'a','83':'s','68':'d','87':'w','74':'j','75':'k','76':'l','73':'i','16':'sh','32':'sp'};//sh:shift, sp:space
// Mouse and keyboard (mK) from non-host clients.
varmK={};mK.name=null;// Key values, cso (client side only) for use only by the client, not to be sent over network
// to the host.
varkeyMap_cso={'16':'key_shift','17':'key_ctrl','80':'key_p'}varmK_cso={};// The client name of this user. This global is only used on the client page and
// is some increment of u1, u2, etc for network clients.
varnewClientName=null;// A global that points at a "Client" or clientlike object. For the host, this will point at the
// "Client" object of the client that most recently attempts to connect. On the client page,
// this will simply keep this structure and replace the 'notyetnamed' with the name of that
// client.
varcl={'name':'notyetnamed'};varrtc_choke=false;varfileName="hostAndClient.js";//////////////////////////////////////////////////
// Object prototypes
//////////////////////////////////////////////////
functionRTC(pars){this.user1=pars.user1||null;this.user2=pars.user2||null;this.streamRequested=pars.streamRequested||null;this.pc=null;this.dataChannel=null;}RTC.prototype.shutdown=function(){//console.log('pc:'+JSON.stringify(this.pc));
//console.log('dataChannel:'+JSON.stringify(this.dataChannel));
// Close then nullify any references to the datachannel and the p2p connection.
if(this.dataChannel){//console.log('a');
this.dataChannel.close();}if(this.pc){varsenders=this.pc.getSenders();if(senders.length>=1){//console.log('senders length = ' + senders.length);
this.pc.removeTrack(senders[0]);senders=this.pc.getSenders();//console.log('senders length = ' + senders.length);
}//console.log('b');
this.pc.close();}if(this.dataChannel){//console.log('c');
this.dataChannel=null;}if(this.pc){//console.log('d');
this.pc=null;}}//////////////////////////////////////////////////
// Functions supporting the socket.io connections
//////////////////////////////////////////////////
functionconnect_and_listen(hostOrClient){if(hostOrClient!='host'){// Disable the client connect button for 4 seconds after use.
$('#ConnectButton').html('Wait 4');$('#ConnectButton').prop('disabled',true);window.setTimeout(function(){$('#ConnectButton').html('Wait 3');},1000);window.setTimeout(function(){$('#ConnectButton').html('Wait 2');},2000);window.setTimeout(function(){$('#ConnectButton').html('Wait 1');},3000);window.setTimeout(function(){$('#ConnectButton').prop('disabled',false);$('#ConnectButton').html('Connect');},4000);}varnodeString=$('#nodeServer').val();if(nodeString==""){// Use one in the list as a default.
nodeString=serverArray[0];$('#nodeServer').val(nodeString);}if(nodeString.includes("heroku")){varurlPrefix="https://"}else{varurlPrefix="http://"}nodeServerURL=urlPrefix+nodeString;//console.log("URL=" + nodeServerURL);
// Use jquery to load the socket.io client code.
$.getScript(nodeServerURL+"/socket.io/socket.io.js",function(){// This callback function will run after the getScript finishes loading the socket.io client.
console.log("socket.io script has loaded.");// If there are already active network connections, close them before making new ones. This is
// the case if the client repeatedly clicks the connect button trying to get a preferred color.
if(socket){if(hostOrClient!='host'){// Send a message to the host (via socket.io server) to shutdown RTC connections.
if(newClientName){if(videoMirror.srcObject)videoMirror.srcObject=null;//console.log("in client's getScript callback, name = " + newClientName);
// Trigger client shutdown at the host.
socket.emit('shutDown-p2p-deleteClient',newClientName);}}window.setTimeout(function(){// Close socket.io connection after waiting a bit for the p2p connections to close.
socket.disconnect();},500);}// Delay this even longer than the socket.disconnect() above (to be sure the disconnect is done).
window.setTimeout(function(){varroomName=$('#roomName').val();if(roomName!=""){// the HTML limit is set to 9 (so you can try a little more then 7, but then get some advice to limit it to 7)
if(roomName.length<=7){// Here is where the socket.io client initiates it's connection to the server. The 'query' parameter is not
// needed here but is just shown to document the form of the query string with a two parameter example.
socket=io.connect(nodeServerURL,{'forceNew':true,'query':'par1=P1&par2=P2'});init_socket_listeners(roomName,hostOrClient);}else{displayMessage('The name should have 7 characters or less.');}}else{displayMessage('Type in a short "Room" name, then click the "Connect" button.');}},600);// Use the "fail" method of getScript to report a connection problem.
}).fail(function(jqxhr,settings,exception){displayMessage('The node server is not responding. Try changing to a different server.');});}functiondisplayMessage(msgText){// Every other line, toggle the background shading.
if(chatStyleToggle){varstyleString='style="background: #efefef;"';}else{varstyleString='style="background: #d9d9d9;"';}$('#messages').prepend('<li '+styleString+'>'+msgText+'</li>');chatStyleToggle=!chatStyleToggle;}functioninit_chatFeatures(hostOrClient){serverArray=['secure-retreat-15768.herokuapp.com','localhost:3000','192.168.1.106:3000','192.168.1.109:3000',//David's computer
'192.168.1.116:3000',//RPi
'192.168.1.117:3000'];//Laptop
// Use jquery to loop over the serverArray and build the URL datalist.
jQuery.each(serverArray,function(i,val){$('#nodeServerList').append("<option value='"+val+"'>");});varpingTestHelp="Your ping test has started.<br><br>"+"Please wait about 10 seconds for the results of the 100-ping test to return. Each time you hit enter or click the chat button "+"a new 100-ping test will be queued. Please manually clear out the words 'ping' or 'ping:host' to stop pinging and start chatting.";// Function that emits (if a socket has been established) the text in the form's input field.
$('#chatForm').submit(function(){varchatString=$('#inputField').val();if(socket){if(chatString=='ping'){echoTest('server');displayMessage(pingTestHelp);}elseif(chatString=='ping:host'){echoTest('host');displayMessage(pingTestHelp);}else{socket.emit('chat message',chatString);$('#inputField').val('');//clear out the input field.
}}else{displayMessage('Type in a short "Room" name, then click the "Connect" button.');}returnfalse;});// Prevent typing in the input fields from triggering document level keyboard events.
$('#inputField, #nodeServer, #roomName, #jsonCapture').on('keyup keydown keypress',function(e){e.stopPropagation();// stops bubbling...
});// A first message in the chat area
varhelpFindClientLink='';if(hostOrClient=='host'){helpFindClientLink='You can be the host of a multi-player room from this page. '+'Please notice the links to the client page in the right panel below the multiplayer checkbox.</br></br>';}else{helpFindClientLink='You can be a client in a multi-player room from this page. You can not be the host.</br></br>';}varhelloMessage='Thank you for trying the multiplayer feature.</br></br>'+helpFindClientLink+'To get started, type in a short "Room" name, then click the "Connect" button.</br></br>'+'Please note that if you do not get an immediate response from the server, it can take a little while for the Heroku node application to wake up. '+'If waking, give it 10 to 20 seconds before expecting a message here.</br></br>'+'To start over, or disconnect from the server, please reload the page.';displayMessage(helloMessage);}functionclientColor(clientName){varcolors={'1':'yellow','2':'blue','3':'green','4':'pink','5':'orange','6':'brown','7':'greenyellow','8':'cyan','9':'tan','0':'gray'};varn=clientName.slice(1);varcolorIndex=n-Math.trunc(n/10)*10;returncolors[colorIndex];}functioninit_socket_listeners(roomName,hostOrClient){// Listeners needed by both the client and the host.
// Listen for chat from the server.
socket.on('chat message',function(msg){// Change the border color of the roomName input box depending on the
// message from the node server.
if(msg.includes('You have joined room')){document.getElementById("roomName").style.borderColor="#008080";//Dark green.
}elseif(msg.includes('Sorry, there is no host')){document.getElementById("roomName").style.borderColor="red";}elseif(msg.includes('You are the host')){msg+='</br></br>You can open a test <a href="indexClient.html" target="_blank">client</a> in a new tab, then drag the tab to make a new window.&nbsp;';msg+='Enter the same room name on the client page. Then the client mouse and keyboard events will render to the canvas of the host.';}displayMessage(msg);});// Once your connection succeeds, join a room.
socket.on('connect',function(){// Connected. Send the room name to the server for room joining.
if(hostOrClient=='host'){// Request to be the host for that room.
socket.emit('roomJoinAsHost',roomName);}else{socket.emit('roomJoin',JSON.stringify({'requestStream':chkRequestStream.checked,'roomName':roomName}));}});// Listen for echo response from the server.
socket.on('echo-from-Server-to-Client',function(msg){varechoTarget=msg;// Stop timer (measure the round trip).
timer.stop=window.performance.now();varelapsed_time=timer.stop-timer.start;// Add this new timing result to the array.
timer.pingArray.push(elapsed_time);// The echo series STOPs here.
if(timer.pingArray.length>99){vartimeAvg=math.mean(timer.pingArray).toFixed(1);vartimeSTD=math.std(timer.pingArray).toFixed(1);vartimeLen=timer.pingArray.length;vartimeMax=math.max(timer.pingArray).toFixed(1);vartimeMin=math.min(timer.pingArray).toFixed(1);displayMessage('Echo test to '+echoTarget+': '+timeAvg+' ms '+'(std='+timeSTD+', min='+timeMin+', max='+timeMax+', n='+timeLen+')');timer.pingArray=[];return;}// Ping it again (continue the series).
echoTest(echoTarget);// Do this after the timer starts (don't slow it down with a write to the console.)
console.log(echoTarget);});// WebRTC Signaling.
// This handles signaling from both sides of the peer-to-peer connection.
socket.on('signaling message',function(msg){// Convert it back to a usable object (parse it).
varsignal_message=JSON.parse(msg);// Note that signalData needs to be in a stringified form when writing to the console.
//console.log("signal message from " + signal_message.from + ", to " + signal_message.to + ": " + JSON.stringify(signal_message.signalData));
// Offers and Answers
if(signal_message.signalData.sdp){//console.log('sdp in signal from host: ' + JSON.stringify(signal_message.signalData));
if(signal_message.signalData.type=='offer'){//console.log("an offer");
handleOffer(signal_message.signalData);}elseif(signal_message.signalData.type=='answer'){//console.log("an answer");
handleAnswer(signal_message.signalData);}else{console.log("Woooooo-HoHo-Hoooooo, something is screwed up. This can't be good.")}// ICE candidates
}elseif(signal_message.signalData.candidate){// handle ICE stuff.
cl.rtc.pc.addIceCandidate(signal_message.signalData).catch(function(reason){// An error occurred, so...
console.log('Error while handling ICE stuff:'+reason);});//console.log('signaling state after handling ICE = ' + cl.rtc.pc.signalingState);
}else{//No WebRTC stuff found in the signaling message. Maybe you are testing...
console.log("In final else block of 'signaling message' handler.")}});// Listeners needed by the client only.
if(hostOrClient=='client'){socket.on('your name is',function(msg){// Put this name in the mouse and keyboard (mK) global that is used to send
// state data from the client.
//console.log("msg in 'your name is' = " + msg);
mK.name=msg;// Put your name in this global (on the client side) for (possible) use by the WebRTC functions.
newClientName=msg;// Initialize this global container for the WebRTC stuff.
cl.name=newClientName;cl.rtc=newRTC({'user1':newClientName,'user2':'host'});// Show the client's color.
ctx.fillStyle=clientColor(mK.name);ctx.fillRect(0,0,clientCanvas.width,clientCanvas.height);});socket.on('disconnectByServer',function(msg){//console.log('in client disconnectByServer, msg='+msg);
varclientName=msg;displayMessage("This client ("+clientName+") is being disconnected by the host.");document.getElementById("roomName").style.borderColor="red";// When the server gets this one, it will remove the socket.
socket.emit('okDisconnectMe',clientName);// Shutdown and delete the client side of the WebRTC p2p connection.
cl.rtc.shutdown();mK.name=null;// Delay this so it takes effect after the p2p toggle finishes.
window.setTimeout(function(){// Paint over the client-color square with a light gray to indicate no connection.
ctx.fillStyle='#EFEFEF';ctx.fillRect(0,0,clientCanvas.width,clientCanvas.height);displayMessage("");displayMessage("Shutdown of the p2p connection for "+clientName+" has finished.");displayMessage("");displayMessage("");displayMessage("");},100);});socket.on('command-from-host-to-all-clients',function(msg){// Clients (only) do something based on the message from the host.
varcommand_message=JSON.parse(msg);vartype=command_message.type;varcommand=command_message.command;//console.log("inside command-from-host-to-all-clients on client" + ", command=" + command);
if(type=='resize'){gW.adjustSizeOfChatDiv(command);if(command=='normal'){videoMirror.width=600,videoMirror.height=600;}else{videoMirror.width=1250,videoMirror.height=950;}}else{console.log("I don't recognize that command; hey, I'm just saying...");}});}// Listeners needed by the host only.
if(hostOrClient=='host'){// (Note: this is the one place where calls to gW are made inside of hC.)
// Listen for client mouse and keyboard (mk) events broadcast from the server.
// StH: Server to Host
socket.on('client-mK-StH-event',function(msg){varmsg_parsed=JSON.parse(msg);//console.log('State('+ msg_parsed.name +'):'+ msg_parsed.MD +','+ msg_parsed.bu +'): '+ msg_parsed.mX + "," + msg_parsed.mY);
// Send this mouse-and-keyboard state to the engine.
gW.updateClientState(msg_parsed.name,msg_parsed);});// As host, create a new client in gW framework.
socket.on('new-game-client',function(msg){varmsgParsed=JSON.parse(msg);varclientName=msgParsed.clientName;varstreamRequested=msgParsed.requestStream;gW.createNetworkClient(clientName);// WebRTC. Start the p2p connection here (from the host) when we hear (from the server)
// that a client is trying to connect to a room.
// Make a global reference to this new (the most recent) client's RTC object.
cl=gW.clients[clientName];cl.rtc.user1='host';cl.rtc.user2=clientName;cl.rtc.streamRequested=streamRequested;//console.log('inside new-game-client, cl.rtc.user2 = ' + cl.rtc.user2);
//console.log('a');
// Start the WebRTC signaling exchange with the new client.
// Diagnostic tools: chrome://webrtc-internals (in Chrome) and about:webrtc (in Firefox)
try{openDataChannel(true);// open as the initiator
createOffer();}catch(e){console.log("WebRTC startup: "+e);}//console.log('b');
// Someone just connected. Send the layout state to them (actually to everyone, but that
// should, of course, cover the connecting user also). Delay it a bit...
window.setTimeout(function(){resizeClients(gW.getChatLayoutState());//console.log('cl.chatLayoutState = ' + gW.getChatLayoutState());
},300);});socket.on('client-disconnected',function(msg){varclientName=msg;//console.log('inside client-disconnected, clientName=' + clientName);
// Null out any WebRTC references in c object (most recent connection on the host page) if it happens to be
// this client.
nullReferences_toRTC_on_c(clientName);// Do corresponding cleanup in gwModule.
gW.deleteNetworkClient(clientName);});socket.on('echo-from-Server-to-Host',function(msg){// Bounce this back to server.
// The msg string is the client id.
socket.emit('echo-from-Host-to-Server',msg);//console.log(msg);
});socket.on('shutDown-p2p-deleteClient',function(msg){varclientName=msg;//console.log('in host, shutDown-p2p-deleteClient, msg='+msg);
// First check for the case where the host has reloaded their page and
// then a client attempts to reconnect.
if(gW.clients[clientName]){// Check if there is a puck controlled by this client.
if(gW.clients[clientName].puck){// This will delete the puck, the client, and all the WebRTC stuff...
gW.clients[clientName].puck.deleteThisOne();}else{// This will only delete the client and all the WebRTC stuff...
//console.log('before deleteRTC_onClientAndHost, name=' + clientName);
gW.deleteRTC_onClientAndHost(clientName);}}});}}// The following two functions are exposed for external use and are called from within gwModule.js.
functionforceClientDisconnect(clientName){//console.log('in forceClientDisconnect, name='+clientName);
socket.emit('clientDisconnectByHost',clientName);}functionresizeClients(command){if(socket){socket.emit('command-from-host-to-all-clients',JSON.stringify({'type':'resize','command':command}));}}functionechoTest(hostOrServer){// Start the timer for one echo.
timer.start=window.performance.now();// The echo series STARTs here.
socket.emit('echo-from-Client-to-Server',hostOrServer);//console.log(hostOrServer);
}////////////////////////////////////////////////
// Functions supporting the WebRTC connections.
////////////////////////////////////////////////
varconfiguration={'iceServers':[{'urls':'stun:stun1.l.google.com:19302'}]};functionopenDataChannel(isInitiator){cl.rtc.pc=newRTCPeerConnection(configuration);// send any ice candidates to the other peer
cl.rtc.pc.onicecandidate=function(evt){if(evt.candidate){//console.log('inside onicecandidate, evt.candidate check');
varsignal_message={'from':cl.rtc.user1,'to':cl.rtc.user2,'signalData':evt.candidate};socket.emit('signaling message',JSON.stringify(signal_message));}};// Host-side data channel
if(isInitiator){vardc_id=cl.rtc.user2.slice(1);vardc_options={'id':dc_id,'ordered':false,'maxRetransmits':1};vardc_label="dc-"+cl.rtc.user2;cl.rtc.dataChannel=cl.rtc.pc.createDataChannel(dc_label,dc_options);cl.rtc.dataChannel.onmessage=function(e){handle_RTC_message(e);};cl.rtc.dataChannel.onopen=function(){console.log("------ RTC DC(H) OPENED ------");};cl.rtc.dataChannel.onclose=function(){console.log("------ RTC DC(H) closed ------");};cl.rtc.dataChannel.onerror=function(){console.log("RTC DC(H) error.....");};if(cl.rtc.streamRequested){startVideoStream();}//console.log('data channel A-block');
// Client-side data channel
}else{// This side of the data channel gets established in response to the channel initialization
// on the host side.
cl.rtc.pc.ondatachannel=function(evt){//console.log('data channel B1-block');
cl.rtc.dataChannel=evt.channel;// Must set up an onmessage handler for the clients too.
cl.rtc.dataChannel.onmessage=function(e){console.log("DC (@client) message:"+e.data);};cl.rtc.dataChannel.onopen=function(){console.log("------ RTC DC(C) OPENED ------");rtc_choke=false;refresh_P2P_indicator();};cl.rtc.dataChannel.onclose=function(){console.log("------ RTC DC(C) closed ------");rtc_choke=true;refresh_P2P_indicator();};cl.rtc.dataChannel.onerror=function(){console.log("RTC DC(C) error.....");};}//console.log('data channel B-block');
// Respond to a new track by sending the stream to the video element.
cl.rtc.pc.ontrack=function(evt){videoMirror.srcObject=evt.streams[0];};}//console.log('signaling state after openDataChannel = ' + cl.rtc.pc.signalingState);
}functionstartVideoStream(){if(!videoStream){varhostCanvas=document.getElementById('hostCanvas');videoStream=hostCanvas.captureStream();//60
}cl.rtc.pc.addTrack(videoStream.getVideoTracks()[0],videoStream);document.getElementById("chkStream").checked=true;videoStream.getVideoTracks()[0].enabled=true;}functionsetCanvasStream(newState){if(videoStream){if(newState=='on'){videoStream.getVideoTracks()[0].enabled=true;}else{videoStream.getVideoTracks()[0].enabled=false;}}}functionhandle_RTC_message(msg){//var user2 = Object.assign({}, cl.rtc.user2);
/*
var user2 = JSON.stringify(cl.rtc.user2);
console.log("I am (cl.rtc.user2) = " + user2);
console.log("DC ID = " + JSON.stringify(cl.rtc.dataChannel.id));
console.log("DC (@host) message: " + e.data);
*/// Process mK events from the client on the other end of this peer-to-peer connection.
varmK_string=msg.data;//console.log('mK_string = ' + mK_string);
varmK_data=JSON.parse(mK_string);// Send this mouse-and-keyboard state to the engine.
gW.updateClientState(mK_data.name,mK_data);}functioncreateOffer(){cl.rtc.pc.createOffer().then(function(offer){returncl.rtc.pc.setLocalDescription(offer);}).then(function(){varsignal_message={'from':cl.rtc.user1,'to':cl.rtc.user2,'signalData':cl.rtc.pc.localDescription};socket.emit('signaling message',JSON.stringify(signal_message));}).catch(function(reason){// An error occurred, so handle the failure to connect
console.log('Error while creating offer:'+reason);});//console.log('signaling state after createOffer = ' + cl.rtc.pc.signalingState);
}functionhandleOffer(msg){openDataChannel(false);// Open as NOT the initiator
cl.rtc.pc.setRemoteDescription(msg).then(function(){returncl.rtc.pc.createAnswer();}).then(function(answer){returncl.rtc.pc.setLocalDescription(answer);}).then(function(){// Send the answer (localDescription) to the remote peer
varsignal_message={'from':cl.rtc.user1,'to':cl.rtc.user2,'signalData':cl.rtc.pc.localDescription};socket.emit('signaling message',JSON.stringify(signal_message));}).catch(function(reason){console.log('Error while handling offer:'+reason);});//console.log('signaling state after handleOffer = ' + cl.rtc.pc.signalingState);
}functionhandleAnswer(answer){cl.rtc.pc.setRemoteDescription(answer).catch(function(reason){console.log('Error while handling answer:'+reason);});//console.log('signaling state after handleAnswer = ' + cl.rtc.pc.signalingState);
}functionlogError(error){console.log(error.name+': '+error.message);}functionnullReferences_toRTC_on_c(clientName){// Check the global "c" pointer (to the most recently connected client) to see if it happens to
// be pointed at this client.
//console.log('cl.rtc='+JSON.stringify( cl.rtc) + ", newClientName=" + clientName);
if(cl.rtc&&(cl.rtc.user2==clientName)){cl.rtc=newRTC({});}}functionrefresh_P2P_indicator(){//console.log("fileName = " + fileName);
//console.log("mK.name = " + mK.name);
if(mK.name){// Show (flood/erase the canvas with) the client's color.
ctx.fillStyle=clientColor(mK.name);ctx.fillRect(0,0,clientCanvas.width,clientCanvas.height);//console.log("rtc_choke = " + rtc_choke);
//console.log("cl.rtc = " + JSON.stringify( cl.rtc));
//console.log("cl.rtc.dataChannel = " + JSON.stringify( cl.rtc.dataChannel));
if(!rtc_choke&&cl.rtc&&cl.rtc.dataChannel&&(cl.rtc.dataChannel.readyState=='open')){//console.log("inside 'P2P' write block");
ctx.font="12px Arial";// Use dark letters for the lighter client colors.
varlightColors=['yellow','greenyellow','pink','cyan','tan'];if(lightColors.includes(clientColor(mK.name))){ctx.fillStyle='black';}else{ctx.fillStyle='white';}ctx.fillText('P2P',10,12);}}else{// Light gray fill.
ctx.fillStyle='#EFEFEF';ctx.fillRect(0,0,clientCanvas.width,clientCanvas.height);}}////////////////////////////////////////////////////////////////////////////////
// Event listeners to capture mouse and keyboard (m & K) state from the non-host
// clients.
////////////////////////////////////////////////////////////////////////////////
functioninit_eventListeners_nonHostClients(){// Initialize the Mouse and Keyboard (mK) state object.
// isMouseDown
mK.MD=false;// mouse button number (which of the three: 0,1,2)
mK.bu=0;// mouse position in pixels: X_px, Y_px
mK.mX=5;mK.mY=5;// Use the keyMap to define and initialize all the key states (to UP) in the
// mK (mouse and keyboard state) object that is sent to the host.
for(varkeyinkeyMap){mK[keyMap[key]]='U';}for(varkeyinkeyMap_cso){mK_cso[keyMap_cso[key]]='U';}clientCanvas=document.getElementById('connectionCanvas');ctx=clientCanvas.getContext('2d');videoMirror=document.getElementById('videoMirror');// Event handlers for this network client (user input)
// Inhibit the context menu that pops up when right clicking (third button).
// Alternatively, could apply this only to the canvas. That way you can still
// source the page.
document.addEventListener("contextmenu",function(e){e.preventDefault();returnfalse;},{capture:false});// For the client, keep these listeners on all the time so you can see the client cursor.
document.addEventListener("touchmove",handleMouseOrTouchMove,{capture:false});document.addEventListener("mousemove",handleMouseOrTouchMove,{capture:false});document.addEventListener("mousedown",function(e){mK.MD=true;mK.bu=e.button;//Pass this first mouse position to the move handler.
handleMouseOrTouchMove(e);//if (cl.rtc && cl.rtc.dataChannel) cl.rtc.dataChannel.send( 'mouse-down event, id = ' + cl.rtc.dataChannel.id);
},{capture:false});document.addEventListener("touchstart",function(e){// Note: e.preventDefault() not needed here if the following canvas style is set
// touch-action: none;
mK.MD=true;mK.bu=0;//Pass this first mouse position to the move handler.
handleMouseOrTouchMove(e);},{capture:false});functionhandleMouseOrTouchMove(e){// Determine if mouse or touch.
if(e.clientX){// Mouse
varraw_x_px=e.clientX;varraw_y_px=e.clientY;}elseif(e.touches){// Touch screen event
varraw_x_px=e.touches[0].clientX;varraw_y_px=e.touches[0].clientY;}// Note the offsets here are the same as in the handleMouseOrTouchMove handler of gwModule.js
mK.mX=raw_x_px-videoMirror.getBoundingClientRect().left-5;mK.mY=raw_y_px-videoMirror.getBoundingClientRect().top-4;// Send the state to the server (there it will be relayed to the host client).
handle_sending_mK_data(mK);//console.log("x,y=" + mK.mX + "," + mK.mY);
};functionhandle_sending_mK_data(mK){// Use WebRTC datachannel if available
/*
*/if(cl.rtc&&cl.rtc.dataChannel&&(cl.rtc.dataChannel.readyState=='open')&&(rtc_choke==false)){cl.rtc.dataChannel.send(JSON.stringify(mK));// Otherwise use socket.io (WebSocket)
}elseif(socket){socket.emit('client-mK-event',JSON.stringify(mK));}//if (socket) socket.emit('client-mK-event', JSON.stringify( mK));
}document.addEventListener("mouseup",function(e){if(!mK.MD)return;// Unlike for the host client, DO NOT shut down the mousemove listener. That
// way we can see the mouse position even if the buttons are released.
resetMouseOrFingerState(e);},{capture:false});document.addEventListener("touchend",function(e){// Note: e.preventDefault() not needed here if the following canvas style is set
// touch-action: none;
if(!mK.MD)return;// Unlike for the host client, DO NOT shut down the touchmove listener. That
// way we can see the finger position even if the buttons are released.
resetMouseOrFingerState(e);},{capture:false});functionresetMouseOrFingerState(e){mK.MD=false;mK.bu=null;handle_sending_mK_data(mK);}document.addEventListener("keydown",function(e){// This allows the spacebar to be used for the puck shields.
if(keyMap[e.keyCode]=='sp'){// Inhibit page scrolling that results from using the spacebar.
e.preventDefault();// The following is necessary in Firefox to avoid the spacebar from re-clicking
// page controls (like the demo buttons) if they have focus.
if(document.activeElement!=document.body)document.activeElement.blur();}//console.log(e.keyCode + "(down)=" + String.fromCharCode(e.keyCode));
if(e.keyCodeinkeyMap_cso){//console.log("keyMap value = " + keyMap_cso[e.keyCode]);
if(mK_cso[keyMap_cso[e.keyCode]]=='U'){// Set the key to DOWN.
mK_cso[keyMap_cso[e.keyCode]]='D';}}// Toggle the p2p connection
if((mK_cso.key_p=='D')&&(mK_cso.key_shift=='D')){rtc_choke=!rtc_choke;refresh_P2P_indicator();}if(e.keyCodeinkeyMap){//console.log("keyMap value = " + keyMap[e.keyCode]);
if(mK[keyMap[e.keyCode]]=='U'){// Set the key to DOWN.
mK[keyMap[e.keyCode]]='D';handle_sending_mK_data(mK);}}},{capture:false});//"false" makes this fire in the bubbling phase (not capturing phase).
document.addEventListener("keyup",function(e){//console.log(e.keyCode + "(up)=" + String.fromCharCode(e.keyCode));
if(e.keyCodeinkeyMap){// Set the key to UP.
mK[keyMap[e.keyCode]]='U';handle_sending_mK_data(mK);}if(e.keyCodeinkeyMap_cso){// Set the key to UP.
mK_cso[keyMap_cso[e.keyCode]]='U';}},{capture:false});//"false" makes this fire in the bubbling phase (not capturing phase).
// Event handlers for the check-boxes to the right of the video element.
chkRequestStream=document.getElementById('chkRequestStream');chkRequestStream.checked=true;chkRequestStream.addEventListener("click",function(){//console.log("chkRequestStream.checked=" + chkRequestStream.checked);
},{capture:false});chkLocalCursor=document.getElementById('chkLocalCursor');chkLocalCursor.checked=true;chkLocalCursor.addEventListener("click",function(){//console.log("chkLocalCursor.checked=" + chkLocalCursor.checked);
//console.log('inside chkLocalCursor, cursor=' + videoMirror.style.cursor + '|');
if(chkLocalCursor.checked){videoMirror.style.cursor='default';}else{videoMirror.style.cursor='none';}},{capture:false});}// Reveal public pointers to private functions and properties ///////////////
return{//nodeServerURL: nodeServerURL,
forceClientDisconnect:forceClientDisconnect,resizeClients:resizeClients,init_chatFeatures:init_chatFeatures,init_eventListeners_nonHostClients:init_eventListeners_nonHostClients,connect_and_listen:connect_and_listen,refresh_P2P_indicator:refresh_P2P_indicator,setCanvasStream:setCanvasStream,RTC:RTC};})();