Localhost: how to configure XHR-Signaling (connection.openSignalingChannel does not receive the call)

I am using RTCMultiConnection v3.4.4

I want to run WebRTC on localhost. I chose XHR-Signaling because I want the project to be completely disabled. I do not want this to depend on the Internet, since everything is on the local host (it will be later deployed to the local network)

I turned on XHRConnection.js and set connection.setCustomSocketHandler(XHRConnection) . I also did an override of connection.openSignalingChannel...

However, when I open / start the room, my video shows, but the buttons that were disabled using disableInputButtons() still remain disabled. Chat is not working.

I did console.log while overriding connection.openSignalingChannel... to confirm if it was ever called, but it is not.

Please help how to implement XHR-Signaling on a local hosting.

Thanks.

the code:

File: Audio+Video+TextChat+FileSharing.html

 <!-- Demo version: 2017.08.10 --> <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>Audio+Video+TextChat+FileSharing using RTCMultiConnection</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> <link rel="shortcut icon" href="./logo.png"> <link rel="stylesheet" href="./stylesheet.css"> <script src="./menu.js"></script> </head> <body> <h1> Audio+Video+TextChat+FileSharing using RTCMultiConnection <p class="no-mobile"> Multi-user (many-to-many) video streaming + text chat + file sharing using mesh networking model. </p> </h1> <section class="make-center"> <input type="text" id="room-id" value="abcdef" autocorrect=off autocapitalize=off size=20> <button id="open-room">Open Room</button><button id="join-room">Join Room</button><button id="open-or-join-room">Auto Open Or Join Room</button> <br><br> <input type="text" id="input-text-chat" placeholder="Enter Text Chat" disabled> <button id="share-file" disabled>Share File</button> <br><br> <button id="btn-leave-room" disabled>Leave/or close the room</button> <div id="room-urls" style="text-align: center;display: none;background: #F1EDED;margin: 15px -10px;border: 1px solid rgb(189, 189, 189);border-left: 0;border-right: 0;"></div> <div id="chat-container"> <div id="file-container"></div> <div class="chat-output"></div> </div> <div id="videos-container"></div> </section> <script src="./RTCMultiConnection.min.js"></script> <script src="./adapter.js"></script> <script src="./XHRConnection.js"></script> <!-- custom layout for HTML5 audio/video elements --> <link rel="stylesheet" href="./getHTMLMediaElement.css"> <script src="./getHTMLMediaElement.js"></script> <script src="./FileBufferReader.js"></script> <script> // ...................................................... // .......................UI Code........................ // ...................................................... document.getElementById('open-room').onclick = function() { disableInputButtons(); connection.open( document.getElementById('room-id').value , function() { showRoomURL(connection.sessionid); xhr ( 'start-broadcast.php' , function( responseText ){ console.log( 'Broadcast started [' + document.getElementById('room-id').value + ']' ) }, JSON.stringify( { name: document.getElementById('room-id').value } ) ); }); }; document.getElementById('join-room').onclick = function() { disableInputButtons(); connection.join(document.getElementById('room-id').value); }; document.getElementById('open-or-join-room').onclick = function() { disableInputButtons(); connection.openOrJoin(document.getElementById('room-id').value, function(isRoomExists, roomid) { if (!isRoomExists) { showRoomURL(roomid); } }); }; document.getElementById('btn-leave-room').onclick = function() { this.disabled = true; if (connection.isInitiator) { // use this method if you did NOT set "autoCloseEntireSession===true" // for more info: https://github.com/muaz-khan/RTCMultiConnection#closeentiresession connection.closeEntireSession(function() { document.querySelector('h1').innerHTML = 'Entire session has been closed.'; }); } else { connection.leave(); } }; // ...................................................... // ................FileSharing/TextChat Code............. // ...................................................... document.getElementById('share-file').onclick = function() { var fileSelector = new FileSelector(); fileSelector.selectSingleFile(function(file) { connection.send(file); }); }; document.getElementById('input-text-chat').onkeyup = function(e) { if (e.keyCode != 13) return; // removing trailing/leading whitespace this.value = this.value.replace(/^\s+|\s+$/g, ''); if (!this.value.length) return; connection.send(this.value); appendDIV(this.value); this.value = ''; }; var chatContainer = document.querySelector('.chat-output'); function appendDIV(event) { var div = document.createElement('div'); div.innerHTML = event.data || event; chatContainer.insertBefore(div, chatContainer.firstChild); div.tabIndex = 0; div.focus(); document.getElementById('input-text-chat').focus(); } // ...................................................... // ..................RTCMultiConnection Code............. // ...................................................... var connection = new RTCMultiConnection(); connection.setCustomSocketHandler(XHRConnection); connection.direction = 'one-way'; // by default, socket.io server is assumed to be deployed on your own URL // connection.socketURL = '/'; connection.trickleIce = false; // comment-out below line if you do not have your own socket.io server // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; //connection.socketMessageEvent = 'audio-video-file-chat-demo'; connection.enableLogs = true; connection.enableFileSharing = true; // by default, it is "false". // this object is used to store "onmessage" callbacks from "openSignalingChannel handler var onMessageCallbacks = {}; // this object is used to make sure identical messages are not used multiple times var messagesReceived = {}; // overriding "openSignalingChannel handler connection.openSignalingChannel = function (config) { console.log( 'called: openSignalingChannel' ); var channel = config.channel || this.channel; onMessageCallbacks[channel] = config.onmessage; // let RTCMultiConnection know that server connection is opened! if (config.onopen) { console.log( 'Calling the config.open object' ); setTimeout(config.onopen, 1); } else console.log( 'No config.open object' ); // returning an object to RTCMultiConnection // so it can send data using "send" method return { send: function (data) { data = { channel: channel, message: data, sender: connection.userid }; // posting data to server // data is also JSON-ified. xhr('xhr-signalhandler-post.php', null, JSON.stringify(data)); }, channel: channel }; }; connection.session = { audio: true, video: true, data: true }; connection.sdpConstraints.mandatory = { OfferToReceiveAudio: true, OfferToReceiveVideo: true }; connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { var width = parseInt(connection.videosContainer.clientWidth / 2) - 20; var mediaElement = getHTMLMediaElement(event.mediaElement, { title: event.userid, buttons: ['full-screen'], width: width, showOnMouseEnter: false }); connection.videosContainer.appendChild(mediaElement); setTimeout(function() { mediaElement.media.play(); }, 5000); mediaElement.id = event.streamid; }; connection.onstreamended = function(event) { var mediaElement = document.getElementById(event.streamid); if (mediaElement) { mediaElement.parentNode.removeChild(mediaElement); } }; connection.onmessage = appendDIV; connection.filesContainer = document.getElementById('file-container'); connection.onopen = function() { console.log( "com. openend" ); document.getElementById('share-file').disabled = false; document.getElementById('input-text-chat').disabled = false; document.getElementById('btn-leave-room').disabled = false; document.querySelector('h1').innerHTML = 'You are connected with: ' + connection.getAllParticipants().join(', '); }; connection.onclose = function() { if (connection.getAllParticipants().length) { document.querySelector('h1').innerHTML = 'You are still connected with: ' + connection.getAllParticipants().join(', '); } else { document.querySelector('h1').innerHTML = 'Seems session has been closed or all participants left.'; } }; connection.onEntireSessionClosed = function(event) { document.getElementById('share-file').disabled = true; document.getElementById('input-text-chat').disabled = true; document.getElementById('btn-leave-room').disabled = true; document.getElementById('open-or-join-room').disabled = false; document.getElementById('open-room').disabled = false; document.getElementById('join-room').disabled = false; document.getElementById('room-id').disabled = false; connection.attachStreams.forEach(function(stream) { stream.stop(); }); // don't display alert for moderator if (connection.userid === event.userid) return; document.querySelector('h1').innerHTML = 'Entire session has been closed by the moderator: ' + event.userid; }; connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) { // seems room is already opened connection.join(useridAlreadyTaken); }; function disableInputButtons() { document.getElementById('open-or-join-room').disabled = true; document.getElementById('open-room').disabled = true; document.getElementById('join-room').disabled = true; document.getElementById('room-id').disabled = true; } // ...................................................... // ......................Handling Room-ID................ // ...................................................... function showRoomURL(roomid) { var roomHashURL = '#' + roomid; var roomQueryStringURL = '?roomid=' + roomid; var html = '<h2>Unique URL for your room:</h2><br>'; html += 'Hash URL: <a href="' + roomHashURL + '" target="_blank">' + roomHashURL + '</a>'; html += '<br>'; html += 'QueryString URL: <a href="' + roomQueryStringURL + '" target="_blank">' + roomQueryStringURL + '</a>'; var roomURLsDiv = document.getElementById('room-urls'); roomURLsDiv.innerHTML = html; roomURLsDiv.style.display = 'block'; } (function() { var params = {}, r = /([^&=]+)=?([^&]*)/g; function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); } var match, search = window.location.search; while (match = r.exec(search.substring(1))) params[d(match[1])] = d(match[2]); window.params = params; })(); var roomid = ''; if (localStorage.getItem(connection.socketMessageEvent)) { roomid = localStorage.getItem(connection.socketMessageEvent); } else { roomid = connection.token(); } document.getElementById('room-id').value = roomid; document.getElementById('room-id').onkeyup = function() { localStorage.setItem(connection.socketMessageEvent, this.value); }; var hashString = location.hash.replace('#', ''); if (hashString.length && hashString.indexOf('comment-') == 0) { hashString = ''; } var roomid = params.roomid; if (!roomid && hashString.length) { roomid = hashString; } if (roomid && roomid.length) { document.getElementById('room-id').value = roomid; localStorage.setItem(connection.socketMessageEvent, roomid); // auto-join-room (function reCheckRoomPresence() { connection.checkPresence(roomid, function(isRoomExists) { if (isRoomExists) { connection.join(roomid); return; } setTimeout(reCheckRoomPresence, 5000); }); })(); disableInputButtons(); } </script> <footer> <small id="send-message"></small> </footer> <script src="common.js"></script> </body> </html> 

