Table of Contents

Two-Way WebRTC

The WebRtcTwoWayPage demonstrates two-way WebRTC communication between peers. It captures video and audio from local devices, streams them to remote peers via WebRTC, and simultaneously plays back media received from remote peers — enabling video conferencing.

Overview

The WebRtcTwoWayPage performs the following:

  1. Enumerates video and audio capture devices
  2. Creates capture sources with H.264 video encoding and PCM audio
  3. Connects to a signaling server and joins a conference room via WebRtcWsTwoWaySession
  4. Sends captured media to remote peers through a shared WebRtcPublisherSink
  5. Receives media from remote peers via per-peer WebRtcClientSource instances
  6. Displays local camera preview and remote peer video side by side
  7. Applies echo cancellation to prevent audio feedback

Differences from One-Way WebRTC

The One-Way WebRTC page only receives media from a server. The Two-Way page both sends and receives:

Aspect One-Way Two-Way
Direction Receive only Send and receive
Session class VastSignalingPlaybackClient (demo code) WebRtcWsTwoWaySession (library)
Signaling URL sign-in?channel={publishingPath} sign-in?room={room}
Capture None Camera and microphone
Preview Remote video only Local preview + remote video
Echo cancellation None Built-in
Multi-peer Single server peer Multiple peers

Device Enumeration

Video and audio devices are enumerated the same way as in Simple Capture, with "No Video" and "No Audio" options at index 0.

Capture Source Configuration

Video

The video capture source is configured for H.264 output with constrained baseline profile:

var mt = new VAST.Common.MediaType
{
    ContentType = VAST.Common.ContentType.Video,
    CodecId = VAST.Common.Codec.H264,
    Width = this.videoWidth,
    Height = this.videoHeight,
    Framerate = this.videoFramerate,
    Bitrate = this.videoWidth * this.videoHeight * 3,
};

var captureMode = dev.FindClosestMatch(mt);

videoCaptureSource = VAST.Media.SourceFactory.CreateVideoCapture(dev.DeviceId);
videoCaptureSource.CaptureMode = captureMode;
videoCaptureSource.Rotation = this.videoRotation;
videoCaptureSource.Renderer = this.localPreview.Renderer;

mt.Metadata.Add("KeyframeInterval", "30");
mt.Metadata.Add("Profile", (66 | 0x100).ToString()); // constrained baseline

await videoCaptureSource.SetDesiredOutputType(0, mt);

FindClosestMatch selects the capture mode closest to the requested resolution and frame rate. The local preview control is connected to the capture source's Renderer to display what the camera sees.

Audio

The audio capture source is configured for PCM output:

audioCaptureSource = VAST.Media.SourceFactory.CreateAudioCapture(deviceId);
await audioCaptureSource.SetDesiredOutputType(0, new VAST.Common.MediaType
{
    ContentType = VAST.Common.ContentType.Audio,
    CodecId = VAST.Common.Codec.PCM,
    SampleFormat = VAST.Common.SampleFormat.S16,
    SampleRate = 44100,
    Channels = 1,
});

The actual audio codec used for communication is decided and applied by the Google native WebRTC library.

Two-Way Session

The WebRtcWsTwoWaySession manages the complete two-way WebRTC lifecycle — signaling, local media pipeline, remote peer connections, and echo cancellation:

this.rtcWsTwoWaySession = new VAST.WebRTC.WebRtcWsTwoWaySession(
    this.textUri.Text, this.textRoom.Text);
this.rtcWsTwoWaySession.IceServers = "turn:test.vastreaming.net:3478";

this.rtcWsTwoWaySession.SetLocalPeer(videoCaptureSource, audioCaptureSource);
this.rtcWsTwoWaySession.AddRemotePeer(this.remotePreview);
this.rtcWsTwoWaySession.Start();

SetLocalPeer specifies the capture sources to send. AddRemotePeer provides a MediaPlayerControl for displaying received media. Start() begins the signaling connection, local media pipeline, and peer negotiation.

Note

The WebRtcWsTwoWaySession is designed to work with VASTreaming StreamingServer and implements its signaling protocol. It doesn't work with other signaling servers.

Sending Media

Internally, the session creates a MediaSession with the capture sources and a shared WebRtcPublisherSink:

this.localMediaSession = new MediaSession();
if (this.videoSource != null) this.localMediaSession.AddSource(this.videoSource);
if (this.audioSource != null) this.localMediaSession.AddSource(this.audioSource);

this.commonWebRtcSink = new WebRtcPublisherSink();
this.commonWebRtcSink.Signalling = this;
this.localMediaSession.AddSink(this.commonWebRtcSink);
this.localMediaSession.Start();

The single WebRtcPublisherSink is shared across all peer connections. When peers join or leave, the sink's peer list is updated dynamically.

Receiving Media

For each remote peer that sends an SDP offer, the session creates a WebRtcClientSource and assigns it to a player:

remotePeer.WebRtcSource = new WebRtcClientSource();
remotePeer.WebRtcSource.Signalling = this;
remotePeer.WebRtcSource.OurPeerId = this.ourPeerId;
remotePeer.WebRtcSource.RemotePeerId = remotePeer.PeerId;
remotePeer.WebRtcSource.Open();

// wait for Opened state, then process the SDP offer
remotePeer.WebRtcSource.ProcessPeerMessage(remotePeer.PeerId, unparsedMessage);
remotePeer.Player.SourceMedia = remotePeer.WebRtcSource;
remotePeer.Player.Play();

Signaling

The session connects to the signaling server via WebSocket at {uri}/sign-in?room={room} and handles the following message types:

Message Type Description
peer-list Initial peer list with assigned peer IDs
peer-list-update Dynamic peer join/leave notifications
message (SDP offer) Remote peer's SDP offer — creates a receiving connection
message (SDP answer) Remote peer's SDP answer — completes a publishing connection
message (ICE candidate) ICE candidates routed by subtype
ping Keep-alive sent every 10 seconds
action (disconnect) Server-initiated disconnection

ICE Candidate Routing

Because two-way communication involves both a sending sink and a receiving source per peer, ICE candidates must be routed to the correct WebRTC object. The session uses a subtype field in signaling messages:

Subtype Target Description
local WebRtcClientSource Candidate for the receiving (playback) side
remote WebRtcPublisherSink Candidate for the sending (publishing) side

The subtype is determined automatically in SendToPeer by inspecting the caller type.

Echo Cancellation

The session automatically creates an echo canceller to prevent audio feedback between the microphone and speaker:

this.echoCanceller = EchoCancellerFactory.Create();
this.audioSource.EchoCanceller = this.echoCanceller;
remotePeer.Player.EchoCanceller = this.echoCanceller;

The echo canceller is attached to both the audio capture source and each remote peer's player, allowing it to subtract played-back audio from the captured microphone signal.

Device Orientation

On mobile devices, the camera rotation is adjusted when the device orientation changes. The page monitors orientation via OnSizeAllocated and restarts the conference with the updated rotation to apply the new camera angle.

Send Log

The page includes a Send Log button that uploads the application log file to VASTreaming support for diagnostics:

await VAST.Common.License.SendLog("MAUI WebRTC two-way issue");

SendLog sends the current log file to the support server. A valid license key must be configured for this feature to work.

See Also