mirror of
https://github.com/overte-org/overte.git
synced 2025-04-07 14:12:26 +02:00
Add WebRTC data channel
This commit is contained in:
parent
ce31b70a1d
commit
6c37628468
9 changed files with 791 additions and 15 deletions
|
@ -165,8 +165,9 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
|
|||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_gatekeeper(this),
|
||||
#ifdef WEBRTC_DATA_CHANNEL
|
||||
#ifdef WEBRTC_DATA_CHANNELS
|
||||
_webrtcSignalingServer(QHostAddress::AnyIPv4, DEFAULT_DOMAIN_SERVER_WS_PORT, this),
|
||||
_webrtcDataChannels(NodeType::DomainServer, this),
|
||||
#endif
|
||||
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT,
|
||||
QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
|
||||
|
@ -251,6 +252,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
updateDownstreamNodes();
|
||||
updateUpstreamNodes();
|
||||
|
||||
setUpWebRTC();
|
||||
|
||||
if (_type != NonMetaverse) {
|
||||
// if we have a metaverse domain, we'll use an access token for API calls
|
||||
resetAccountManagerAccessToken();
|
||||
|
@ -3136,6 +3139,18 @@ void DomainServer::updateUpstreamNodes() {
|
|||
updateReplicationNodes(Upstream);
|
||||
}
|
||||
|
||||
void DomainServer::setUpWebRTC() {
|
||||
|
||||
// Inbound WebRTC signaling messages received from a client.
|
||||
connect(&_webrtcSignalingServer, &WebRTCSignalingServer::messageReceived,
|
||||
&_webrtcDataChannels, &WebRTCDataChannels::onSignalingMessage);
|
||||
|
||||
// Outbound WebRTC signaling messages being sent to a client.
|
||||
connect(&_webrtcDataChannels, &WebRTCDataChannels::signalingMessage,
|
||||
&_webrtcSignalingServer, &WebRTCSignalingServer::sendMessage);
|
||||
|
||||
}
|
||||
|
||||
void DomainServer::initializeExporter() {
|
||||
static const QString ENABLE_EXPORTER = "monitoring.enable_prometheus_exporter";
|
||||
static const QString EXPORTER_PORT = "monitoring.prometheus_exporter_port";
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <HTTPSConnection.h>
|
||||
#include <LimitedNodeList.h>
|
||||
#include <shared/WebRTC.h>
|
||||
#include <webrtc/WebRTCDataChannels.h>
|
||||
#include <webrtc/WebRTCSignalingServer.h>
|
||||
|
||||
#include "AssetsBackupHandler.h"
|
||||
|
@ -143,6 +144,9 @@ private slots:
|
|||
void updateReplicatedNodes();
|
||||
void updateDownstreamNodes();
|
||||
void updateUpstreamNodes();
|
||||
|
||||
void setUpWebRTC();
|
||||
|
||||
void initializeExporter();
|
||||
void initializeMetadataExporter();
|
||||
|
||||
|
@ -314,8 +318,9 @@ private:
|
|||
|
||||
QThread _assetClientThread;
|
||||
|
||||
#ifdef WEBRTC_DATA_CHANNEL
|
||||
#ifdef WEBRTC_DATA_CHANNELS
|
||||
WebRTCSignalingServer _webrtcSignalingServer;
|
||||
WebRTCDataChannels _webrtcDataChannels;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ endif ()
|
|||
|
||||
if (WIN32)
|
||||
# we need ws2_32.lib on windows, but it's static so we don't bubble it up
|
||||
target_link_libraries(${TARGET_NAME} ws2_32.lib)
|
||||
# Libraries needed for WebRTC: security.log winmm.lib
|
||||
target_link_libraries(${TARGET_NAME} ws2_32.lib security.lib winmm.lib)
|
||||
elseif(APPLE)
|
||||
# IOKit is needed for getting machine fingerprint
|
||||
find_library(FRAMEWORK_IOKIT IOKit)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Stephen Birarda on 05/29/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2021 Vircadia contributors.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -14,6 +15,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
/// @file
|
||||
/// @brief NodeType
|
||||
|
||||
/// @brief An 8-bit value identifying the type of a node - domain server, audio mixer, etc.
|
||||
typedef quint8 NodeType_t;
|
||||
|
||||
namespace NodeType {
|
||||
|
@ -37,7 +42,6 @@ namespace NodeType {
|
|||
NodeType_t upstreamType(NodeType_t primaryType);
|
||||
NodeType_t downstreamType(NodeType_t primaryType);
|
||||
|
||||
|
||||
NodeType_t fromString(QString type);
|
||||
}
|
||||
|
||||
|
|
475
libraries/networking/src/webrtc/WebRTCDataChannels.cpp
Normal file
475
libraries/networking/src/webrtc/WebRTCDataChannels.cpp
Normal file
|
@ -0,0 +1,475 @@
|
|||
//
|
||||
// WebRTCDataChannels.cpp
|
||||
// libraries/networking/src/webrtc
|
||||
//
|
||||
// Created by David Rowe on 21 May 2021.
|
||||
// Copyright 2021 Vircadia contributors.
|
||||
//
|
||||
|
||||
#include "WebRTCDataChannels.h"
|
||||
|
||||
#if defined(WEBRTC_DATA_CHANNELS)
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "../NetworkLogging.h"
|
||||
|
||||
|
||||
// References:
|
||||
// - https://webrtc.github.io/webrtc-org/native-code/native-apis/
|
||||
// - https://webrtc.googlesource.com/src/+/master/api/peer_connection_interface.h
|
||||
|
||||
const std::string ICE_SERVER_URI = "stun://ice.vircadia.com:7337";
|
||||
|
||||
#define WEBRTC_DEBUG
|
||||
|
||||
|
||||
void WDCSetSessionDescriptionObserver::OnSuccess() {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCSetSessionDescriptionObserver::OnSuccess()";
|
||||
#endif
|
||||
}
|
||||
|
||||
void WDCSetSessionDescriptionObserver::OnFailure(RTCError error) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCSetSessionDescriptionObserver::OnFailure() :" << error.message();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
WDCCreateSessionDescriptionObserver::WDCCreateSessionDescriptionObserver(WDCConnection* parent) :
|
||||
_parent(parent)
|
||||
{ }
|
||||
|
||||
void WDCCreateSessionDescriptionObserver::OnSuccess(SessionDescriptionInterface* description) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCCreateSessionDescriptionObserver::OnSuccess()";
|
||||
#endif
|
||||
_parent->sendAnswer(description);
|
||||
_parent->setLocalDescription(description);
|
||||
}
|
||||
|
||||
void WDCCreateSessionDescriptionObserver::OnFailure(RTCError error) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCCreateSessionDescriptionObserver::OnFailure() :" << error.message();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
WDCPeerConnectionObserver::WDCPeerConnectionObserver(WDCConnection* parent) :
|
||||
_parent(parent)
|
||||
{ }
|
||||
|
||||
void WDCPeerConnectionObserver::OnSignalingChange(PeerConnectionInterface::SignalingState newState) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
QStringList states{
|
||||
"Stable",
|
||||
"HaveLocalOffer",
|
||||
"HaveLocalPrAnswer",
|
||||
"HaveRemoteOffer",
|
||||
"HaveRemotePrAnswer",
|
||||
"Closed"
|
||||
};
|
||||
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnSignalingChange()" << newState << states[newState];
|
||||
#endif
|
||||
}
|
||||
|
||||
void WDCPeerConnectionObserver::OnRenegotiationNeeded() {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnRenegotiationNeeded()";
|
||||
#endif
|
||||
}
|
||||
|
||||
void WDCPeerConnectionObserver::OnIceGatheringChange(PeerConnectionInterface::IceGatheringState newState) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceGatheringChange()" << newState;
|
||||
#endif
|
||||
}
|
||||
|
||||
void WDCPeerConnectionObserver::OnIceCandidate(const IceCandidateInterface* candidate) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceCandidate()";
|
||||
#endif
|
||||
_parent->sendIceCandidate(candidate);
|
||||
}
|
||||
|
||||
void WDCPeerConnectionObserver::OnDataChannel(rtc::scoped_refptr<DataChannelInterface> dataChannel) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnDataChannel()";
|
||||
#endif
|
||||
_parent->onDataChannelOpened(dataChannel);
|
||||
}
|
||||
|
||||
void WDCPeerConnectionObserver::OnConnectionChange(PeerConnectionInterface::PeerConnectionState newState) {
|
||||
}
|
||||
|
||||
|
||||
WDCDataChannelObserver::WDCDataChannelObserver(WDCConnection* parent) :
|
||||
_parent(parent)
|
||||
{ }
|
||||
|
||||
void WDCDataChannelObserver::OnStateChange() {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCDataChannelObserver::OnStateChange()";
|
||||
#endif
|
||||
_parent->onDataChannelStateChanged();
|
||||
}
|
||||
|
||||
void WDCDataChannelObserver::OnMessage(const DataBuffer& buffer) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCDataChannelObserver::OnMessage()";
|
||||
#endif
|
||||
_parent->onDataChannelMessageReceived(buffer);
|
||||
}
|
||||
|
||||
|
||||
WDCConnection::WDCConnection(quint16 webSocketID, WebRTCDataChannels* parent) :
|
||||
_webSocketID(webSocketID),
|
||||
_parent(parent)
|
||||
{
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::WebRTCDataChannels()";
|
||||
#endif
|
||||
|
||||
// Create observers.
|
||||
_setSessionDescriptionObserver = new rtc::RefCountedObject<WDCSetSessionDescriptionObserver>();
|
||||
_createSessionDescriptionObserver = new rtc::RefCountedObject<WDCCreateSessionDescriptionObserver>(this);
|
||||
_dataChannelObserver = std::make_shared<WDCDataChannelObserver>(this);
|
||||
_peerConnectionObserver = std::make_shared<WDCPeerConnectionObserver>(this);
|
||||
|
||||
// Create new peer connection.
|
||||
_peerConnection = _parent->createPeerConnection(_peerConnectionObserver);
|
||||
};
|
||||
|
||||
void WDCConnection::setRemoteDescription(QJsonObject& description) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::setRemoteDescription() :" << description;
|
||||
#endif
|
||||
|
||||
SdpParseError sdpParseError;
|
||||
auto sessionDescription = CreateSessionDescription(
|
||||
description.value("type").toString().toStdString(),
|
||||
description.value("sdp").toString().toStdString(),
|
||||
&sdpParseError);
|
||||
if (!sessionDescription) {
|
||||
qCWarning(networking_webrtc) << "Error creating WebRTC remote description:"
|
||||
<< QString::fromStdString(sdpParseError.description);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "3. Set remote description:" << sessionDescription;
|
||||
#endif
|
||||
_peerConnection->SetRemoteDescription(_setSessionDescriptionObserver, sessionDescription);
|
||||
}
|
||||
|
||||
void WDCConnection::createAnswer() {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::createAnswer()";
|
||||
qCDebug(networking_webrtc) << "4.a Create answer";
|
||||
#endif
|
||||
_peerConnection->CreateAnswer(_createSessionDescriptionObserver, PeerConnectionInterface::RTCOfferAnswerOptions());
|
||||
}
|
||||
|
||||
void WDCConnection::sendAnswer(SessionDescriptionInterface* description) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::sendAnswer()";
|
||||
qCDebug(networking_webrtc) << "4.b Send answer to the remote peer";
|
||||
#endif
|
||||
|
||||
QJsonObject jsonDescription;
|
||||
std::string descriptionString;
|
||||
description->ToString(&descriptionString);
|
||||
jsonDescription.insert("sdp", QString::fromStdString(descriptionString));
|
||||
jsonDescription.insert("type", "answer");
|
||||
|
||||
QJsonObject jsonWebRTCPayload;
|
||||
jsonWebRTCPayload.insert("description", jsonDescription);
|
||||
|
||||
QJsonObject jsonObject;
|
||||
jsonObject.insert("from", QString(_parent->getNodeType()));
|
||||
jsonObject.insert("to", _webSocketID);
|
||||
jsonObject.insert("data", jsonWebRTCPayload);
|
||||
|
||||
_parent->sendSignalingMessage(jsonObject);
|
||||
}
|
||||
|
||||
void WDCConnection::setLocalDescription(SessionDescriptionInterface* description) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::setLocalDescription()";
|
||||
qCDebug(networking_webrtc) << "5. Set local description";
|
||||
#endif
|
||||
_peerConnection->SetLocalDescription(_setSessionDescriptionObserver, description);
|
||||
}
|
||||
|
||||
void WDCConnection::addIceCandidate(QJsonObject& data) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::addIceCandidate()";
|
||||
#endif
|
||||
|
||||
SdpParseError sdpParseError;
|
||||
auto iceCandidate = CreateIceCandidate(
|
||||
data.value("sdpMid").toString().toStdString(),
|
||||
data.value("sdpMLineIndex").toInt(),
|
||||
data.value("candidate").toString().toStdString(),
|
||||
&sdpParseError);
|
||||
if (!iceCandidate) {
|
||||
qCWarning(networking_webrtc) << "Error adding WebRTC ICE candidate:"
|
||||
<< QString::fromStdString(sdpParseError.description);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "6. Add ICE candidate";
|
||||
#endif
|
||||
_peerConnection->AddIceCandidate(iceCandidate);
|
||||
}
|
||||
|
||||
void WDCConnection::sendIceCandidate(const IceCandidateInterface* candidate) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::sendIceCandidate()";
|
||||
#endif
|
||||
|
||||
std::string candidateString;
|
||||
candidate->ToString(&candidateString);
|
||||
QJsonObject jsonCandidate;
|
||||
jsonCandidate.insert("candidate", QString::fromStdString(candidateString));
|
||||
jsonCandidate.insert("sdpMid", QString::fromStdString(candidate->sdp_mid()));
|
||||
jsonCandidate.insert("sdpMLineIndex", candidate->sdp_mline_index());
|
||||
|
||||
QJsonObject jsonWebRTCData;
|
||||
jsonWebRTCData.insert("candidate", jsonCandidate);
|
||||
|
||||
QJsonObject jsonObject;
|
||||
jsonObject.insert("from", QString(_parent->getNodeType()));
|
||||
jsonObject.insert("to", _webSocketID);
|
||||
jsonObject.insert("data", jsonWebRTCData);
|
||||
QJsonDocument jsonDocument = QJsonDocument(jsonObject);
|
||||
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "7. Send ICE candidate to the remote peer";
|
||||
#endif
|
||||
_parent->sendSignalingMessage(jsonObject);
|
||||
}
|
||||
|
||||
void WDCConnection::onDataChannelOpened(rtc::scoped_refptr<DataChannelInterface> dataChannel) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::onDataChannelOpened() :"
|
||||
<< dataChannel->id()
|
||||
<< QString::fromStdString(dataChannel->label())
|
||||
<< QString::fromStdString(dataChannel->protocol())
|
||||
<< dataChannel->negotiated()
|
||||
<< dataChannel->maxRetransmitTime()
|
||||
<< dataChannel->maxRetransmits()
|
||||
<< dataChannel->maxPacketLifeTime().value_or(-1)
|
||||
<< dataChannel->maxRetransmitsOpt().value_or(-1);
|
||||
#endif
|
||||
|
||||
_dataChannel = dataChannel;
|
||||
_dataChannelID = dataChannel->id();
|
||||
_dataChannel->RegisterObserver(_dataChannelObserver.get());
|
||||
|
||||
_parent->onDataChannelOpened(this, _dataChannelID);
|
||||
}
|
||||
|
||||
void WDCConnection::onDataChannelStateChanged() {
|
||||
auto state = _dataChannel->state();
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::dataChannelStateChanged() :" << (int)state
|
||||
<< DataChannelInterface::DataStateString(state);
|
||||
#endif
|
||||
if (state == DataChannelInterface::kClosed) {
|
||||
_parent->onDataChannelClosed(this, _dataChannelID);
|
||||
}
|
||||
}
|
||||
|
||||
void WDCConnection::onDataChannelMessageReceived(const DataBuffer& buffer) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::onDataChannelMessageReceived()";
|
||||
#endif
|
||||
|
||||
auto byteArray = QByteArray(buffer.data.data<char>(), (int)buffer.data.size());
|
||||
|
||||
// Echo message back to sender.
|
||||
if (byteArray.startsWith("echo:")) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "Echo message back";
|
||||
#endif
|
||||
_parent->sendDataMessage(_dataChannelID, byteArray); // Use parent method to exercise the code stack.
|
||||
return;
|
||||
}
|
||||
|
||||
_parent->emitDataMessage(_dataChannelID, byteArray);
|
||||
}
|
||||
|
||||
bool WDCConnection::sendDataMessage(const DataBuffer& buffer) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WDCConnection::sendDataMessage()";
|
||||
#endif
|
||||
const int MAX_WEBRTC_BUFFER_SIZE = 16 * 1024 * 1024; // 16MB
|
||||
if (_dataChannel->buffered_amount() + buffer.size() > MAX_WEBRTC_BUFFER_SIZE) {
|
||||
// Don't send, otherwise the data channel will be closed.
|
||||
return false;
|
||||
}
|
||||
return _dataChannel->Send(buffer);
|
||||
}
|
||||
|
||||
|
||||
WebRTCDataChannels::WebRTCDataChannels(NodeType_t nodeType, QObject* parent) :
|
||||
_nodeType(nodeType),
|
||||
_parent(parent)
|
||||
{
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::WebRTCDataChannels()";
|
||||
#endif
|
||||
|
||||
// Create a peer connection factory.
|
||||
#ifdef WEBRTC_DEBUG
|
||||
// Numbers are per WebRTC's peer_connection_interface.h.
|
||||
qCDebug(networking_webrtc) << "1. Create a new PeerConnectionFactoryInterface";
|
||||
#endif
|
||||
_rtcNetworkThread = rtc::Thread::CreateWithSocketServer();
|
||||
_rtcNetworkThread->Start();
|
||||
_rtcWorkerThread = rtc::Thread::Create();
|
||||
_rtcWorkerThread->Start();
|
||||
_rtcSignalingThread = rtc::Thread::Create();
|
||||
_rtcSignalingThread->Start();
|
||||
PeerConnectionFactoryDependencies dependencies;
|
||||
dependencies.network_thread = _rtcNetworkThread.get();
|
||||
dependencies.worker_thread = _rtcWorkerThread.get();
|
||||
dependencies.signaling_thread = _rtcSignalingThread.get();
|
||||
_peerConnectionFactory = CreateModularPeerConnectionFactory(std::move(dependencies));
|
||||
if (!_peerConnectionFactory) {
|
||||
qCWarning(networking_webrtc) << "Failed to create WebRTC peer connection factory";
|
||||
}
|
||||
}
|
||||
|
||||
WebRTCDataChannels::~WebRTCDataChannels() {
|
||||
QHashIterator<int, WDCConnection*> i(_connectionsByDataChannel);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
delete i.value();
|
||||
}
|
||||
_connectionsByWebSocket.clear();
|
||||
_connectionsByDataChannel.clear();
|
||||
|
||||
_peerConnectionFactory = nullptr;
|
||||
_rtcSignalingThread->Stop();
|
||||
_rtcSignalingThread = nullptr;
|
||||
_rtcWorkerThread->Stop();
|
||||
_rtcWorkerThread = nullptr;
|
||||
_rtcNetworkThread->Stop();
|
||||
_rtcNetworkThread = nullptr;
|
||||
}
|
||||
|
||||
void WebRTCDataChannels::onDataChannelOpened(WDCConnection* connection, int dataChannelID) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::onDataChannelOpened() :" << dataChannelID;
|
||||
#endif
|
||||
_connectionsByDataChannel.insert(dataChannelID, connection);
|
||||
}
|
||||
|
||||
void WebRTCDataChannels::onDataChannelClosed(WDCConnection* connection, int dataChannelID) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::onDataChannelClosed() :" << dataChannelID;
|
||||
#endif
|
||||
|
||||
// Delete WDCConnection.
|
||||
_connectionsByWebSocket.remove(connection->getWebSocketID());
|
||||
_connectionsByDataChannel.remove(dataChannelID);
|
||||
delete connection;
|
||||
}
|
||||
|
||||
void WebRTCDataChannels::onSignalingMessage(const QJsonObject& message) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannel::onSignalingMessage()" << message;
|
||||
#endif
|
||||
|
||||
// Validate message.
|
||||
const int MAX_DEBUG_DETAIL_LENGTH = 64;
|
||||
auto data = message.value("data").isObject() ? message.value("data").toObject() : QJsonObject();
|
||||
int from = message.value("from").isDouble() ? (quint16)(message.value("from").toInt()) : 0;
|
||||
if (from <= 0 || from > MAXUINT16 || !data.contains("description") && !data.contains("candidate")) {
|
||||
qCWarning(networking_webrtc) << "Unexpected signaling message:"
|
||||
<< QJsonDocument(message).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find or create a connection.
|
||||
WDCConnection* connection;
|
||||
if (_connectionsByWebSocket.contains(from)) {
|
||||
connection = _connectionsByWebSocket.value(from);
|
||||
} else {
|
||||
connection = new WDCConnection(from, this);
|
||||
_connectionsByWebSocket.insert(from, connection);
|
||||
}
|
||||
|
||||
// Set the remote description and reply with an answer.
|
||||
if (data.contains("description")) {
|
||||
auto description = data.value("description").toObject();
|
||||
if (description.value("type").toString() == "offer") {
|
||||
connection->setRemoteDescription(description);
|
||||
connection->createAnswer();
|
||||
} else {
|
||||
qCWarning(networking_webrtc) << "Unexpected signaling description:"
|
||||
<< QJsonDocument(description).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a remote ICE candidate.
|
||||
if (data.contains("candidate")) {
|
||||
connection->addIceCandidate(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void WebRTCDataChannels::sendSignalingMessage(const QJsonObject& message) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendSignalingMessage() :" << QJsonDocument(message).toJson(QJsonDocument::Compact);
|
||||
#endif
|
||||
emit signalingMessage(message);
|
||||
}
|
||||
|
||||
void WebRTCDataChannels::emitDataMessage(int dataChannelID, const QByteArray& byteArray) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::emitDataMessage() :" << dataChannelID;
|
||||
#endif
|
||||
emit dataMessage(dataChannelID, byteArray);
|
||||
}
|
||||
|
||||
bool WebRTCDataChannels::sendDataMessage(int dataChannelID, const QByteArray& byteArray) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendDataMessage() :" << dataChannelID;
|
||||
#endif
|
||||
|
||||
// Find connection.
|
||||
if (!_connectionsByDataChannel.contains(dataChannelID)) {
|
||||
qCWarning(networking_webrtc) << "Could not find data channel to send message on!";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto connection = _connectionsByDataChannel.value(dataChannelID);
|
||||
DataBuffer buffer(byteArray.toStdString(), true);
|
||||
return connection->sendDataMessage(buffer);
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<PeerConnectionInterface> WebRTCDataChannels::createPeerConnection(
|
||||
const std::shared_ptr<WDCPeerConnectionObserver> peerConnectionObserver) {
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "WebRTCDataChannels::createPeerConnection()";
|
||||
#endif
|
||||
|
||||
PeerConnectionInterface::RTCConfiguration configuration;
|
||||
PeerConnectionInterface::IceServer iceServer;
|
||||
iceServer.uri = ICE_SERVER_URI;
|
||||
configuration.servers.push_back(iceServer);
|
||||
|
||||
#ifdef WEBRTC_DEBUG
|
||||
qCDebug(networking_webrtc) << "2. Create a new PeerConnection";
|
||||
#endif
|
||||
return _peerConnectionFactory->CreatePeerConnection(configuration, nullptr, nullptr, peerConnectionObserver.get());
|
||||
}
|
||||
|
||||
|
||||
#endif // WEBRTC_DATA_CHANNELS
|
278
libraries/networking/src/webrtc/WebRTCDataChannels.h
Normal file
278
libraries/networking/src/webrtc/WebRTCDataChannels.h
Normal file
|
@ -0,0 +1,278 @@
|
|||
//
|
||||
// WebRTCDataChannels.h
|
||||
// libraries/networking/src/webrtc
|
||||
//
|
||||
// Created by David Rowe on 21 May 2021.
|
||||
// Copyright 2021 Vircadia contributors.
|
||||
//
|
||||
|
||||
#ifndef vircadia_WebRTCDataChannels_h
|
||||
#define vircadia_WebRTCDataChannels_h
|
||||
|
||||
#include <shared/WebRTC.h>
|
||||
|
||||
#if defined(WEBRTC_DATA_CHANNELS)
|
||||
|
||||
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
|
||||
#undef emit // Avoid conflict between Qt signals/slots and the WebRTC library's.
|
||||
#include <api/peer_connection_interface.h>
|
||||
#define emit
|
||||
|
||||
#include "../NodeType.h"
|
||||
|
||||
using namespace webrtc;
|
||||
|
||||
class WebRTCDataChannels;
|
||||
class WDCConnection;
|
||||
|
||||
|
||||
// A WebRTC data channel session description observer.
|
||||
class WDCSetSessionDescriptionObserver : public SetSessionDescriptionObserver {
|
||||
public:
|
||||
// The call to SetLocalDescription or SetRemoteDescription succeeded.
|
||||
void OnSuccess() override;
|
||||
|
||||
// The call to SetLocalDescription or SetRemoteDescription failed.
|
||||
void OnFailure(RTCError error) override;
|
||||
};
|
||||
|
||||
|
||||
// A WebRTC data channel create session description observer.
|
||||
class WDCCreateSessionDescriptionObserver : public CreateSessionDescriptionObserver {
|
||||
public:
|
||||
WDCCreateSessionDescriptionObserver(WDCConnection* parent);
|
||||
|
||||
// The call to CreateAnswer succeeded.
|
||||
void OnSuccess(SessionDescriptionInterface* desc) override;
|
||||
|
||||
// The call to CreateAnswer failed.
|
||||
void OnFailure(RTCError error) override;
|
||||
|
||||
private:
|
||||
WDCConnection* _parent;
|
||||
};
|
||||
|
||||
|
||||
// A WebRTC data channel peer connection observer.
|
||||
class WDCPeerConnectionObserver : public PeerConnectionObserver {
|
||||
public:
|
||||
WDCPeerConnectionObserver(WDCConnection* parent);
|
||||
|
||||
// Triggered when the SignalingState changed.
|
||||
void OnSignalingChange(PeerConnectionInterface::SignalingState newState) override;
|
||||
|
||||
// Triggered when renegotiation is needed. For example, an ICE restart has begun.
|
||||
void OnRenegotiationNeeded() override;
|
||||
|
||||
// Called any time the IceGatheringState changes.
|
||||
void OnIceGatheringChange(PeerConnectionInterface::IceGatheringState newState) override;
|
||||
|
||||
// A new ICE candidate has been gathered.
|
||||
void OnIceCandidate(const IceCandidateInterface* candidate) override;
|
||||
|
||||
// Triggered when a remote peer opens a data channel.
|
||||
void OnDataChannel(rtc::scoped_refptr<DataChannelInterface> dataChannel) override;
|
||||
|
||||
// Called any time the PeerConnectionState changes.
|
||||
void OnConnectionChange(PeerConnectionInterface::PeerConnectionState newState) override;
|
||||
|
||||
private:
|
||||
WDCConnection* _parent;
|
||||
};
|
||||
|
||||
|
||||
// A WebRTC data channel observer.
|
||||
class WDCDataChannelObserver : public DataChannelObserver {
|
||||
public:
|
||||
WDCDataChannelObserver(WDCConnection* parent);
|
||||
|
||||
// The data channel state changed.
|
||||
void OnStateChange() override;
|
||||
|
||||
// A data buffer was successfully received.
|
||||
void OnMessage(const DataBuffer& buffer) override;
|
||||
|
||||
private:
|
||||
WDCConnection* _parent;
|
||||
};
|
||||
|
||||
|
||||
/// @brief A WebRTC data channel connection.
|
||||
/// @details Opens and manages a WebRTC data channel connection.
|
||||
class WDCConnection {
|
||||
|
||||
public:
|
||||
/// @brief Constructs a new WDCConnection and opens a WebRTC data connection.
|
||||
/// @param webSocketID The signaling channel that initiated the opening of the WebRTC data channel.
|
||||
/// @param parent The parent WebRTCDataChannels object.
|
||||
WDCConnection(quint16 webSocketID, WebRTCDataChannels* parent);
|
||||
|
||||
/// @brief Gets the WebSocket ID.
|
||||
/// @return The ID of the WebSocket.
|
||||
quint16 getWebSocketID() { return _webSocketID; }
|
||||
|
||||
/// @brief Gets the WebRTC data channel ID.
|
||||
/// @return The WebRTC data channel ID. `-1` if not open yet.
|
||||
int getDataChannelID() { return _dataChannelID; }
|
||||
|
||||
|
||||
/// @brief Sets the remote session description received from the remote client via the signaling channel.
|
||||
/// @param description The remote session description.
|
||||
void setRemoteDescription(QJsonObject& description);
|
||||
|
||||
/// @brief Creates an answer to an offer received from the remote client via the signaling channel.
|
||||
void createAnswer();
|
||||
|
||||
/// @brief Sends an answer to the remote client via the signaling channel.
|
||||
/// @param description The answer.
|
||||
void sendAnswer(SessionDescriptionInterface* description);
|
||||
|
||||
/// @brief Sets the local session description on the WebRTC data channel being connected.
|
||||
/// @param description The local session description.
|
||||
void setLocalDescription(SessionDescriptionInterface* description);
|
||||
|
||||
/// @brief Adds an ICE candidate received from the remote client via the signaling channel.
|
||||
/// @param data The ICE candidate.
|
||||
void addIceCandidate(QJsonObject& data);
|
||||
|
||||
/// @brief Sends an ICE candidate to the remote vlient via the signaling channel.
|
||||
/// @param candidate The ICE candidate.
|
||||
void sendIceCandidate(const IceCandidateInterface* candidate);
|
||||
|
||||
/// @brief Handles the WebRTC data channel being opened.
|
||||
/// @param dataChannel The WebRTC data channel.
|
||||
void onDataChannelOpened(rtc::scoped_refptr<DataChannelInterface> dataChannel);
|
||||
|
||||
/// @brief Handles a change in the state of the WebRTC data channel.
|
||||
void onDataChannelStateChanged();
|
||||
|
||||
|
||||
/// @brief Handles a message being received on the WebRTC data channel.
|
||||
/// @param buffer The message received.
|
||||
void onDataChannelMessageReceived(const DataBuffer& buffer);
|
||||
|
||||
/// @brief Sends a message on the WebRTC data channel.
|
||||
/// @param buffer The message to send.
|
||||
/// @return `true` if the message was sent, otherwise `false`.
|
||||
bool sendDataMessage(const DataBuffer& buffer);
|
||||
|
||||
private:
|
||||
WebRTCDataChannels* _parent;
|
||||
quint16 _webSocketID { 0 };
|
||||
int _dataChannelID { -1 };
|
||||
|
||||
rtc::scoped_refptr<WDCSetSessionDescriptionObserver> _setSessionDescriptionObserver { nullptr };
|
||||
rtc::scoped_refptr<WDCCreateSessionDescriptionObserver> _createSessionDescriptionObserver { nullptr };
|
||||
|
||||
std::shared_ptr<WDCDataChannelObserver> _dataChannelObserver { nullptr };
|
||||
rtc::scoped_refptr<DataChannelInterface> _dataChannel { nullptr };
|
||||
|
||||
std::shared_ptr<WDCPeerConnectionObserver> _peerConnectionObserver { nullptr };
|
||||
rtc::scoped_refptr<PeerConnectionInterface> _peerConnection { nullptr };
|
||||
};
|
||||
|
||||
|
||||
/// @brief Manages WebRTC data channels on the domain server or an assignment clients that Interface clients can connect to.
|
||||
///
|
||||
/// @details Presents multiple individual WebRTC data channels as a single one-to-many WebRTCDataChannels object. Interface
|
||||
/// clients may use WebRTC data channels for Vircadia protocol network communications instead of UDP.
|
||||
/// A WebRTCSignalingServer is used in the process of setting up a WebRTC data channel between an Interface client and the
|
||||
/// domain server or assignment client.
|
||||
/// The Interface client initiates the connection - including initiating the data channel - and the domain server or assignment
|
||||
/// client responds.
|
||||
///
|
||||
/// Additionally, for debugging purposes, instead of containing a Vircadia protocol payload, a WebRTC message may be an echo
|
||||
/// request. This is bounced back to the client.
|
||||
///
|
||||
class WebRTCDataChannels : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// @brief Constructs a new WebRTCDataChannels object.
|
||||
/// @paramm nodeType The type of node that the WebRTCDataChannels object is being used in.
|
||||
/// @param parent The parent Qt object.
|
||||
WebRTCDataChannels(NodeType_t nodeType, QObject* parent);
|
||||
|
||||
/// @brief Destroys a WebRTCDataChannels object.
|
||||
~WebRTCDataChannels();
|
||||
|
||||
/// @brief Returns the type of node that the WebRTCDataChannels object is being used in.
|
||||
/// @return The type of node.
|
||||
NodeType_t getNodeType() {
|
||||
return _nodeType;
|
||||
}
|
||||
|
||||
/// @brief Handles a WebRTC data channel opening.
|
||||
/// @param connection The WebRTC data channel connection.
|
||||
/// @param dataChannelID The WebRTC data channel ID.
|
||||
void onDataChannelOpened(WDCConnection* connection, int dataChannelID);
|
||||
|
||||
/// @brief Handles a WebRTC data channel closing.
|
||||
/// @param connection The WebRTC data channel connection.
|
||||
/// @param dataChannelID The WebRTC data channel ID.
|
||||
void onDataChannelClosed(WDCConnection* connection, int dataChannelID);
|
||||
|
||||
/// @brief Emits a signalingMessage received for the Interface client.
|
||||
/// @param message The WebRTC signaling message to send.
|
||||
void sendSignalingMessage(const QJsonObject& message);
|
||||
|
||||
/// @brief Emits a dataMessage received from the Interface client.
|
||||
/// @param dataChannelID The WebRTC data channel the message was received on.
|
||||
/// @param byteArray The data message received.
|
||||
void emitDataMessage(int dataChannelID, const QByteArray& byteArray);
|
||||
|
||||
/// @brief Sends a data message to an Interface client.
|
||||
/// @param dataChannelID The WebRTC channel ID of the Interface client.
|
||||
/// @param message The data message to send.
|
||||
/// @return `true` if the data message was sent, otherwise `false`.
|
||||
bool sendDataMessage(int dataChannelID, const QByteArray& message);
|
||||
|
||||
/// @brief Creates a new WebRTC peer connection for connecting to an Interface client.
|
||||
/// @param peerConnectionObserver An observer to monitor the WebRTC peer connection.
|
||||
/// @return The new WebRTC peer connection.
|
||||
rtc::scoped_refptr<PeerConnectionInterface> createPeerConnection(
|
||||
const std::shared_ptr<WDCPeerConnectionObserver> peerConnectionObserver);
|
||||
|
||||
public slots:
|
||||
|
||||
/// @brief Handles a WebRTC signaling message received from the Interface client.
|
||||
/// @param message The WebRTC signaling message.
|
||||
void onSignalingMessage(const QJsonObject& message);
|
||||
|
||||
signals:
|
||||
|
||||
/// @brief A WebRTC signaling message to be sent to the Interface client.
|
||||
/// @description This message is for the WebRTCSignalingServer to send.
|
||||
/// @param message The WebRTC signaling message to send.
|
||||
void signalingMessage(const QJsonObject& message);
|
||||
|
||||
/// @brief A WebRTC data message received from the Interface client.
|
||||
/// @description This message is for handling at a higher level in the Vircadia protocol.
|
||||
/// @param dataChannelID The WebRTC data channel ID.
|
||||
/// @param byteArray The Vircadia protocol message.
|
||||
void dataMessage(int dataChannelID, const QByteArray& byteArray);
|
||||
|
||||
private:
|
||||
|
||||
QObject* _parent;
|
||||
|
||||
NodeType_t _nodeType;
|
||||
|
||||
std::unique_ptr<rtc::Thread> _rtcNetworkThread { nullptr };
|
||||
std::unique_ptr<rtc::Thread> _rtcWorkerThread { nullptr };
|
||||
std::unique_ptr<rtc::Thread> _rtcSignalingThread { nullptr };
|
||||
|
||||
rtc::scoped_refptr<PeerConnectionFactoryInterface> _peerConnectionFactory { nullptr };
|
||||
|
||||
QHash<quint16, WDCConnection*> _connectionsByWebSocket;
|
||||
QHash<int, WDCConnection*> _connectionsByDataChannel;
|
||||
};
|
||||
|
||||
|
||||
#endif // WEBRTC_DATA_CHANNELS
|
||||
|
||||
#endif // vircadia_WebRTCDataChannels_h
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "WebRTCSignalingServer.h"
|
||||
|
||||
#if defined(WEBRTC_DATA_CHANNEL)
|
||||
#if defined(WEBRTC_DATA_CHANNELS)
|
||||
|
||||
#include <QtCore>
|
||||
#include <QWebSocket>
|
||||
|
@ -93,4 +93,4 @@ void WebRTCSignalingServer::newWebSocketConnection() {
|
|||
_webSockets.insert(webSocket->peerPort(), webSocket);
|
||||
}
|
||||
|
||||
#endif // WEBRTC_DATA_CHANNEL
|
||||
#endif // WEBRTC_DATA_CHANNELS
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
// WebRTCSignalingServer.h
|
||||
// libraries/networking/src/webrtc
|
||||
//
|
||||
// Provides a signaling channel for setting up WebRTC connections between the Web app and the domain servers and mixers.
|
||||
//
|
||||
// Created by David Rowe on 16 May 2021.
|
||||
// Copyright 2021 Vircadia contributors.
|
||||
//
|
||||
|
||||
#ifndef vircadia_SignalingServer_h
|
||||
#define vircadia_SignalingServer_h
|
||||
#ifndef vircadia_WebRTCSignalingServer_h
|
||||
#define vircadia_WebRTCSignalingServer_h
|
||||
|
||||
#include <shared/WebRTC.h>
|
||||
|
||||
#if defined(WEBRTC_DATA_CHANNEL)
|
||||
#if defined(WEBRTC_DATA_CHANNELS)
|
||||
|
||||
#include <QObject>
|
||||
#include <QtCore/QTimer>
|
||||
|
@ -99,6 +97,6 @@ private:
|
|||
};
|
||||
|
||||
|
||||
#endif // WEBRTC_DATA_CHANNEL
|
||||
#endif // WEBRTC_DATA_CHANNELS
|
||||
|
||||
#endif // vircadia_SignalingServer_h
|
||||
#endif // vircadia_WebRTCSignalingServer_h
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#endif
|
||||
|
||||
// WEBRTC_AUDIO: WebRTC audio features, e.g., echo canceling.
|
||||
// WEBRTC_DATA_CHANNEL: WebRTC client-server connections in parallel with UDP.
|
||||
// WEBRTC_DATA_CHANNELS: WebRTC client-server connections in parallel with UDP.
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
# define WEBRTC_AUDIO 1
|
||||
|
@ -25,7 +25,7 @@
|
|||
# define WEBRTC_LEGACY 1
|
||||
#elif defined(Q_OS_WIN)
|
||||
# define WEBRTC_AUDIO 1
|
||||
# define WEBRTC_DATA_CHANNEL 1
|
||||
# define WEBRTC_DATA_CHANNELS 1
|
||||
# define WEBRTC_WIN 1
|
||||
# define NOMINMAX 1
|
||||
# define WIN32_LEAN_AND_MEAN 1
|
||||
|
|
Loading…
Reference in a new issue