SpawnDev.BlazorJS.SimplePeer brings the amazing simple-peer library to Blazor WebAssembly.
SpawnDev.BlazorJS.SimplePeer uses SpawnDev.BlazorJS for Javascript interop allowing strongly typed, full usage of the simple-peer Javascript library. Voice, video and data channels are all fully supported in Blazor WebAssembly. The SpawnDev.BlazorJS.SimplePeer API is a strongly typed version of the API found at the simple-peer repo.
Add the Nuget package SpawnDev.BlazorJS.SimplePeer
to your project using your package manager of choice.
Modify the Blazor WASM Program.cs
to initialize SpawnDev.BlazorJS for Javascript interop.
Example Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using SpawnDev.BlazorJS;
using SpawnDev.BlazorJS.SimplePeer;
using SpawnDev.BlazorJS.SimplePeer.Demo;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Add SpawnDev.BlazorJS interop
builder.Services.AddBlazorJSRuntime();
// Load the SimplePeer Javascript library. Can be called in a component instead if desired, or loaded using a <script> tag in the index.html
await SimplePeer.Init();
// Run app using BlazorJSRunAsync extension method
await builder.Build().BlazorJSRunAsync();
ManualConnectExample.razor
Blazor version of: simple-peer usage example
@page "/"
@using System.Text;
@using System.Text.Json;
@using SpawnDev.BlazorJS;
@using SpawnDev.BlazorJS.JSObjects;
@using SpawnDev.BlazorJS.JSObjects.WebRTC;
@implements IDisposable
<PageTitle>SimplePeer Test</PageTitle>
<h1>SimplePeer Test</h1>
<p>
An "offer" will be generated by the initiator. Paste this into the receiver's form and
hit submit. The receiver generates an "answer". Paste this into the initiator's form and
hit submit. <a href="https://github.com/feross/simple-peer?tab=readme-ov-file#usage">Original Example</a>
</p>
<div>
Role: @peerRole<br />
<button disabled="@(peer != null)" class="btn btn-primary" @onclick="@(()=>Init(true))">Create Initiator</button>
<button disabled="@(peer != null)" class="btn btn-primary" @onclick="@(()=>Init(false))">Create Receiver</button>
</div>
<div>
<textarea style="width: 600px; word-wrap: break-word; white-space: normal;" @bind=@incoming></textarea>
<button disabled="@(peer == null)" @onclick=@Submit>submit</button>
</div>
<pre style="width: 600px; word-wrap: break-word; white-space: normal;">@((MarkupString)outgoing)</pre>
@code {
string peerRole => peer == null ? "(select)" : (peer.Initiator ? "initiator" : "receiver");
SimplePeer? peer = null;
string outgoing = "";
string incoming = "";
void Init(bool initiator)
{
peer = new SimplePeer(new SimplePeerOptions
{
Initiator = initiator,
Trickle = false,
});
peer.OnError += SimplePeer_OnError;
peer.OnSignal += SimplePeer_OnSignal;
peer.OnConnect += SimplePeer_OnConnect;
peer.OnClose += SimplePeer_OnClose;
peer.OnData += SimplePeer_OnData;
}
void Submit()
{
peer!.Signal(JSON.Parse(incoming)!);
}
void SimplePeer_OnConnect()
{
outgoing = "Connected<br/>";
StateHasChanged();
// send string
peer!.Send("Hello " + Guid.NewGuid().ToString());
// send byte array (binary data)
peer!.Send(new byte[] { 65, 66, 67, 68 });
}
void SimplePeer_OnClose()
{
outgoing += "Closed<br/>";
StateHasChanged();
}
void SimplePeer_OnSignal(JSObject data)
{
outgoing = JSON.Stringify(data);
StateHasChanged();
}
void SimplePeer_OnError(NodeError error)
{
outgoing = error.Code! + "<br/>";
StateHasChanged();
}
void SimplePeer_OnData(NodeBuffer data)
{
outgoing += "Binary: " + Encoding.UTF8.GetString((byte[])data!) + "<br/>";
StateHasChanged();
}
public void Dispose()
{
if (peer != null)
{
peer.OnError -= SimplePeer_OnError;
peer.OnSignal -= SimplePeer_OnSignal;
peer.OnConnect -= SimplePeer_OnConnect;
peer.OnClose -= SimplePeer_OnClose;
peer.OnData -= SimplePeer_OnData;
peer.Destroy();
peer.Dispose();
peer = null;
}
}
}
An "offer" will be generated by the initiator. Paste this into the receiver's form and hit submit. The receiver generates an "answer". Paste this into the initiator's form and hit submit.
Now you have a direct P2P connection between two browsers!
Create a new WebRTC peer connection.
A "data channel" for text/binary communication is always established, because it's cheap and often useful. For video/voice communication, pass the stream
option.
If opts
is specified, then the default options (shown below) will be overridden.
{
Initiator: false,
ChannelConfig: {},
ChannelName: '<random string>',
Config: { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:global.stun.twilio.com:3478?transport=udp' }] },
OfferOptions: {},
AnswerOptions: {},
Stream: false,
Streams: [],
Trickle: true,
AllowHalfTrickle: false,
ObjectMode: false
}
The SimplePeerOptions properties do the following:
Initiator
-bool
set totrue
if this is the initiating peerChannelConfig
-RTCDataChannelOptions
custom webrtc data channel configuration (used bycreateDataChannel
)ChannelName
-string
custom webrtc data channel nameConfig
-RTCConfiguration
custom webrtc configuration (used byRTCPeerConnection
constructor)OfferOptions
-RTCOfferOptions
custom offer options (used bycreateOffer
method)AnswerOptions
-RTCAnswerOptions
custom answer options (used bycreateAnswer
method)Stream
-MediaStream
if video/voice is desired, pass stream returned fromgetUserMedia
Streams
-MediaStream[]
an array of MediaStreams returned fromgetUserMedia
Trickle
-bool
set tofalse
to disable trickle ICE and get a single 'signal' event (slower)ObjectMode
-bool
set totrue
to create the stream in Object Mode. In this mode, incoming string data is not automatically converted toNodeBuffer
objects.
Call this method whenever the remote peer emits a peer.OnSignal
event.
The data
will encapsulate a webrtc offer, answer, or ice candidate. These messages help
the peers to eventually establish a direct connection to each other. The contents of these
messages are an implementation detail that can be ignored by the user of this module;
simply pass the data from 'signal' events to the remote peer and call peer.signal(data)
to get connected.
Send text/binary data to the remote peer. data
can be any of several types: string
,
Buffer
(see buffer), TypedArray
(Uint8Array
,
etc.), ArrayBuffer
, or Blob
(in browsers that support it).
Note: If this method is called before the peer.OnConnect
event has fired, then an exception will be thrown. Use peer.Write(data)
(which is inherited from the node.js duplex stream interface) if you want this data to be buffered instead.
Add a MediaStream
to the connection.
Remove a MediaStream
from the connection.
Add a MediaStreamTrack
to the connection. Must also pass the MediaStream
you want to attach it to.
Remove a MediaStreamTrack
from the connection. Must also pass the MediaStream
that it was attached to.
Replace a MediaStreamTrack
with another track. Must also pass the MediaStream
that the old track was attached to.
Add a RTCRtpTransceiver
to the connection. Can be used to add transceivers before adding tracks. Automatically called as necessary by AddTrack
.
Destroy and cleanup this peer connection.
If the optional err
parameter is passed, then it will be emitted as an 'error'
event on the stream.
Detect native WebRTC support in the javascript environment.
if (SimplePeer.WEBRTC_SUPPORT) {
// webrtc support!
} else {
// fallback
}
Note: Registered event handlers need to be unregistered (-=
or RemoveListener
) when they are no longer needed to prevent memory leaks. Lambda event handlers are used here to keep the examples simple.
SimplePeer
inherits from EventEmitter
. Event handlers can be added using JSEventCallback
and +=/-=
operators or using EventEmitter.On
and EventEmitter.RemoveListener
methods.
Fired when the peer wants to send signaling data to the remote peer.
It is the responsibility of the application developer (that's you!) to get this data to
the other peer. This usually entails using a websocket signaling server. This data is an
Object
, so remember to call JSON.Stringify(data)
to serialize it first. Then, simply
call peer.Signal(data)
on the remote peer.
(Be sure to listen to this event immediately to avoid missing it. For Initiator = true
peers, it fires right away. For Initiator = false
peers, it fires when the remote
offer is received.)
Fired when the peer connection and data channel are ready to use.
Received a message from the remote peer (via the data channel).
For ObjectMode = false
peers (default) data
is a NodeBuffer
. For ObjectMode = true
peers, data
can be a string
or a NodeBuffer
.
Received a remote video stream, which can be displayed in a video tag:
peer.OnStream += stream => {
using var document = JS.Get<Document>("document");
using var video = document.QuerySelector<HTMLVideoElement>("video");
video.SrcObject = stream;
video.Play();
};
Received a remote audio/video track. Streams may contain multiple tracks.
Called when the peer connection has closed.
Fired when a fatal error occurs. Usually, this means bad signaling data was received from the remote peer.
err
is a NodeError
object.
Errors returned by the error
event have an err.Code
property that will indicate the origin of the failure. Constants for these errors can be found in the class SimplePeer.ErrorCodes
.
Possible error codes:
ERR_WEBRTC_SUPPORT
ERR_CREATE_OFFER
ERR_CREATE_ANSWER
ERR_SET_LOCAL_DESCRIPTION
ERR_SET_REMOTE_DESCRIPTION
ERR_ADD_ICE_CANDIDATE
ERR_ICE_CONNECTION_FAILURE
ERR_SIGNALING
ERR_DATA_CHANNEL
ERR_CONNECTION_FAILURE