diff --git a/libs/Microsoft.MixedReality.WebRTC.Native/include/api.h b/libs/Microsoft.MixedReality.WebRTC.Native/include/api.h
index 80c52a488..992a0f5c8 100644
--- a/libs/Microsoft.MixedReality.WebRTC.Native/include/api.h
+++ b/libs/Microsoft.MixedReality.WebRTC.Native/include/api.h
@@ -24,6 +24,22 @@ rtc::Thread* UnsafeGetWorkerThread();
extern "C" {
+//
+// Errors
+//
+
+using mrsResult = std::uint32_t;
+
+constexpr const mrsResult MRS_SUCCESS{0};
+
+// Peer conection (0x0xx)
+constexpr const mrsResult MRS_E_INVALID_PEER_HANDLE{0x8000001};
+constexpr const mrsResult MRS_E_PEER_NOT_INITIALIZED{0x8000002};
+
+// Data (0x3xx)
+constexpr const mrsResult MRS_E_SCTP_NOT_NEGOTIATED{0x8000301};
+constexpr const mrsResult MRS_E_INVALID_DATA_CHANNEL_ID{0x8000302};
+
//
// Generic utilities
//
@@ -243,7 +259,7 @@ mrsPeerConnectionAddLocalAudioTrack(PeerConnectionHandle peerHandle) noexcept;
/// - If id >= 0, then it adds a new out-of-band negotiated channel with the
/// given ID, and it is the responsibility of the app to create a channel with
/// the same ID on the remote peer to be able to use the channel.
-MRS_API bool MRS_CALL mrsPeerConnectionAddDataChannel(
+MRS_API mrsResult MRS_CALL mrsPeerConnectionAddDataChannel(
PeerConnectionHandle peerHandle,
int id, // -1 for auto, >=0 for negotiated
const char* label, // optional, can be null or empty string
diff --git a/libs/Microsoft.MixedReality.WebRTC.Native/src/api.cpp b/libs/Microsoft.MixedReality.WebRTC.Native/src/api.cpp
index 280eaf2b8..d7431fb04 100644
--- a/libs/Microsoft.MixedReality.WebRTC.Native/src/api.cpp
+++ b/libs/Microsoft.MixedReality.WebRTC.Native/src/api.cpp
@@ -498,7 +498,7 @@ mrsPeerConnectionAddLocalAudioTrack(PeerConnectionHandle peerHandle) noexcept {
return false;
}
-bool MRS_CALL mrsPeerConnectionAddDataChannel(
+mrsResult MRS_CALL mrsPeerConnectionAddDataChannel(
PeerConnectionHandle peerHandle,
int id,
const char* label,
@@ -519,7 +519,7 @@ bool MRS_CALL mrsPeerConnectionAddDataChannel(
DataChannelBufferingCallback{buffering_callback, buffering_user_data},
DataChannelStateCallback{state_callback, state_user_data});
}
- return false;
+ return MRS_E_INVALID_PEER_HANDLE;
}
void MRS_CALL mrsPeerConnectionRemoveLocalVideoTrack(
diff --git a/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.cpp b/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.cpp
index 8f80db190..87db0ebb3 100644
--- a/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.cpp
+++ b/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.cpp
@@ -160,12 +160,12 @@ bool PeerConnection::AddDataChannel(
config.id = id;
} else {
// Valid IDs are 0-65535 (16 bits)
- return false;
+ return MRS_E_INVALID_DATA_CHANNEL_ID;
}
if (!sctp_negotiated_) {
// Don't try to create a data channel without SCTP negotiation, it will get
// stuck in the kConnecting state forever.
- return false;
+ return MRS_E_SCTP_NOT_NEGOTIATED;
}
std::string labelString;
if (label)
@@ -275,8 +275,8 @@ bool PeerConnection::CreateOffer() noexcept {
options.offer_to_receive_audio = true;
options.offer_to_receive_video = true;
}
- if (!data_channel_from_id_.empty()) {
- sctp_negotiated_ = true;
+ if (data_channel_from_id_.empty()) {
+ sctp_negotiated_ = false;
}
peer_->CreateOffer(this, options);
return true;
@@ -299,6 +299,9 @@ bool PeerConnection::SetRemoteDescription(const char* type,
const char* sdp) noexcept {
if (!peer_)
return false;
+ if (data_channel_from_id_.empty()) {
+ sctp_negotiated_ = false;
+ }
std::string sdp_type_str(type);
auto sdp_type = webrtc::SdpTypeFromString(sdp_type_str);
if (!sdp_type.has_value())
diff --git a/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.h b/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.h
index 2f0012c6a..33c46ce73 100644
--- a/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.h
+++ b/libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.h
@@ -232,7 +232,7 @@ class PeerConnection : public webrtc::PeerConnectionObserver,
/// data channel is created before the connection is established, which will
/// force the connection to negotiate the necessary SCTP information. See
/// https://stackoverflow.com/questions/43788872/how-are-data-channels-negotiated-between-two-peers-with-webrtc
- bool sctp_negotiated_ = false;
+ bool sctp_negotiated_ = true;
private:
PeerConnection(const PeerConnection&) = delete;
diff --git a/libs/Microsoft.MixedReality.WebRTC/PeerConnection.cs b/libs/Microsoft.MixedReality.WebRTC/PeerConnection.cs
index e1174fc06..612983031 100644
--- a/libs/Microsoft.MixedReality.WebRTC/PeerConnection.cs
+++ b/libs/Microsoft.MixedReality.WebRTC/PeerConnection.cs
@@ -672,9 +672,8 @@ public void Dispose()
/// On UWP this requires the "webcam" capability.
/// See
/// for more details.
- ///
- /// This method throws an exception if the peer connection is not initialized.
///
+ /// The peer connection is not intialized.
public Task AddLocalVideoTrackAsync(VideoCaptureDevice device = default(VideoCaptureDevice), bool enableMrc = false)
{
ThrowIfConnectionNotOpen();
@@ -692,7 +691,7 @@ public void Dispose()
///
/// Remove from the current connection the local video track added with .
///
- /// This method throws an exception if the peer connection is not initialized.
+ /// The peer connection is not intialized.
public void RemoveLocalVideoTrack()
{
ThrowIfConnectionNotOpen();
@@ -707,9 +706,8 @@ public void RemoveLocalVideoTrack()
/// On UWP this requires the "microphone" capability.
/// See
/// for more details.
- ///
- /// This method throws an exception if the peer connection is not initialized.
///
+ /// The peer connection is not intialized.
public Task AddLocalAudioTrackAsync()
{
ThrowIfConnectionNotOpen();
@@ -727,7 +725,7 @@ public Task AddLocalAudioTrackAsync()
///
/// Remove from the current connection the local audio track added with .
///
- /// This method throws an exception if the peer connection is not initialized.
+ /// The peer connection is not intialized.
public void RemoveLocalAudioTrack()
{
ThrowIfConnectionNotOpen();
@@ -756,6 +754,9 @@ public void RemoveLocalAudioTrack()
/// Indicates whether data channel messages are reliably delivered
/// (see ).
/// Returns a task which completes once the data channel is created.
+ /// The peer connection is not intialized.
+ /// SCTP not negotiated.
+ /// Invalid data channel ID, must be in [0:65535].
public async Task AddDataChannelAsync(ushort id, string label, bool ordered, bool reliable)
{
if (id < 0)
@@ -783,6 +784,9 @@ public async Task AddDataChannelAsync(ushort id, string label, bool
/// Indicates whether data channel messages are reliably delivered
/// (see ).
/// Returns a task which completes once the data channel is created.
+ /// The peer connection is not intialized.
+ /// SCTP not negotiated.
+ /// Invalid data channel ID, must be in [0:65535].
public async Task AddDataChannelAsync(string label, bool ordered, bool reliable)
{
return await AddDataChannelAsyncImpl(-1, label, ordered, reliable);
@@ -798,6 +802,9 @@ public async Task AddDataChannelAsync(string label, bool ordered, b
/// Indicates whether data channel messages are reliably delivered
/// (see ).
/// Returns a task which completes once the data channel is created.
+ /// The peer connection is not intialized.
+ /// SCTP not negotiated.
+ /// Invalid data channel ID, must be in [0:65535].
private async Task AddDataChannelAsyncImpl(int id, string label, bool ordered, bool reliable)
{
// Preconditions
@@ -821,13 +828,22 @@ private async Task AddDataChannelAsyncImpl(int id, string label, bo
// Create the native channel
return await Task.Run(() =>
{
- if (NativeMethods.PeerConnectionAddDataChannel(_nativePeerhandle, id, label, ordered, reliable,
- args.MessageCallback, userData, args.BufferingCallback, userData, args.StateCallback, userData))
+ uint res = NativeMethods.PeerConnectionAddDataChannel(_nativePeerhandle, id, label, ordered, reliable,
+ args.MessageCallback, userData, args.BufferingCallback, userData, args.StateCallback, userData);
+ if (res == 0)
{
return dataChannel;
}
handle.Free();
- return null;
+ if (res == 0x80000301) // MRS_E_SCTP_NOT_NEGOTIATED
+ {
+ throw new InvalidOperationException("Cannot add a first data channel after the connection handshake started. Call AddDataChannelAsync() before calling CreateOffer().");
+ }
+ if (res == 0x80000302) // MRS_E_INVALID_DATA_CHANNEL_ID
+ {
+ throw new ArgumentOutOfRangeException("id", id, "Invalid ID passed to AddDataChannelAsync().");
+ }
+ throw new Exception("AddDataChannelAsync() failed.");
});
}
@@ -854,24 +870,44 @@ internal void SendDataChannelMessage(int id, byte[] message)
///
///
///
+ /// The peer connection is not intialized.
public void AddIceCandidate(string sdpMid, int sdpMlineindex, string candidate)
{
ThrowIfConnectionNotOpen();
NativeMethods.PeerConnectionAddIceCandidate(_nativePeerhandle, sdpMid, sdpMlineindex, candidate);
}
+ ///
+ /// Create an SDP offer message as an attempt to establish a connection.
+ ///
+ /// true if the offer was created successfully.
+ /// The peer connection is not intialized.
public bool CreateOffer()
{
ThrowIfConnectionNotOpen();
return NativeMethods.PeerConnectionCreateOffer(_nativePeerhandle);
}
+ ///
+ /// Create an SDP answer message to a previously-received offer, to accept a connection.
+ ///
+ /// true if the offer was created successfully.
+ /// The peer connection is not intialized.
public bool CreateAnswer()
{
ThrowIfConnectionNotOpen();
return NativeMethods.PeerConnectionCreateAnswer(_nativePeerhandle);
}
+ ///
+ /// Pass the given SDP description received from the remote peer via signaling to the
+ /// underlying WebRTC implementation, which will parse and use it.
+ ///
+ /// This must be called by the signaler when receiving a message.
+ ///
+ /// The type of SDP message ("offer", "answer", "ice")
+ /// The content of the SDP message
+ /// The peer connection is not intialized.
public void SetRemoteDescription(string type, string sdp)
{
ThrowIfConnectionNotOpen();
@@ -885,6 +921,7 @@ public void SetRemoteDescription(string type, string sdp)
/// Utility to throw an exception if a method is called before the underlying
/// native peer connection has been initialized.
///
+ /// The peer connection is not intialized.
private void ThrowIfConnectionNotOpen()
{
lock (_openCloseLock)
@@ -1044,7 +1081,7 @@ public static extern void PeerConnectionRegisterARGBRemoteVideoFrameCallback(Int
[DllImport(dllPath, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi,
EntryPoint = "mrsPeerConnectionAddDataChannel")]
- public static extern bool PeerConnectionAddDataChannel(IntPtr peerHandle, int id, string label,
+ public static extern uint PeerConnectionAddDataChannel(IntPtr peerHandle, int id, string label,
bool ordered, bool reliable, PeerConnectionDataChannelMessageCallback messageCallback,
IntPtr messageUserData, PeerConnectionDataChannelBufferingCallback bufferingCallback,
IntPtr bufferingUserData, PeerConnectionDataChannelStateCallback stateCallback,