本地主机:如何设置 XHR 信号(connection.openSignalingChannel 未被调用)

localhost: how to setup XHR-Signaling (connection.openSignalingChannel not getting called)

我正在使用 RTCMultiConnection v3.4.4

我想 运行 在本地主机上使用 WebRTC。我选择了 XHR-Signaling,因为我希望项目完全离线。我不希望它依赖于互联网,因为一切都在本地主机上(稍后部署在局域网上)

我已经包含了XHRConnection.js并设置了connection.setCustomSocketHandler(XHRConnection)。我也做了覆盖 connection.openSignalingChannel...

然而,当我 open/start 房间时,我的视频显示了,但被 disableInputButtons() 禁用的按钮仍然保持禁用状态。聊天不工作。

我在覆盖 connection.openSignalingChannel... 时做了一个 console.log 来确认它是否被调用过,但它没有。

请帮助了解如何在本地主机上实施 XHR 信令。

谢谢。

代码:

文件: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);
}

开始-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's "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 );
?>

我从未玩过 WebRTC 或 RTCMultiConnection,但我想我明白你的问题出在哪里。

当您使用 XHR 时,您的客户端(网络浏览器)和服务器之间没有直接连接。因此,服务器无法向客户端推送信息,因此永远不会触发 openSignalingChannel 覆盖。

诀窍是定期调用一个函数来检查服务器状态(也称为长轮询)。

如果您查看关于 openSignalingChannel 覆盖 (http://www.rtcmulticonnection.org/docs/openSignalingChannel/#xhr-signaling) 的 RTCMultiConnection 文档,您会注意到 repeatedlyCheck(); 的使用。我认为这是你的拼图缺失的部分。

希望对您有所帮助。