XHRConnection.js

 function XHRConnection(connection, connectCallback) { connection.socket = { send: function(data) { data = { message: data, sender: connection.userid }; // posting data to server // data is also JSON-ified. xhr('xhr-signalhandler-post.php', null, JSON.stringify(data)); } }; // this object is used to make sure identical messages are not used multiple times var messagesReceived = {}; function repeatedlyCheck() { xhr('xhr-signalhandler-get.php', function(data) { // if server says nothing; wait. if (data == false) return setTimeout(repeatedlyCheck, 400); // if already receied same message; skip. if (messagesReceived[data.ID]) return setTimeout(repeatedlyCheck, 400); messagesReceived[data.ID] = data.Message; // "Message" property is JSON-ified in "openSignalingChannel handler data = JSON.parse(data.Message); if (data.eventName === connection.socketMessageEvent) { onMessagesCallback(data.data); } if (data.eventName === 'presence') { data = data.data; if (data.userid === connection.userid) return; connection.onUserStatusChanged({ userid: data.userid, status: data.isOnline === true ? 'online' : 'offline', extra: connection.peers[data.userid] ? connection.peers[data.userid].extra : {} }); } // repeatedly check the database setTimeout(repeatedlyCheck, 1); }); } repeatedlyCheck(); setTimeout ( function() { if (connection.enableLogs) { console.info('XHR connection opened'); } connection.socket.emit('presence', { userid: connection.userid, isOnline: true }); if( connectCallback ) { console.log( 'Calling connectCallback...' ); connectCallback(connection.socket); console.log( 'Done' ); } }, 2000 ); connection.socket.emit = function(eventName, data, callback) { if (eventName === 'changed-uuid') return; if (data.message && data.message.shiftedModerationControl) return; connection.socket.send({ eventName: eventName, data: data }); if (callback) { callback(); } }; var mPeer = connection.multiPeersHandler; function onMessagesCallback(message) { if (message.remoteUserId != connection.userid) return; if (connection.peers[message.sender] && connection.peers[message.sender].extra != message.extra) { connection.peers[message.sender].extra = message.extra; connection.onExtraDataUpdated({ userid: message.sender, extra: message.extra }); } if (message.message.streamSyncNeeded && connection.peers[message.sender]) { var stream = connection.streamEvents[message.message.streamid]; if (!stream || !stream.stream) { return; } var action = message.message.action; if (action === 'ended' || action === 'stream-removed') { connection.onstreamended(stream); return; } var type = message.message.type != 'both' ? message.message.type : null; stream.stream[action](type); return; } if (message.message === 'connectWithAllParticipants') { if (connection.broadcasters.indexOf(message.sender) === -1) { connection.broadcasters.push(message.sender); } mPeer.onNegotiationNeeded({ allParticipants: connection.getAllParticipants(message.sender) }, message.sender); return; } if (message.message === 'removeFromBroadcastersList') { if (connection.broadcasters.indexOf(message.sender) !== -1) { delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)]; connection.broadcasters = removeNullEntries(connection.broadcasters); } return; } if (message.message === 'dropPeerConnection') { connection.deletePeer(message.sender); return; } if (message.message.allParticipants) { if (message.message.allParticipants.indexOf(message.sender) === -1) { message.message.allParticipants.push(message.sender); } message.message.allParticipants.forEach(function(participant) { mPeer[!connection.peers[participant] ? 'createNewPeer' : 'renegotiatePeer'](participant, { localPeerSdpConstraints: { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: isData(connection.session) }); }); return; } if (message.message.newParticipant) { if (message.message.newParticipant == connection.userid) return; if (!!connection.peers[message.message.newParticipant]) return; mPeer.createNewPeer(message.message.newParticipant, message.message.userPreferences || { localPeerSdpConstraints: { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: isData(connection.session) }); return; } if (message.message.readyForOffer || message.message.addMeAsBroadcaster) { connection.addNewBroadcaster(message.sender); } if (message.message.newParticipationRequest && message.sender !== connection.userid) { if (connection.peers[message.sender]) { connection.deletePeer(message.sender); } var userPreferences = { extra: message.extra || {}, localPeerSdpConstraints: message.message.remotePeerSdpConstraints || { OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo }, remotePeerSdpConstraints: message.message.localPeerSdpConstraints || { OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio, OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo }, isOneWay: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', isDataOnly: typeof message.message.isDataOnly !== 'undefined' ? message.message.isDataOnly : isData(connection.session), dontGetRemoteStream: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way', dontAttachLocalStream: !!message.message.dontGetRemoteStream, connectionDescription: message, successCallback: function() { // if its oneway----- todo: THIS SEEMS NOT IMPORTANT. if (typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way') { connection.addNewBroadcaster(message.sender, userPreferences); } if (!!connection.session.oneway || connection.direction === 'one-way' || isData(connection.session)) { connection.addNewBroadcaster(message.sender, userPreferences); } } }; connection.onNewParticipant(message.sender, userPreferences); return; } if (message.message.shiftedModerationControl) { connection.onShiftedModerationControl(message.sender, message.message.broadcasters); return; } if (message.message.changedUUID) { if (connection.peers[message.message.oldUUID]) { connection.peers[message.message.newUUID] = connection.peers[message.message.oldUUID]; delete connection.peers[message.message.oldUUID]; } } if (message.message.userLeft) { mPeer.onUserLeft(message.sender); if (!!message.message.autoCloseEntireSession) { connection.leave(); } return; } mPeer.addNegotiatedMessage(message.message, message.sender); } window.addEventListener('beforeunload', function() { connection.socket.emit('presence', { userid: connection.userid, isOnline: false }); }, false); } // a simple function to make XMLHttpRequests function xhr( url, callback, data ) { // if( data ) console.log('[' + url + '] sending: ' + JSON.stringify( data ) ); if (!window.XMLHttpRequest || !window.JSON){ console.log( 'No JSON and/or XMLHttpRequest support' ); return; } var request = new XMLHttpRequest(); request.onreadystatechange = function() { if (callback && request.readyState == 4 && request.status == 200) { // server MUST return JSON text if( request.responseText != 'false' ) console.log('Logging non-false data [from ' + url + ']: ' + request.responseText + "[...data POST'ed: " + JSON.stringify( data ) + "]" ); callback(JSON.parse(request.responseText)); } }; request.open( 'POST', url ); var formData = new FormData(); // you're passing "message" parameter formData.append( 'message', data ); request.send(formData); } 

start-broadcast.php :

 <?php require( "connection.inc.php" ); if( isset( $_POST['message'] ) ) { $data = json_decode( $_POST['message'] , true ); // Now, if someone initiates WebRTC session; you should make an XHR request to create a record in the room-table; and // set "Owner-id" equals to that user "user-id". //{"message":{"eventName":"presence","data":{"userid":"winey","isOnline":true}},"sender":"winey"} $query = " INSERT INTO active_broadcasts ( name ) VALUES ( '{$data['name']}' ) "; if( $mysqli->query( $query ) ) { $transport = json_encode( false ); exit( $transport ); } else exit( $mysqli->error ); } else exit( 'No data sent' ); ?> 

XHR-signalhandler-post.php :

 <?php require( "connection.inc.php" ); $response = array(); //{"message":{"eventName":"presence","data":{"userid":"winey","isOnline":true}},"sender":"winey"} // var_dump( $_POST ); // exit; if( isset( $_POST['message'] ) ) { $query = " INSERT INTO webrtc-messages ( name ) VALUES ( '{$_POST['name']}' ) "; if( $mysqli->query( $query ) ) { $transport = json_encode( false ); exit( $transport ); } else exit( $mysqli->error ); // Now, if someone else joins the room; you can update above record; and append his "user-id" in the "Participants-id" column. } if( @$_POST["message"] = "undefined" ) $response = false; $transport = json_encode( $response ); exit( $transport ); ?> 

XHR-signalhandler-get.php :

 <?php require( "connection.inc.php" ); $response = array(); // var_dump( $_POST ); if( isset( $_POST['message'] ) ) { $query = "SELECT id , message , channel , `sender-id` FROM `webrtc-messages` "; if( $mysqli->connect_errno ) exit ( "Failed to connect to MySQL: " . $mysqli->connect_error ); if( $res = $mysqli->query( $query ) ) { if( $res->num_rows > 0 ) { while( $value = mysqli_fetch_assoc( $res ) ) { // } } } else { echo "<center class='text-danger'>Server error</center>"; exit( $mysqli->error ); } } if( @$_POST["message"] = "undefined" ) $response = false; $transport = json_encode( $response ); exit( $transport ); ?> 
+7
javascript webrtc openwebrtc rtcmulticonnection
source share
1 answer

I have never played with WebRTC or RTCMultiConnection, but I think I understand where your problem came from.

Since you are using XHR, there is no direct connection between your client (web browser) and your server. Thus, the server cannot transmit information to the client, so the openSignalingChannel override will never be triggered.

The trick is to regularly call a function to check the status of the server (e.g. long polling).

If you check the RTCMultiConnection documentation on overriding openSignalingChannel ( http://www.rtcmulticonnection.org/docs/openSignalingChannel/#xhr-signaling ), you will notice that using repeatCheck () ;. I think this is the missing part of your puzzle.

Hope this helps.

+1
source share

All Articles