Add WebRTC data channel

This commit is contained in:
David Rowe 2021-05-31 12:29:48 +12:00
parent ce31b70a1d
commit 6c37628468
9 changed files with 791 additions and 15 deletions

View file

@ -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";

View file

@ -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
};

View file

@ -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)

View file

@ -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);
}

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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