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,