A custom signaling solution
Signaling is the process of communicating with a remote endpoint with the intent of establishing a peer-to-peer connection. The WebRTC standard does not enforce any specific protocol or solution for WebRTC signaling; instead it simply states that some opaque messages must be transported between the remote peers by whatever mean the developer choses, its signaling solution.
The .NET Core Desktop tutorial introduces the NamedPipeSignaler
, a simple solution for local discovery based on named pipes. Unfortunately named pipes are not available on UWP, so this solution cannot be used. Instead, we rely in this tutorial on the existing NodeDssSignaler
already used by the TestAppUWP
sample app and the Unity integration. This requires a little bit more setup, described in details in the Unity tutorial.
Note
The NodeDssSignaler
found in the TestAppUWP
and the one found in the Unity integration use the same protocol and are compatible, but the code is different, the latter being based on Unity's MonoBehaviour
component class. Here we use the former, which is written in pure C# and is independent of Unity.
Install
As for the VideoBridge
helper class, the easiest way to consume the NodeDssSignaler
class in the App1
sample app for UWP is to copy the examples/TestAppUwp/NodeDssSignaler.cs
file alongside the App1.csproj
project of the current tutorial, and add a reference to it in the project using right-click > Add > Existing Item... (or Shift+Alt+A).
The NodeDssSignaler
class makes use of the Newtonsoft.Json
package for JSON data serialization and deserialization. This module is available as a NuGet package.
In the Solution Explorer panel, right click on the References item and select Manage NuGet Packages....
In the Browse panel, search for the
Newtonsoft.Json
package, select the latest stable version, and click the Install button.
Setup
Continue editing the MainPage.xaml.cs
file.
Import the
TestAppUwp
module. At the top of theMainPage.xaml.cs
file add:using TestAppUwp;
At the top of the
MainPage
class, create aNodeDssSignaler
field.private NodeDssSignaler _signaler;
In the
OnLoaded()
method, after subscribing to theI420LocalVideoFrameReady
event, add some more subscriptions to the signaling events._peerConnection.LocalSdpReadytoSend += Peer_LocalSdpReadytoSend; _peerConnection.IceCandidateReadytoSend += Peer_IceCandidateReadytoSend;
The
LocalSdpReadytoSend
event is triggered after a call toCreateOffer
orCreateAnswer
when WebRTC prepared the corresponding SDP message and signals the application the message is ready to be sent. TheIceCandidateReadytoSend
event similarly corresponds to ICE candidate messages generated by WebRTC, which the application needs to deliver to the remote peer.Implement the event handlers, which simply format the SDP message for the singaler.
private void Peer_LocalSdpReadytoSend(string type, string sdp) { var msg = new NodeDssSignaler.Message { MessageType = NodeDssSignaler.Message.WireMessageTypeFromString(type), Data = sdp, IceDataSeparator = "|" }; _signaler.SendMessageAsync(msg); } private void Peer_IceCandidateReadytoSend( string candidate, int sdpMlineindex, string sdpMid) { var msg = new NodeDssSignaler.Message { MessageType = NodeDssSignaler.Message.WireMessageType.Ice, Data = $"{candidate}|{sdpMlineindex}|{sdpMid}", IceDataSeparator = "|" }; _signaler.SendMessageAsync(msg); }
The
NodeDssSignaler
uses a simple JSON-based message encoding with a single string of data per message. For ICE messages, the 3 components of the message are joined together in that string with a given separator passed alongside the message, and split back into individual components on the receiving peer.Continue appending to the
OnLoaded()
method to initialize and start the signaler.// Initialize the signaler _signaler = new NodeDssSignaler() { HttpServerAddress = "http://127.0.0.1:3000/", LocalPeerId = "App1", RemotePeerId = "<input the remote peer ID here>", }; _signaler.OnMessage += (NodeDssSignaler.Message msg) => { switch (msg.MessageType) { case NodeDssSignaler.Message.WireMessageType.Offer: _peerConnection.SetRemoteDescription("offer", msg.Data); _peerConnection.CreateAnswer(); break; case NodeDssSignaler.Message.WireMessageType.Answer: _peerConnection.SetRemoteDescription("answer", msg.Data); break; case NodeDssSignaler.Message.WireMessageType.Ice: var parts = msg.Data.Split(new string[] { msg.IceDataSeparator }, StringSplitOptions.RemoveEmptyEntries); // Note the inverted arguments for historical reasons. // 'candidate' is last in AddIceCandidate(), but first in the message. string sdpMid = parts[2]; int sdpMlineindex = int.Parse(parts[1]); string candidate = parts[0]; _peerConnection.AddIceCandidate(sdpMid, sdpMlineindex, candidate); break; } }; _signaler.StartPollingAsync();
Warning
Take care to set the value of the
RemotePeerId
field of theNodeDssSignaler
to the remote peer's ID, otherwise the signaling will not work. Similarly, this tutorial arbitrarily usesApp1
as the peer ID for the application; this value needs to be set as the remote peer's ID when configuring the remote peer signaler.In the
App_Suspending()
event handler, stop the signaler and clean-up the resources.if (_signaler != null) { _signaler.StopPollingAsync(); _signaler = null; }
At this point the sample app is ready to establish a connection.