eyeball messenger sdk webrtc developer reference guide
TRANSCRIPT
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Eyeball Messenger
Interoperable with WebRTC
Developer Reference Guide
Last Modified: February 2014
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Introduction
Thanks for using Messenger! We hope you find Messenger to be both powerful and easy to use. If you have any questions or encounter any difficulties, please get in touch with us.
What is Messenger?
Messenger is a collection of libraries that enable you to create reliable UDP media streams between peers, regardless of the peer's network configuration and environment. Messenger traverses every possible firewall/NAT combination to guarantee connection establishment. It uses IETF/IANA standards to provide the broadest compatiblity with third-party components.
What platforms does Messenger support?
Messenger has libraries for JavaScript, iOS, Java, Mac, Mono, .NET, .NET Compact, Unity, Windows Phone, Windows 8, Xamarin.Android and Xamarin.iOS. If you have a particular platform need, please let us know.
Does Messenger support WebRTC?
Yes! Messenger's network tunneling strategies are identical to the ones recommended by the WebRTC specification. The WebRTC extension bundled with Messenger provides a full audio/video/data-channel stack that is fully interoperable with modern WebRTC implementations.
What about older web browsers?
Messenger comes bundled with a Java applet that includes a complete WebRTC implementation. It is integrated with the JavaScript SDK tightly so that the browser will automatically fall-back to use the applet if native WebRTC functionality is unavailable. This graceful degradation is completely seamless. You never have to touch a single line of Java code.
What are you waiting for? Start building your P2P app!
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Signaling
Before a P2P link can be established, peers must exchange some information - a description of the streams and formats that will be used, called the offer/answer, and a set of network "candidates". (The initiating peer creates what is called the offer, and the other peer creates an answer in response. Both peers create candidates.) A well-known protocol called SDP (Session Description Protocol) is used to define both the offer/answer and candidates.
Messenger will generate the offers/answers and candidates, but it needs some external messaging system to send them from one peer to another. We use WebSync in all our examples. Other options include SIP, XMPP, and Google's Channel API.
The process itself is simple. Messenger starts out with a Conference. Whenever Messenger generates an OfferAnswer or Candidate, the Conference fires an event (OnOfferAnswer or OnCandidate). Your responsibility as the developer is to ensure that:
1. Whenever an OfferAnswer or Candidate is raised, the SDP message in the event arguments is sent to the other peer.
2. Whenever an SDP message is received from a peer, either ReceiveOfferAnswer or ReceiveCandidate is called.
P2P links are often encrypted, and for this reason, we highly recommend using a secure channel to send SDP offers/answers and candidates between peers. If you are going to use WebSync, make sure you have a valid SSL certificate installed on your server and you use HTTPS to connect your clients.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Conferences
Conferences are the core of Messenger. Whether you are creating a link to one peer or several, you start by creating a Conference.
Conferences are associated with an Messenger server and a number of stream descriptions. The Messenger server is used when connecting to a new peer to facilitate network discovery (and possibly relay data for highly restrictive firewalls). The stream descriptions are used to create the offer/answer that will be used to describe the types of data/media that will be exchanged.
Conferences make sure that links between peers are created seamlessly with a consistent eventing structure. Each link that is created goes through a simple event cycle:
1. LinkInit is fired when a new link is about to start. The initiating peer triggers this event explicitly by calling Conference.Link, perhaps as the result of dialing a number or double-clicking on a contact. The other peer triggers this event implicitly by calling Conference.ReceiveOfferAnswer when an SDP offer/answer arrives on your signalling connection.
2. LinkUp is fired when a link has been successfully established. If this link fires, it does so after LinkInit has been fired. This event provides a reference to the negotiated stream formats that both peers had in common.
3. LinkDown is fired when either (a) a link could not be established or (b) a link was established, but went down. The WasUp property lets you differentiate between the two.
Each event will fire at most once for each unique link. After a link goes down, it is discarded. A reconnection will result in the creation of a new link.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
WebRTC
Where possible, Messenger providers a WebRTC extension that allows you to create WebRTC-compatible audio/video/data streams between peers, including web browsers.
What is WebRTC? In a nutshell, it's an open project backed by Google, Mozilla, Ericsson, and other industry leaders that is attempting to standardize real-time P2P audio/video/data communications and make them accessible in a web browser without the use of plug-ins.
Messenger's network traversal strategy is identical to the one recommended by WebRTC. Building on this, the WebRTC extension for Messenger provides a complete audio/video/data-channel stack that lets you send/receive P2P data between a web browser and a desktop client, all without plugins. We do all the heavy lifting:
Stream formatting.
RTP packet processing.
RTCP packet processing.
Audio encoding and decoding (G.711u, G.711a).
Video encoding and decoding (VP8, Motion-JPEG).
Audio capturing from device microphone.
Video capturing from device camera.
Audio rendering to device speakers.
Video rendering to screen container.
For audio/video feeds, first get a reference to the device's local media stream by calling UserMedia.GetMedia. If successful, create a WebRTC AudioStream, VideoStream, and/or DataChannelStream and use those stream definitions when creating your Conference. The rest is automatic! We even include a LayoutManager for each platform that helps you position your video feed controls in your interface.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Creating a Conference: iOS and Mac
The iOS/Mac SDK provides a complete peer-to-peer (P2P) streaming solution, including a
complete WebRTC stack for rapidly creating audio, video, and data-channel streams that can be
consumed natively in modern web browsers.
To get started, you'll need to add some static libraries and headers from the SDK to your project (Xcode):
libEyeball.a + Eyeball.h (supporting code) libEyeballMessenger.a + EyeballMessenger.h (Messenger core) libEyeballMessengerWebRTC.a + EyeballMessengerWebRTC.h (WebRTC stack, optional)
Also add a few Apple framework dependencies:
libz.dylib
Security.framework CFNetwork.framework (iOS only)
For WebRTC only, some additional dependencies are required:
AudioToolbox.framework
AudioUnit.framework (Mac only) AVFoundation.framework
CoreAudio.framework
CoreGraphics.framework CoreMedia.framework
CoreVideo.framework GLKit.framework (iOS only) OpenGLES.framework (iOS only)
Finally, add "-all_load" to "Other Linker Flags" under the target's build settings.
Now let's write some code! (See iOS/Mac.Client, iOS/Mac.Client.WebRTC, and
iOS/Mac.Client.DataChannels in the SDK Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
EyeballMessengerStream *customStream = [EyeballMessengerStream streamWithType:EyeballMessengerStreamTypeText format:[EyeballMessengerStreamFormat streamFormatWithEncodingName:@"utf8"]];
[customStream addOnLinkReceiveRTPWithValueBlock:^(EyeballMessengerStreamLinkReceiveRTPArgs *e)
{
NSLog(@"%@", [[EyeballEncoding utf8] getStringWithBytes:e.packet.payload]);
}];
// To send custom stream data, use EyeballMessengerConference.sendRTPWithFormat:packet:
// once you have created a conference (see Part 2):
// <code>
// [conference sendRTPWithFormat:customStream.format packet:[EyeballMessengerRTPPacket rtpPacketWithPayload:[EyeballEncoding.utf8 getBytesWithS:@"Hello, world!"]]];
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
EyeballMessengerWebRTCDataChannelInfo *dataChannelInfo = [EyeballMessengerWebRTCDataChannelInfo dataChannelInfoWithLabel:@"mydatachannel"];
[dataChannelInfo setOnReceiveBlock:^(EyeballMessengerWebRTCDataChannelReceiveArgs *e)
{
NSLog(@"%@", e.data);
}];
// Create a WebRTC data channel stream description using
// our data channel.
EyeballMessengerWebRTCDataChannelStream *dataChannelStream = [EyeballMessengerWebRTCDataChannelStream dataChannelStreamWithChannelInfo:dataChannelInfo];
// To send data-channel stream data, use EyeballMessengerConference.sendDataWithChannelInfo:data:
// once you have created a conference (see Part 2):
// <code>
// [conference sendDataWithChannelInfo:dataChannelInfo data:@"Hello, world!"];
// </code>
// TO BE CONTINUED...
Part 1c: WebRTC Audio/Video Stream Descriptions
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
WebRTC audio and video stream descriptions require a reference to the local media (microphone,
camera, or both), obtained by calling [EyeballMessengerWebRTCUserMedia
getMediaWithGetMediaArgs:].
// WebRTC has chosen VP8 as its mandatory video codec.
// Since video encoding is best done using native code,
// reference the video codec at the application-level.
// This is required when using a WebRTC video stream.
[EyeballMessengerWebRTCVideoStream registerCodecWithEncodingName:@"VP8" createCodecBlock:^()
{
return [[[Vp8Codec alloc] init] autorelease];
} preferred:YES];
// WebRTC audio and video streams require us to first get access to
// the local media (microphone, camera, or both).
EyeballMessengerWebRTCGetMediaArgs *getMediaArgs = [EyeballMessengerWebRTCGetMediaArgs getMediaArgsWithAudio:YES video:YES];
[getMediaArgs setVideoWidth:320]; // optional
[getMediaArgs setVideoHeight:240]; // optional
[getMediaArgs setVideoFrameRate:30]; // optional
[getMediaArgs setOnFailureBlock:^(EyeballMessengerWebRTCGetMediaFailureArgs *e)
{
[self alert:@"Could not get media. %@", e.exception.message];
}];
[getMediaArgs setOnSuccessBlock:^(EyeballMessengerWebRTCGetMediaSuccessArgs *e)
{
// We have successfully acquired access to the local
// audio/video device! Grab a reference to the media.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Internally, it maintains access to the local audio
// and video feeds coming from the device hardware.
EyeballMessengerWebRTCLocalMediaStream *localMedia = e.localStream;
// This is our local video control, a UIView (iOS) or
// and NSView (Mac). It is constantly updated with our
// live video feed since we requested video above. Add
// it directly to the UI or use the Messenger layout manager,
// which we do below.
NSObject *localVideoControl = e.localVideoControl;
// Create an Messenger layout manager, which makes the task
// of arranging video controls easy. Give it a reference
// to a UIView (iOS) or NSView (Mac) that can be filled
// with video feeds.
EyeballMessengerWebRTCLayoutManager *layoutManager = [EyeballMessengerWebRTCLayoutManager layoutManagerWithContainer:container];
// Position and display the local video control on-screen
// by passing it to the layout manager created above.
[layoutManager setLocalVideoControlWithLocalVideoControl:localVideoControl];
// Create a WebRTC audio stream description (requires a
// reference to the local audio feed).
EyeballMessengerWebRTCAudioStream *audioStream = [EyeballMessengerWebRTCAudioStream audioStreamWithLocalStream:localMedia];
// Create a WebRTC video stream description (requires a
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// reference to the local video feed). Whenever a P2P link
// initializes using this description, position and display
// the remote video control on-screen by passing it to the
// layout manager created above. Whenever a P2P link goes
// down, remove it.
EyeballMessengerWebRTCVideoStream *videoStream = [EyeballMessengerWebRTCVideoStream videoStreamWithLocalStream:localMedia];
[videoStream addOnLinkInitWithValueBlock:^(EyeballMessengerStreamLinkInitArgs *e)
{
NSObject *remoteVideoControl = [e.link getRemoteVideoControl];
[layoutManager addRemoteVideoControlWithPeerId:e.peerId remoteVideoControl:remoteVideoControl];
}];
[videoStream addOnLinkDownWithValueBlock:^(EyeballMessengerStreamLinkDownArgs *e)
{
[layoutManager removeRemoteVideoControlWithPeerId:e.peerId];
}];
// TO BE CONTINUED...
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
NSMutableArray *streams = [NSMutableArray arrayWithObjects:audioStream, videoStream, dataChannelStream, nil];
EyeballMessengerConference *conference = [EyeballMessengerConference conferenceWithServerAddress:@"127.0.0.1"
serverPort:3478
streams:streams];
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
[conference setRelayUsername:@"test"];
[conference setRelayPassword:@"pa55w0rd!"];
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
[conference addOnLinkInitWithValueBlock:^(EyeballMessengerLinkInitArgs *e)
{
NSLog(@"Link to peer initializing...");
}];
[conference addOnLinkUpWithValueBlock:^(EyeballMessengerLinkUpArgs *e)
{
NSLog(@"Link to peer is UP.");
}];
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
[conference addOnLinkDownWithValueBlock:^(EyeballMessengerLinkDownArgs *e)
{
NSLog([NSString stringWithFormat:@"Link to peer is DOWN. %@", e.exception.message]);
}];
// TO BE CONTINUED...
Part 3: Signalling
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
EyeballWebSyncClient *client = [EyeballWebSyncClient clientWithRequestUrl:@"http://localhost/websync.ashx"];
EyeballWebSyncConnectArgs *connectArgs = [EyeballWebSyncConnectArgs connectArgs];
[connectArgs setOnFailureBlock:^(EyeballWebSyncConnectFailureArgs *e)
{
NSLog(@"Could not connect to WebSync. %@", e.exception.message);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
}];
[client connectWithConnectArgs:connectArgs];
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
[conference addOnLinkOfferAnswerWithValueBlock:^(EyeballMessengerLinkOfferAnswerArgs *e)
{
[client notifyWithNotifyArgs:[EyeballWebSyncNotifyArgs notifyArgsWithClientId:[EyeballGuid guidWithG:e.peerId]
dataJson:[e.offerAnswer toJson]
tag:@"offeranswer"]];
}];
[conference addOnLinkCandidateWithValueBlock:^(EyeballMessengerLinkCandidateArgs *e)
{
[client notifyWithNotifyArgs:[EyeballWebSyncNotifyArgs notifyArgsWithClientId:[EyeballGuid guidWithG:e.peerId]
dataJson:[e.candidate toJson]
tag:@"candidate"]];
}];
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Call the "receiveOfferAnswer" or "receiveCandidate"
// method to pass the information to the conference.
[client addOnNotifyWithValueBlock:^(EyeballWebSyncNotifyReceiveArgs *e)
{
NSString *peerId = [e.notifyingClient.clientId toString];
NSObject *peerState = e.notifyingClient.boundRecords;
if ([e.tag isEqualToString:@"offeranswer"])
{
[conference receiveOfferAnswerWithOfferAnswer:[EyeballMessengerOfferAnswer fromJsonWithOfferAnswerJson:e.dataJson]
peerId:peerId
peerState:peerState];
}
else if ([e.tag isEqualToString:@"candidate"])
{
[conference receiveCandidateWithCandidate:[EyeballMessengerCandidate fromJsonWithCandidateJson:e.dataJson]
peerId:peerId];
}
}];
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Peer A: The conference will raise the OnLinkInit event.
Peer A: Messenger will generate an offer and raise the OnOfferAnswer event. Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer.
Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer. Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event.
Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer. Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer.
Peer A/B: Messenger will generate candidates and raise the OnCandidate event. Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer.
Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate. Peer A/B: Messenger will successfully establish a P2P link.
Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
EyeballWebSyncSubscribeArgs *subscribeArgs = [EyeballWebSyncSubscribeArgs subscribeArgsWithChannel:@"/mychannel"];
[subscribeArgs setOnFailureBlock:^(EyeballWebSyncSubscribeFailureArgs *e)
{
NSLog(@"Could not subscribe to %@. %@", e.channel, e.exception.message);
}];
[subscribeArgs setOnReceiveBlock:^(EyeballWebSyncSubscribeReceiveArgs *e) { }];
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
[subscribeArgs setOnClientSubscribeWithOnClientSubscribeBlock:^(EyeballWebSyncSubscribersClientSubscribeArgs *e)
{
NSString *peerId = [e.subscribedClient.clientId toString];
NSObject *peerState = e.subscribedClient.boundRecords;
[conference linkWithPeerId:peerId peerState:peerState];
}];
[subscribeArgs setOnClientUnsubscribeWithOnClientUnsubscribeBlock:^(EyeballWebSyncSubscribersClientUnsubscribeArgs *e)
{
NSString *peerId = [e.unsubscribedClient.clientId toString];
[conference unlinkWithPeerId:peerId];
}];
[client subscribeWithSubscribeArgs:subscribeArgs];
}];
[EyeballMessengerWebRTCUserMedia getMediaWithGetMediaArgs:getMediaArgs];
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea.
That's it! Load 'er up and watch the magic!
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Creating a Conference: Java
The Java SDK provides a complete peer-to-peer (P2P) streaming solution, including a
complete WebRTC stack for rapidly creating audio, video, and data-channel streams that can be
consumed natively in modern web browsers.
To get started, you'll need to add some JARs from the SDK to your project classpath (IntelliJ
IDEA, Eclipse, command-line):
eyeball.jar (supporting code) eyeball.messenger.jar (Messenger core) eyeball.messenger.webrtc.jar (WebRTC stack, optional)
Now let's write some code! (See Java.Client, Java.Client.WebRTC, and Java.Client.DataChannels in the
SDK Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
Stream customStream = new eyeball.messenger.Stream(StreamType.Application, new StreamFormat("utf8"));
customStream.addOnLinkReceiveRTP(new SingleAction<StreamLinkReceiveRTPArgs>()
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
public void invoke(StreamLinkReceiveRTPArgs e)
{
alert(Encoding.getUTF8().getString(e.getPacket().getPayload()));
}
});
// To send custom stream data, use Conference.sendRTP
// once you have created a conference (see Part 2):
// <code>
// conference.sendRTP(customStream.getFormat(), new RTPPacket(Encoding.getUTF8().getBytes("Hello, world!")));
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
DataChannelInfo dataChannelInfo = new eyeball.messenger.webrtc.DataChannelInfo("mydatachannel")
{{
setOnReceive(new SingleAction<DataChannelReceiveArgs>()
{
public void invoke(DataChannelReceiveArgs e)
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
alert(e.getData());
}
});
}};
// Create a WebRTC data channel stream description using
// our data channel.
DataChannelStream dataChannelStream = new eyeball.messenger.webrtc.DataChannelStream(dataChannelInfo);
// To send data-channel stream data, use ConferenceExtensions.sendData
// once you have created a conference (see Part 2):
// <code>
// ConferenceExtensions.sendData(conference, dataChannelInfo, "Hello, world!");
// </code>
// TO BE CONTINUED...
Part 1c: WebRTC Audio/Video Stream Descriptions
WebRTC audio and video stream descriptions require a reference to the local media (microphone,
camera, or both), obtained by calling UserMedia.getMedia.
// WebRTC has chosen VP8 as its mandatory video codec.
// Since video encoding is best done using native code,
// reference the video codec at the application-level.
// This is required when using a WebRTC video stream.
eyeball.messenger.webrtc.VideoStream.registerCodec("VP8", new EmptyFunction<VideoCodec>()
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
public VideoCodec invoke()
{
return new Vp8Codec();
}
}, true);
// Android requires an Activity context to create Views.
// Since the default WebRTC video providers must create
// Views, supply a reference to the current activity.
// This is required when using a WebRTC video stream on
// the Android platform.
eyeball.messenger.webrtc.DefaultProviders.setAndroidActivity(this);
// WebRTC audio and video streams require us to first get access to
// the local media (microphone, camera, or both).
eyeball.messenger.webrtc.UserMedia.getMedia(new GetMediaArgs(true, true)
{{
setVideoWidth(320); // optional
setVideoHeight(240); // optional
setVideoFrameRate(30); // optional
setOnFailure(new SingleAction<GetMediaFailureArgs>()
{
public void invoke(GetMediaFailureArgs e)
{
alert("Could not get media. %s", e.getException().getMessage());
}
});
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
setOnSuccess(new SingleAction<GetMediaSuccessArgs>()
{
public void invoke(GetMediaSuccessArgs e)
{
// We have successfully acquired access to the local
// audio/video device! Grab a reference to the media.
// Internally, it maintains access to the local audio
// and video feeds coming from the device hardware.
LocalMediaStream localMedia = e.getLocalStream();
// This is our local video control, a Java Component
// or Android View. It is constantly updated with our
// live video feed since we requested video above. Add
// it directly to the UI or use the Messenger layout manager,
// which we do below.
Object localVideoControl = e.getLocalVideoControl();
// Create an Messenger layout manager, which makes the task
// of arranging video controls easy. Give it a reference
// to a Java Container that can be filled with video feeds.
// For Android users, the WebRTC extension includes
// AndroidLayoutManager, which accepts an Android ViewGroup.
LayoutManager layoutManager = new eyeball.messenger.webrtc.LayoutManager(container);
// Position and display the local video control on-screen
// by passing it to the layout manager created above.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
layoutManager.setLocalVideoControl(localVideoControl);
// Create a WebRTC audio stream description (requires a
// reference to the local audio feed).
AudioStream audioStream = new eyeball.messenger.webrtc.AudioStream(localMedia);
// Create a WebRTC video stream description (requires a
// reference to the local video feed). Whenever a P2P link
// initializes using this description, position and display
// the remote video control on-screen by passing it to the
// layout manager created above. Whenever a P2P link goes
// down, remove it.
VideoStream videoStream = new eyeball.messenger.webrtc.VideoStream(localMedia);
videoStream.addOnLinkInit(new SingleAction<StreamLinkInitArgs>()
{
public void invoke(final StreamLinkInitArgs e)
{
Object remoteVideoControl = LinkExtensions.getRemoteVideoControl(e.getLink());
layoutManager.addRemoteVideoControl(e.getPeerId(), remoteVideoControl);
}
});
videoStream.addOnLinkDown(new SingleAction<StreamLinkDownArgs>()
{
public void invoke(final StreamLinkDownArgs e)
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
layoutManager.removeRemoteVideoControl(e.getPeerId());
}
});
// TO BE CONTINUED...
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
Conference conference = new eyeball.messenger.Conference("127.0.0.1", 3478, new Stream[] { audioStream, videoStream, dataChannelStream });
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.setRelayUsername("test");
conference.setRelayPassword("pa55w0rd!");
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.addOnLinkInit(new SingleAction<LinkInitArgs>()
{
public void invoke(LinkInitArgs e)
{
writeLine("Link to peer initializing...");
}
});
conference.addOnLinkUp(new SingleAction<LinkUpArgs>()
{
public void invoke(LinkUpArgs e)
{
writeLine("Link to peer is UP.");
}
});
conference.addOnLinkDown(new SingleAction<LinkDownArgs>()
{
public void invoke(LinkDownArgs e)
{
writeLine("Link to peer is DOWN. " + e.getException().getMessage());
}
});
// TO BE CONTINUED...
Part 3: Signalling
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
Client client = new eyeball.websync.Client("http://localhost/websync.ashx");
client.connect(new ConnectArgs()
{{
setOnFailure(new SingleAction()
{
public void invoke(ConnectFailureArgs e)
{
alert("Could not connect to WebSync. " + e.getException().getMessage());
}
});
}});
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
conference.addOnLinkOfferAnswer(new SingleAction<LinkOfferAnswerArgs>()
{
public void invoke(LinkOfferAnswerArgs e)
{
try
{
client.notify(new NotifyArgs(new Guid(e.getPeerId()), e.getOfferAnswer().toJson(), "offeranswer"));
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
conference.addOnLinkCandidate(new SingleAction<LinkCandidateArgs>()
{
public void invoke(LinkCandidateArgs e)
{
try
{
client.notify(new NotifyArgs(new Guid(e.getPeerId()), e.getCandidate().toJson(), "candidate"));
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "receiveOfferAnswer" or "receiveCandidate"
// method to pass the information to the conference.
client.addOnNotify(new SingleAction<NotifyReceiveArgs>()
{
public void invoke(NotifyReceiveArgs e)
{
try
{
String peerId = e.getNotifyingClient().getClientId().toString();
Object peerState = e.getNotifyingClient().getBoundRecords();
if (e.getTag().equals("offeranswer"))
{
conference.receiveOfferAnswer(OfferAnswer.fromJson(e.getDataJson()), peerId, peerState);
}
else if (e.getTag().equals("candidate"))
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
conference.receiveCandidate(Candidate.fromJson(e.getDataJson()), peerId);
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event.
Peer A: Messenger will generate an offer and raise the OnOfferAnswer event. Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer.
Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer. Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event.
Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer. Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer.
Peer A/B: Messenger will generate candidates and raise the OnCandidate event. Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer.
Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link. Peer A/B: The conference will raise the OnLinkUp event.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
SubscribeArgs subscribeArgs = new SubscribeArgs("/mychannel")
{{
setOnFailure(new SingleAction<SubscribeFailureArgs>()
{
public void invoke(SubscribeFailureArgs e)
{
alert("Could not subscribe to " + e.getChannel() + ". " + e.getException().getMessage());
}
});
setOnReceive(new SingleAction<SubscribeReceiveArgs>()
{
public void invoke(SubscribeReceiveArgs e) { }
});
}};
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
SubscribeArgsExtensions.setOnClientSubscribe(subscribeArgs, new SingleAction<ClientSubscribeArgs>()
{
public void invoke(ClientSubscribeArgs e)
{
try
{
String peerId = e.getSubscribedClient().getClientId().toString();
Object peerState = e.getSubscribedClient().getBoundRecords();
conference.link(peerId, peerState);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
SubscribeArgsExtensions.setOnClientUnsubscribe(subscribeArgs, new SingleAction<ClientUnsubscribeArgs>()
{
public void invoke(ClientUnsubscribeArgs e)
{
try
{
String peerId = e.getUnsubscribedClient().getClientId().toString();
conference.unlink(peerId);
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
catch (Exception ex)
{
ex.printStackTrace();
}
}
});
client.subscribe(subscribeArgs);
}
});
}});
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea.
That's it! Load 'er up and watch the magic!
Creating a Conference: JavaScript
JavaScript
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
The JavaScript SDK provides a complete WebRTC-based peer-to-peer (P2P) streaming solution, including
support for audio, video, and data channels. Native WebRTC implementations, such as those found in
Chrome and Firefox, are used if present. For all other cases, the SDK automatically and seamlessly falls
back to an embedded Java applet that provides a complete WebRTC stack. This graceful degradation is
completely transparent to you as the developer.
There are two ways to use the JavaScript client. The first is completely object-oriented, like Java or .NET.
var getMediaArgs = new eyeball.messenger.webrtc.getMediaArgs();
getMediaArgs.setAudio(true);
getMediaArgs.setVideo(true);
getMediaArgs.setOnSuccess(function(e) {
...
});
eyeball.messenger.webrtc.userMedia.getMedia(getMediaArgs);
The second uses a more JavaScript-style syntax to accomplish the same thing.
eyeball.messenger.webrtc.userMedia.getMedia({
audio: true,
video: true,
onSuccess: function(e) {
...
}
});
Properties on the getMedia config object (such as "audio", "video", etc) are mapped automatically to the
appropriate "setter" methods (such as "setAudio", "setVideo", etc). Just make sure the property names
match up to a setter method, and the rest is automatic!
To get started, you'll need to add some scripts from the SDK to your page:
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
<!-- supporting code -->
<script type="text/javascript" src="eyeball.js"></script>
<!-- Messenger core -->
<script type="text/javascript" src="eyeball.messenger.js"></script>
<!-- WebRTC stack -->
<script type="text/javascript" src="eyeball.messenger.webrtc.js"></script>
Now let's write some code! (See JavaScript.Client.WebRTC and JavaScript.Client.DataChannels in the SDK
Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. The WebRTC
extension allows us to create audio, video, and data-channel streams. For this example, we'll create
all three.
// For backwards-compability with browsers that do not yet support
// WebRTC, provide a reference to eyeball.messenger.webrtc.applet.jar, a
// Java applet that provides a full WebRTC stack through the exact
// same JavaScript API you use for modern browsers. You can set this
// for all browsers - only the ones that need it will use it.
eyeball.messenger.webrtc.setApplet({
path: './eyeball.messenger.webrtc.applet.jar',
name: 'Messenger WebRTC for JavaScript'
});
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// TO BE CONTINUED...
Part 1a: WebRTC Data-Channel Stream Description
WebRTC data-channel stream descriptions are easy. Describe one or more data channels (name +
message handler) and pass them into the stream description.
// Create a data channel description, including a handler for
// processing received messages.
var dataChannelInfo = new eyeball.messenger.webrtc.dataChannelInfo({
label: 'mydatachannel',
onReceive: function(e)
{
alert(e.getData());
}
});
// Create a data channel stream description using our data channel.
var dataChannelStream = new eyeball.messenger.webrtc.dataChannelStream([ dataChannelInfo ]);
// To send data-channel stream data, use conference.sendData
// once you have created a conference (see Part 2):
// <code>
// conference.sendData({
channelInfo: dataChannelInfo,
data: 'Hello, world!'
});
// </code>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// TO BE CONTINUED...
Part 1b: WebRTC Audio/Video Stream Descriptions
WebRTC audio and video stream descriptions require a reference to the local media (microphone,
camera, or both), obtained by calling userMedia.getMedia.
// WebRTC audio and video streams require us to first get access to
// the local media (microphone, camera, or both).
eyeball.messenger.webrtc.userMedia.getMedia({
audio: true, // required if you want to send audio
video: true, // required if you want to send video
videoWidth: 320, // optional
videoHeight: 240, // optional
videoFrameRate: 30, // optional
onFailure: function(e)
{
alert('Could not get media. ' + e.getException().message);
},
onSuccess: function(e)
{
// We have successfully acquired access to the local
// audio/video device! Grab a reference to the media.
// Internally, it maintains access to the local audio
// and video feeds coming from the device hardware.
var localMedia = e.getLocalStream();
// This is our local video control, a simple DOM element.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// It is constantly updated with our live video feed
// since we requested video above. To add it to the DOM,
// either call XXX.appendChild(localVideoControl) or use
// the Messenger layout manager, which we do below.
var localVideoControl = e.getLocalVideoControl();
// Create an Messenger layout manager, which makes the task
// of arranging video controls easy. Give it a reference
// to a DOM element that can be filled with video feeds.
var layoutManager = new eyeball.messenger.webrtc.layoutManager(document.getElementById('container'));
// Position and display the local video control on-screen
// by passing it to the layout manager created above.
layoutManager.setLocalVideoControl(localVideoControl);
// Create an audio stream description (requires a reference
// to the local audio feed).
var audioStream = new eyeball.messenger.webrtc.audioStream(localMedia);
// Create a video stream description (requires a reference
// to the local video feed). Whenever a P2P link initializes
// using this description, position and display the remote
// video control on-screen by passing it to the layout manager
// created above. Whenever a P2P link goes down, remove it.
var videoStream = new eyeball.messenger.webrtc.videoStream(localMedia);
videoStream.addOnLinkInit(function(e)
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
var remoteVideoControl = e.getLink().getRemoteVideoControl();
layoutManager.addRemoteVideoControl(e.getPeerId(), remoteVideoControl);
});
videoStream.addOnLinkDown(function(e)
{
layoutManager.removeRemoteVideoControl(e.getPeerId());
});
// TO BE CONTINUED...
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
var conference = new eyeball.messenger.conference('127.0.0.1', 3478, [ audioStream, videoStream, dataChannelStream ]);
// Supply TURN relay credentials in case we are behind a
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.setRelayUsername('test');
conference.setRelayPassword('pa55w0rd!');
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.addOnLinkInit(function(e)
{
if (console && console.log) console.log('Link to peer initializing...');
});
conference.addOnLinkUp(function(e)
{
if (console && console.log) console.log('Link to peer is UP.');
});
conference.addOnLinkDown(function(e)
{
if (console && console.log) console.log('Link to peer is DOWN. ' + e.getException().message);
});
// TO BE CONTINUED...
Part 3: Signalling
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
var client = new eyeball.websync.client('http://localhost/websync.ashx');
client.connect({
onFailure: function(e)
{
alert('Could not connect to WebSync. ' + e.getException().message);
}
});
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
conference.addOnLinkOfferAnswer(function(e)
{
client.notify({
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
clientId: e.getPeerId(),
dataJson: e.getOfferAnswer().toJson(),
tag: 'offeranswer'
});
});
conference.addOnLinkCandidate(function(e)
{
client.notify({
clientId: e.getPeerId(),
dataJson: e.getCandidate().toJson(),
tag: 'candidate'
});
});
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "receiveOfferAnswer" or "receiveCandidate"
// method to pass the information to the conference.
client.addOnNotify(function(e)
{
var peerId = e.getNotifyingClient().getClientId().toString();
var peerState = e.getNotifyingClient().getBoundRecords(); // optional
if (e.getTag() == 'offeranswer')
{
conference.receiveOfferAnswer(eyeball.messenger.offerAnswer.fromJson(e.getDataJson()), peerId, peerState);
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
else if (e.getTag() == 'candidate')
{
conference.receiveCandidate(eyeball.messenger.candidate.fromJson(e.getDataJson()), peerId);
}
});
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event.
Peer A: Messenger will generate an offer and raise the OnOfferAnswer event. Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer.
Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer. Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event.
Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer. Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer.
Peer A/B: Messenger will generate candidates and raise the OnCandidate event. Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer.
Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link. Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
client.subscribe({
channel: '/mychannel',
onFailure: function(e)
{
alert('Could not subscribe to ' + e.getChannel() + '. ' + e.getException().message);
},
onReceive: function(e) { },
onClientSubscribe: function(e)
{
var peerId = e.getSubscribedClient().getClientId().toString();
var peerState = e.getSubscribedClient().getBoundRecords();
conference.link(peerId, peerState);
},
onClientUnsubscribe: function(e)
{
var peerId = e.getUnsubscribedClient().getClientId().toString();
conference.unlink(peerId);
}
});
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
});
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea.
That's it! Load 'er up and watch the magic!
Creating a Conference: Xamarin.Android
Please read peer-to-peer (P2P) streaming solution, including a partial WebRTC stack for rapidly creating
data-channel streams that can be consumed natively in modern web browsers.
To get started, you'll need to add some assemblies from the SDK to your project references (Xamarin
Studio, Visual Studio, MonoDevelop):
Eyeball.dll (supporting code) Eyeball.Messenger.dll (Messenger core) Eyeball.Messenger.WebRTC.dll (WebRTC stack, optional)
Now let's write some code! (See Xamarin.Android.Client and Xamarin.Android.Client.DataChannels in the
SDK Examples folder.)
Part 1: Stream Descriptions
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
var customStream = new Eyeball.Messenger.Stream(StreamType.Application, new StreamFormat("utf8"));
customStream.OnLinkReceiveRTP += (e)
{
Alert(Encoding.UTF8.GetString(e.Packet.Payload));
};
// To send custom stream data, use Conference.SendRTP
// once you have created a conference (see Part 2):
// <code>
// conference.SendRTP(customStream.Format, new RTPPacket(Encoding.UTF8.GetBytes("Hello, world!")));
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
var dataChannelInfo = new Eyeball.Messenger.WebRTC.DataChannelInfo("mydatachannel")
{
OnReceive = (e) =>
{
Alert(e.Data);
}
};
// Create a WebRTC data channel stream description using
// our data channel.
var dataChannelStream = new Eyeball.Messenger.WebRTC.DataChannelStream(dataChannelInfo);
// To send data-channel stream data, use Conference.SendData
// once you have created a conference (see Part 2):
// <code>
// conference.SendData(dataChannelInfo, "Hello, world!");
// </code>
// TO BE CONTINUED...
Part 2: The Conference
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
var conference = new Eyeball.Messenger.Conference("127.0.0.1", 3478, dataChannelStream);
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.RelayUsername = "test";
conference.RelayPassword = "pa55w0rd!";
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.OnLinkInit += (e) =>
{
Console.WriteLine("Link to peer initializing...");
};
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
conference.OnLinkUp += (e) =>
{
Console.WriteLine("Link to peer is UP.");
};
conference.OnLinkDown += (e) =>
{
Console.WriteLine("Link to peer is DOWN. " + e.Exception.Message);
};
// TO BE CONTINUED...
Part 3: Signalling
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
var client = new Eyeball.WebSync.Client("http://localhost/websync.ashx");
client.Connect(new ConnectArgs()
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
OnFailure = (e) =>
{
Alert("Could not connect to WebSync. " + e.Exception.Message);
});
});
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
conference.OnLinkOfferAnswer += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.OfferAnswer.ToJson(), "offeranswer"));
};
conference.OnLinkCandidate += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.Candidate.ToJson(), "candidate"));
};
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "ReceiveOfferAnswer" or "ReceiveCandidate"
// method to pass the information to the conference.
client.OnNotify += (e) =>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
var peerId = e.NotifyingClient.ClientId.ToString();
var peerState = e.NotifyingClient.BoundRecords;
if (e.Tag == "offeranswer")
{
conference.ReceiveOfferAnswer(OfferAnswer.FromJson(e.DataJson), peerId, peerState);
}
else if (e.Tag == "candidate")
{
conference.ReceiveCandidate(Candidate.FromJson(e.DataJson), peerId);
}
};
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event.
Peer A: Messenger will generate an offer and raise the OnOfferAnswer event. Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer.
Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer. Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event.
Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer. Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer.
Peer A/B: Messenger will generate candidates and raise the OnCandidate event. Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer.
Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
client.Subscribe(new SubscribeArgs("/mychannel")
{
OnFailure = (e) =>
{
Alert("Could not subscribe to " + e.Channel + ". " + e.Exception.Message);
},
OnReceive = (e) => { }
}
.SetOnClientSubscribe((e) =>
{
var peerId = e.SubscribedClient.ClientId.ToString();
var peerState = e.SubscribedClient.BoundRecords;
conference.Link(peerId, peerState);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
})
.SetOnClientUnsubscribe((e) =>
{
var peerId = e.UnsubscribedClient.ClientId.ToString();
conference.Unlink(peerId);
}));
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea. That's it! Load 'er up and watch the magic!
Creating a Conference: Xamarin.iOS
The Xamarin.iOS SDK provides a complete peer-to-peer (P2P) streaming solution, including a
partial WebRTC stack for rapidly creating data-channel streams that can be consumed natively in modern
web browsers.
To get started, you'll need to add some assemblies from the SDK to your project references (Xamarin
Studio, Visual Studio, MonoDevelop):
Eyeball.dll (supporting code) Eyeball.Messenger.dll (Messenger core) Eyeball.Messenger.WebRTC.dll (WebRTC stack, optional)
Now let's write some code! (See Xamarin.iOS.Client and Xamarin.iOS.Client.DataChannels in the SDK
Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
var customStream = new Eyeball.Messenger.Stream(StreamType.Application, new StreamFormat("utf8"));
customStream.OnLinkReceiveRTP += (e)
{
Alert(Encoding.UTF8.GetString(e.Packet.Payload));
};
// To send custom stream data, use Conference.SendRTP
// once you have created a conference (see Part 2):
// <code>
// conference.SendRTP(customStream.Format, new RTPPacket(Encoding.UTF8.GetBytes("Hello, world!")));
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
var dataChannelInfo = new Eyeball.Messenger.WebRTC.DataChannelInfo("mydatachannel")
{
OnReceive = (e) =>
{
Alert(e.Data);
}
};
// Create a WebRTC data channel stream description using
// our data channel.
var dataChannelStream = new Eyeball.Messenger.WebRTC.DataChannelStream(dataChannelInfo);
// To send data-channel stream data, use Conference.SendData
// once you have created a conference (see Part 2):
// <code>
// conference.SendData(dataChannelInfo, "Hello, world!");
// </code>
// TO BE CONTINUED...
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
var conference = new Eyeball.Messenger.Conference("127.0.0.1", 3478, dataChannelStream);
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.RelayUsername = "test";
conference.RelayPassword = "pa55w0rd!";
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.OnLinkInit += (e) =>
{
Console.WriteLine("Link to peer initializing...");
};
conference.OnLinkUp += (e) =>
{
Console.WriteLine("Link to peer is UP.");
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
};
conference.OnLinkDown += (e) =>
{
Console.WriteLine("Link to peer is DOWN. " + e.Exception.Message);
};
// TO BE CONTINUED...
Part 3: Signalling
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
var client = new Eyeball.WebSync.Client("http://localhost/websync.ashx");
client.Connect(new ConnectArgs()
{
OnFailure = (e) =>
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Alert("Could not connect to WebSync. " + e.Exception.Message);
});
});
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
conference.OnLinkOfferAnswer += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.OfferAnswer.ToJson(), "offeranswer"));
};
conference.OnLinkCandidate += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.Candidate.ToJson(), "candidate"));
};
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "ReceiveOfferAnswer" or "ReceiveCandidate"
// method to pass the information to the conference.
client.OnNotify += (e) =>
{
var peerId = e.NotifyingClient.ClientId.ToString();
var peerState = e.NotifyingClient.BoundRecords;
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
if (e.Tag == "offeranswer")
{
conference.ReceiveOfferAnswer(OfferAnswer.FromJson(e.DataJson), peerId, peerState);
}
else if (e.Tag == "candidate")
{
conference.ReceiveCandidate(Candidate.FromJson(e.DataJson), peerId);
}
};
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event. Peer A: Messenger will generate an offer and raise the OnOfferAnswer event.
Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer. Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer.
Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event. Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer.
Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer. Peer A/B: Messenger will generate candidates and raise the OnCandidate event.
Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer. Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link.
Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
client.Subscribe(new SubscribeArgs("/mychannel")
{
OnFailure = (e) =>
{
Alert("Could not subscribe to " + e.Channel + ". " + e.Exception.Message);
},
OnReceive = (e) => { }
}
.SetOnClientSubscribe((e) =>
{
var peerId = e.SubscribedClient.ClientId.ToString();
var peerState = e.SubscribedClient.BoundRecords;
conference.Link(peerId, peerState);
})
.SetOnClientUnsubscribe((e) =>
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
var peerId = e.UnsubscribedClient.ClientId.ToString();
conference.Unlink(peerId);
}));
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea. That's it! Load 'er up and watch the magic!
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Creating a Conference: .NET, .NET Compact, and Mono
The .NET SDK provides a complete peer-to-peer (P2P) streaming solution, including a
complete WebRTC stack for rapidly creating audio, video, and data-channel streams that can be
consumed natively in modern web browsers.
To get started, you'll need to add some assemblies from the SDK to your project references (Visual
Studio, MonoDevelop):
Eyeball.dll (supporting code) Eyeball.Messenger.dll (Messenger core) Eyeball.Messenger.WebRTC.dll (WebRTC stack, optional)
Now let's write some code! (See NET.Client, NET.Client.WebRTC, and NET.Client.DataChannels in the
SDK Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
var customStream = new Eyeball.Messenger.Stream(StreamType.Application, new StreamFormat("utf8"));
customStream.OnLinkReceiveRTP += (e)
{
Alert(Encoding.UTF8.GetString(e.Packet.Payload));
};
// To send custom stream data, use Conference.SendRTP
// once you have created a conference (see Part 2):
// <code>
// conference.SendRTP(customStream.Format, new RTPPacket(Encoding.UTF8.GetBytes("Hello, world!")));
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
var dataChannelInfo = new Eyeball.Messenger.WebRTC.DataChannelInfo("mydatachannel")
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
OnReceive = (e) =>
{
Alert(e.Data);
}
};
// Create a WebRTC data channel stream description using
// our data channel.
var dataChannelStream = new Eyeball.Messenger.WebRTC.DataChannelStream(dataChannelInfo);
// To send data-channel stream data, use Conference.SendData
// once you have created a conference (see Part 2):
// <code>
// conference.SendData(dataChannelInfo, "Hello, world!");
// </code>
// TO BE CONTINUED...
Part 1c: WebRTC Audio/Video Stream Descriptions
WebRTC audio and video stream descriptions require a reference to the local media (microphone,
camera, or both), obtained by calling UserMedia.GetMedia.
// WebRTC has chosen VP8 as its mandatory video codec.
// Since video encoding is best done using native code,
// reference the video codec at the application-level.
// This is required when using a WebRTC video stream.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Eyeball.Messenger.WebRTC.VideoStream.RegisterCodec("VP8", () =>
{
return new Win.VP8.Codec();
}, true);
// WebRTC audio and video streams require us to first get access to
// the local media (microphone, camera, or both).
Eyeball.Messenger.WebRTC.UserMedia.GetMedia(new GetMediaArgs(true, true)
{
// Specify an audio capture provider, a video
// capture provider, an audio render provider,
// and a video render provider. The Messenger
// SDK comes bundled with video support for
// WinForms and WPF, and uses NAudio for audio
// support. For lower latency audio, use ASIO.
AudioCaptureProvider = new NAudioCaptureProvider(),
VideoCaptureProvider = new AForgeVideoCaptureProvider(),
CreateAudioRenderProvider = () =>
{
return new NAudioRenderProvider();
},
CreateVideoRenderProvider = () =>
{
return new PictureBoxVideoRenderProvider();
},
VideoWidth = 320, // optional
VideoHeight = 240, // optional
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
VideoFrameRate = 30, // optional
OnFailure = (e) =>
{
Alert("Could not get media. {0}", e.Exception.Message);
},
OnSuccess = (ea) =>
{
// We have successfully acquired access to the local
// audio/video device! Grab a reference to the media.
// Internally, it maintains access to the local audio
// and video feeds coming from the device hardware.
var localMedia = ea.LocalStream;
// This is our local video control, a WinForms Control or
// WPF FrameworkElement. It is constantly updated with
// our live video feed since we requested video above.
// Add it directly to the UI or use the Messenger layout
// manager, which we do below.
var localVideoControl = ea.LocalVideoControl;
// Create an Messenger layout manager, which makes the task
// of arranging video controls easy. Give it a reference
// to a WinForms control that can be filled with video feeds.
// For WPF users, the WebRTC extension includes
// WpfLayoutManager, which accepts a Canvas.
var layoutManager = new Eyeball.Messenger.WebRTC.LayoutManager(container);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Position and display the local video control on-screen
// by passing it to the layout manager created above.
layoutManager.SetLocalVideoControl(localVideoControl);
// Create a WebRTC audio stream description (requires a
// reference to the local audio feed).
var audioStream = new Eyeball.Messenger.WebRTC.AudioStream(localMedia);
// Create a WebRTC video stream description (requires a
// reference to the local video feed). Whenever a P2P link
// initializes using this description, position and display
// the remote video control on-screen by passing it to the
// layout manager created above. Whenever a P2P link goes
// down, remove it.
var videoStream = new Eyeball.Messenger.WebRTC.VideoStream(localMedia);
videoStream.OnLinkInit += (e) =>
{
var remoteVideoControl = e.Link.GetRemoteVideoControl();
layoutManager.AddRemoteVideoControl(e.PeerId, remoteVideoControl);
};
videoStream.OnLinkDown += (e) =>
{
layoutManager.RemoveRemoteVideoControl(e.PeerId);
};
// TO BE CONTINUED...
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
var conference = new Eyeball.Messenger.Conference("127.0.0.1", 3478, new[] { audioStream, videoStream, dataChannelStream });
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.RelayUsername = "test";
conference.RelayPassword = "pa55w0rd!";
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.OnLinkInit += (e) =>
{
Console.WriteLine("Link to peer initializing...");
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
};
conference.OnLinkUp += (e) =>
{
Console.WriteLine("Link to peer is UP.");
};
conference.OnLinkDown += (e) =>
{
Console.WriteLine("Link to peer is DOWN. " + e.Exception.Message);
};
// TO BE CONTINUED...
Part 3: Signalling
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
var client = new Eyeball.WebSync.Client("http://localhost/websync.ashx");
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
client.Connect(new ConnectArgs()
{
OnFailure = (e) =>
{
Alert("Could not connect to WebSync. " + e.Exception.Message);
});
});
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
conference.OnLinkOfferAnswer += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.OfferAnswer.ToJson(), "offeranswer"));
};
conference.OnLinkCandidate += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.Candidate.ToJson(), "candidate"));
};
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "ReceiveOfferAnswer" or "ReceiveCandidate"
// method to pass the information to the conference.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
client.OnNotify += (e) =>
{
var peerId = e.NotifyingClient.ClientId.ToString();
var peerState = e.NotifyingClient.BoundRecords;
if (e.Tag == "offeranswer")
{
conference.ReceiveOfferAnswer(OfferAnswer.FromJson(e.DataJson), peerId, peerState);
}
else if (e.Tag == "candidate")
{
conference.ReceiveCandidate(Candidate.FromJson(e.DataJson), peerId);
}
};
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event. Peer A: Messenger will generate an offer and raise the OnOfferAnswer event.
Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer. Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer.
Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event. Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer.
Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer. Peer A/B: Messenger will generate candidates and raise the OnCandidate event.
Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link. Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
client.Subscribe(new SubscribeArgs("/mychannel")
{
OnFailure = (e) =>
{
Alert("Could not subscribe to " + e.Channel + ". " + e.Exception.Message);
},
OnReceive = (e) => { }
}
.SetOnClientSubscribe((e) =>
{
var peerId = e.SubscribedClient.ClientId.ToString();
var peerState = e.SubscribedClient.BoundRecords;
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
conference.Link(peerId, peerState);
})
.SetOnClientUnsubscribe((e) =>
{
var peerId = e.UnsubscribedClient.ClientId.ToString();
conference.Unlink(peerId);
}));
}
});
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea.
That's it! Load 'er up and watch the magic!
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Creating a Conference: Windows Phone
The Windows Phone SDK provides a complete peer-to-peer (P2P) streaming solution, including a
partial WebRTC stack for rapidly creating data-channel streams that can be consumed natively in modern
web browsers.
To get started, you'll need to add some assemblies from the SDK to your project references (Visual
Studio):
Eyeball.dll (supporting code) Eyeball.Messenger.dll (Messenger core) Eyeball.Messenger.WebRTC.dll (WebRTC stack, optional)
Now let's write some code! (See WindowsPhone.Client and WindowsPhone.Client.DataChannels in the
SDK Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
var customStream = new Eyeball.Messenger.Stream(StreamType.Application, new StreamFormat("utf8"));
customStream.OnLinkReceiveRTP += (e)
{
Alert(Encoding.UTF8.GetString(e.Packet.Payload));
};
// To send custom stream data, use Conference.SendRTP
// once you have created a conference (see Part 2):
// <code>
// conference.SendRTP(customStream.Format, new RTPPacket(Encoding.UTF8.GetBytes("Hello, world!")));
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
var dataChannelInfo = new Eyeball.Messenger.WebRTC.DataChannelInfo("mydatachannel")
{
OnReceive = (e) =>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
Alert(e.Data);
}
};
// Create a WebRTC data channel stream description using
// our data channel.
var dataChannelStream = new Eyeball.Messenger.WebRTC.DataChannelStream(dataChannelInfo);
// To send data-channel stream data, use Conference.SendData
// once you have created a conference (see Part 2):
// <code>
// conference.SendData(dataChannelInfo, "Hello, world!");
// </code>
// TO BE CONTINUED...
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Create a conference using our stream descriptions. Replace
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
var conference = new Eyeball.Messenger.Conference("127.0.0.1", 3478, dataChannelStream);
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.RelayUsername = "test";
conference.RelayPassword = "pa55w0rd!";
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.OnLinkInit += (e) =>
{
Console.WriteLine("Link to peer initializing...");
};
conference.OnLinkUp += (e) =>
{
Console.WriteLine("Link to peer is UP.");
};
conference.OnLinkDown += (e) =>
{
Console.WriteLine("Link to peer is DOWN. " + e.Exception.Message);
};
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// TO BE CONTINUED...
Part 3: Signalling
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
var client = new Eyeball.WebSync.Client("http://localhost/websync.ashx");
client.Connect(new ConnectArgs()
{
OnFailure = (e) =>
{
Alert("Could not connect to WebSync. " + e.Exception.Message);
});
});
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
// "notify" method is used to send data to a specific client.
conference.OnLinkOfferAnswer += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.OfferAnswer.ToJson(), "offeranswer"));
};
conference.OnLinkCandidate += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.Candidate.ToJson(), "candidate"));
};
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "ReceiveOfferAnswer" or "ReceiveCandidate"
// method to pass the information to the conference.
client.OnNotify += (e) =>
{
var peerId = e.NotifyingClient.ClientId.ToString();
var peerState = e.NotifyingClient.BoundRecords;
if (e.Tag == "offeranswer")
{
conference.ReceiveOfferAnswer(OfferAnswer.FromJson(e.DataJson), peerId, peerState);
}
else if (e.Tag == "candidate")
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
conference.ReceiveCandidate(Candidate.FromJson(e.DataJson), peerId);
}
};
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event. Peer A: Messenger will generate an offer and raise the OnOfferAnswer event.
Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer. Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer.
Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event. Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer.
Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer. Peer A/B: Messenger will generate candidates and raise the OnCandidate event.
Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer. Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link.
Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
client.Subscribe(new SubscribeArgs("/mychannel")
{
OnFailure = (e) =>
{
Alert("Could not subscribe to " + e.Channel + ". " + e.Exception.Message);
},
OnReceive = (e) => { }
}
.SetOnClientSubscribe((e) =>
{
var peerId = e.SubscribedClient.ClientId.ToString();
var peerState = e.SubscribedClient.BoundRecords;
conference.Link(peerId, peerState);
})
.SetOnClientUnsubscribe((e) =>
{
var peerId = e.UnsubscribedClient.ClientId.ToString();
conference.Unlink(peerId);
}));
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea. That's it! Load 'er up and watch the magic!
Creating a Conference: Windows 8
The Windows 8 SDK provides a complete peer-to-peer (P2P) streaming solution, including a
partial WebRTC stack for rapidly creating data-channel streams that can be consumed natively in modern
web browsers.
To get started, you'll need to add some assemblies from the SDK to your project references (Visual
Studio):
Eyeball.dll (supporting code) Eyeball.Messenger.dll (Messenger core) Eyeball.Messenger.WebRTC.dll (WebRTC stack, optional)
Now let's write some code! (See Windows8.Client and Windows8.Client.DataChannels in the SDK
Examples folder.)
Part 1: Stream Descriptions
The first step is to describe the streams we want to use. Stream descriptions are used to define the types
of media we are going to be sending back and forth between peers in a conference. Messenger allows us
to createcustom streams with any number of possible data formats. The WebRTC extension allows us to
create audio, video, and data-channel streams. You can mix and match custom and WebRTC streams,
but only WebRTC streams can be consumed natively by web browsers.
Part 1a: Custom Stream Descriptions
Custom stream descriptions require a "stream type" and one or more "stream formats". The stream type
is used in the SDP offer/answer to help differentiate between multiple streams (audio, video,
application/binary, etc.). The stream format is used in the SDP offer/answer to help differentiate between
multiple data formats supported for a given stream (i.e. G.711, G.722, Opus, etc. for audio).
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Create a custom stream description that indicates text
// data will be sent using a format labelled "utf8".
var customStream = new Eyeball.Messenger.Stream(StreamType.Application, new StreamFormat("utf8"));
customStream.OnLinkReceiveRTP += (e)
{
Alert(Encoding.UTF8.GetString(e.Packet.Payload));
};
// To send custom stream data, use Conference.SendRTP
// once you have created a conference (see Part 2):
// <code>
// conference.SendRTP(customStream.Format, new RTPPacket(Encoding.UTF8.GetBytes("Hello, world!")));
// </code>
// TO BE CONTINUED...
Part 1b: WebRTC Data-Channel Stream Description
WebRTC data-channel streams are easy. Describe one or more data channels (name + message
handler) and pass them into the stream description.
// Create a WebRTC data channel description, including a
// handler for processing received messages.
var dataChannelInfo = new Eyeball.Messenger.WebRTC.DataChannelInfo("mydatachannel")
{
OnReceive = (e) =>
{
Alert(e.Data);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
}
};
// Create a WebRTC data channel stream description using
// our data channel.
var dataChannelStream = new Eyeball.Messenger.WebRTC.DataChannelStream(dataChannelInfo);
// To send data-channel stream data, use Conference.SendData
// once you have created a conference (see Part 2):
// <code>
// conference.SendData(dataChannelInfo, "Hello, world!");
// </code>
// TO BE CONTINUED...
Part 2: The Conference
Now that we have some stream descriptions, we are ready to create a conference - the hub that
manages your P2P links.
Conferences require the address/port of a STUN/TURN server. STUN provides a way for peers to discover
their public IP addresses, and TURN provides a way to relay data through highly restrictive firewalls.
Messenger comes bundled with a STUN/TURN server implementation, so you can run your own if you
have access to a computer that is directly exposed to the public Internet. Alternatively, there
are several public STUN servers that are free-to-use. Free TURN servers are more difficult to find, since
relaying with TURN can consume significant server bandwidth.
// CONTINUED FROM ABOVE...
// Create a conference using our stream descriptions. Replace
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// 127.0.0.1 : 3478 with your STUN/TURN (Messenger) server address.
var conference = new Eyeball.Messenger.Conference("127.0.0.1", 3478, dataChannelStream);
// Supply TURN relay credentials in case we are behind a
// highly restrictive firewall. These credentials will be
// verified by the TURN server.
conference.RelayUsername = "test";
conference.RelayPassword = "pa55w0rd!";
// Add a few event handlers to the conference so we can see
// when a new P2P link is created or changes state.
conference.OnLinkInit += (e) =>
{
Console.WriteLine("Link to peer initializing...");
};
conference.OnLinkUp += (e) =>
{
Console.WriteLine("Link to peer is UP.");
};
conference.OnLinkDown += (e) =>
{
Console.WriteLine("Link to peer is DOWN. " + e.Exception.Message);
};
// TO BE CONTINUED...
Part 3: Signalling
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
With our conference in hand, we are almost done! Just one more thing to set up.
Before we can create a P2P link, the peers have to exchange some information - specifically descriptions
of the streams (called the offer/answer) and some local network addresses (called candidates).
Messengergenerates this information automatically, but you are responsible for distributing it to the
peer as quickly as possible. This is called "signalling".
We're going to use WebSync here, but other popular options include SIP and XMPP - any real-time
messaging system will do. We use WebSync since it uses HTTP (WebSockets/long-polling) and therefore
has no issues with firewalls or connecting from JavaScript-based web applications.
// CONTINUED FROM ABOVE...
// Create a WebSync client and establish a persistent
// connection to the server. Replace localhost with your
// WebSync server address.
var client = new Eyeball.WebSync.Client("http://localhost/websync.ashx");
client.Connect(new ConnectArgs()
{
OnFailure = (e) =>
{
Alert("Could not connect to WebSync. " + e.Exception.Message);
});
});
// Add a couple event handlers to the conference to send
// generated offers/answers and candidates to a peer.
// The peer ID is something we define later. In this case,
// it represents the remote WebSync client ID. WebSync's
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// "notify" method is used to send data to a specific client.
conference.OnLinkOfferAnswer += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.OfferAnswer.ToJson(), "offeranswer"));
};
conference.OnLinkCandidate += (e) =>
{
client.Notify(new NotifyArgs(new Guid(e.PeerId), e.Candidate.ToJson(), "candidate"));
};
// Add an event handler to the WebSync client to receive
// incoming offers/answers and candidates from a peer.
// Call the "ReceiveOfferAnswer" or "ReceiveCandidate"
// method to pass the information to the conference.
client.OnNotify += (e) =>
{
var peerId = e.NotifyingClient.ClientId.ToString();
var peerState = e.NotifyingClient.BoundRecords;
if (e.Tag == "offeranswer")
{
conference.ReceiveOfferAnswer(OfferAnswer.FromJson(e.DataJson), peerId, peerState);
}
else if (e.Tag == "candidate")
{
conference.ReceiveCandidate(Candidate.FromJson(e.DataJson), peerId);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
}
};
// TO BE CONTINUED...
Part 4: Kicking It Off
Everything is set now. We just need to start the process by having one of the peers
call Conference.Link. That peer becomes the "controlling agent". The other peer becomes the
"controlled agent" and creates a P2P link automatically when it receives the controlling agent's offer.
Here's a detailed breakdown of what happens:
Peer A: The conference will raise the OnLinkInit event. Peer A: Messenger will generate an offer and raise the OnOfferAnswer event.
Peer A: Our event handler will use WebSync's Notify method to send that offer to the peer. Peer B: Our event handler will receive that offer and call Conference.ReceiveOfferAnswer.
Peer B: The conference will raise the OnLinkInit event.
Peer B: Messenger will generate an answer and raise the OnOfferAnswer event. Peer B: Our event handler will use WebSync's Notify method to send that answer to the peer.
Peer A: Our event handler will receive that answer and call Conference.ReceiveOfferAnswer. Peer A/B: Messenger will generate candidates and raise the OnCandidate event.
Peer A/B: Our event handler will use WebSync's Notify method to send those candidates to the peer. Peer A/B: Our event handler will receive those candidates and call Conference.ReceiveCandidate.
Peer A/B: Messenger will successfully establish a P2P link.
Peer A/B: The conference will raise the OnLinkUp event.
If the P2P link could not be established, the conference will raise the OnLinkDown event. The event
arguments will include an Exception property with details. The most common reason for a link failure is
a misconfiguration on the server. (Server address or port is wrong, server firewall is blocking UDP traffic,
server is on the same network as one of the clients, server is not publicly accessible, etc.)
If you are having trouble setting up your Messenger server, try using our public server, located
at messenger.eyeball.com : 3478.
A common scenario when using a pub-sub framework like WebSync is to create a P2P link whenever two
peers join the same channel (and destroy that link when one peer leaves).
// CONTINUED FROM ABOVE...
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Subscribe to a WebSync channel. When another client joins the same
// channel, create a P2P link. When a client leaves, destroy it.
client.Subscribe(new SubscribeArgs("/mychannel")
{
OnFailure = (e) =>
{
Alert("Could not subscribe to " + e.Channel + ". " + e.Exception.Message);
},
OnReceive = (e) => { }
}
.SetOnClientSubscribe((e) =>
{
var peerId = e.SubscribedClient.ClientId.ToString();
var peerState = e.SubscribedClient.BoundRecords;
conference.Link(peerId, peerState);
})
.SetOnClientUnsubscribe((e) =>
{
var peerId = e.UnsubscribedClient.ClientId.ToString();
conference.Unlink(peerId);
}));
Note that when calling Conference.Link, you must supply a unique peer ID. This string can be anything
that uniquely identifies the remote peer. This should be the remote WebSync client ID when using
WebSync, the remote SIP client ID when using SIP, the remote XMPP client ID when using XMPP... You
get the idea.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
That's it! Load 'er up and watch the magic!
Creating a Server: iOS and MAC
Running an Messenger server on iOS or Mac is simple. Remember that the server runs on port 3478 by
default, and this port must be publicly accessible for incoming UDP packets.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
1. Add libraries to your project:
1. libEyeball.a 2. libEyeballMessenger.a
2. Add headers to your project: 0. Eyeball.h
1. EyeballMessenger.h
3. Add dependencies to your project: 0. libz.dylib
1. iOS only: 1. CFNetwork.framework
4. Add "-all_load" to "Other Linker Flags" under the target's build settings. 5. Write some code!
#import "Eyeball.h"
#import "EyeballMessenger.h"
...
- (void)run
{
// Create a new Messenger server.
_server = [[EyeballMessengerServer alloc] init];
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
[_server enableRelayWithRelayAuthenticateBlock:^EyeballMessengerRelayAuthenticateResult *(EyeballMessengerRelayAuthenticateArgs *e)
{
// This is our authentication callback. It will be invoked
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if ([e.username isEqualToString:@"test"])
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the EyeballMessengerSTUN class to create one.
return [[[EyeballMessengerRelayAuthenticateResult alloc] initWithLongTermKeyBytes:[EyeballMessengerSTUN createLongTermKeyWithUsername:e.username realm:e.realm password:@"pa55w0rd!"]] autorelease];
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
//return [[[EyeballMessengerRelayAuthenticateResult alloc] initWithPassword:@"pa55w0rd!"] autorelease];
// This method of authentication is required by the TURN
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return nil;
}];
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
[_server start];
}
Creating a Server: Java
Running an Messenger server on Java is simple. Remember that the server runs on port 3478 by default,
and this port must be publicly accessible for incoming UDP packets.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
1. Add libraries to your project:
1. eyeball.jar 2. eyeball.messenger.jar
2. Write some code!
import eyeball.*;
import eyeball.messenger.*;
...
public void run()
{
// Create a new Messenger server.
Server server = new Server();
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
server.enableRelay(new SingleFunction()
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
public RelayAuthenticateResult invoke(RelayAuthenticateArgs e)
{
// This is our authentication callback. It will be invoked
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.getUsername().equals("test"))
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.createLongTermKey(e.getUsername(), e.getRealm(), "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
//return new RelayAuthenticateResult("pa55w0rd!");
// This method of authentication is required by the TURN
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return null;
}
});
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
server.start();
}
Creating a Server: Xamarin.Android
Note: when using the Android simulator, localhost is accessible through IP 10.0.2.2.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Running an Messenger server on Xamarin.Android is simple. Remember that the server runs on port 3478
by default, and this port must be publicly accessible for incoming UDP packets.
1. Add libraries to your project: 1. Eyeball.dll
2. Eyeball.Messenger.dll 2. Write some code!
using Eyeball;
using Eyeball.Messenger;
...
public void Run()
{
// Create a new Messenger server.
var server = new Eyeball.Messenger.Server();
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
server.EnableRelay((e) =>
{
// This is our authentication callback. It will be invoked
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.Username == "test")
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.CreateLongTermKey(e.Username, e.Realm, "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
//return new RelayAuthenticateResult("pa55w0rd!");
// This method of authentication is required by the TURN
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return null;
});
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
server.Start();
}
Creating a Server: Xamarin.iOS
Running an Messenger server on Xamarin.iOS is simple. Remember that the server runs on port 3478 by
default, and this port must be publicly accessible for incoming UDP packets.
1. Add libraries to your project:
1. Eyeball.dll 2. Eyeball.Messenger.dll
2. Write some code!
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
using Eyeball;
using Eyeball.Messenger;
...
public void Run()
{
// Create a new Messenger server.
var server = new Eyeball.Messenger.Server();
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
server.EnableRelay((e) =>
{
// This is our authentication callback. It will be invoked
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.Username == "test")
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.CreateLongTermKey(e.Username, e.Realm, "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
//return new RelayAuthenticateResult("pa55w0rd!");
// This method of authentication is required by the TURN
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// TURN, so the choice of which one to use is up to you.
}
return null;
});
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
server.Start();
}
Creating a Server: .NET, .NET Compact, and Mono
Running an Messenger server on .NET, .NET Compact, or Mono is simple. Remember that the server runs
on port 3478 by default, and this port must be publicly accessible for incoming UDP packets.
1. Add libraries to your project: 1. Eyeball.dll
2. Eyeball.Messenger.dll
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
2. Write some code!
using Eyeball;
using Eyeball.Messenger;
...
public void Run()
{
// Create a new Messenger server.
var server = new Eyeball.Messenger.Server();
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
server.EnableRelay((e) =>
{
// This is our authentication callback. It will be invoked
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.Username == "test")
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.CreateLongTermKey(e.Username, e.Realm, "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
//return new RelayAuthenticateResult("pa55w0rd!");
// This method of authentication is required by the TURN
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return null;
});
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
server.Start();
}
Creating a Server: Windows Phone
Running an Messenger server on Windows Phone is simple. Remember that the server runs on port 3478
by default, and this port must be publicly accessible for incoming UDP packets.
1. Add libraries to your project:
1. Eyeball.dll 2. Eyeball.Messenger.dll
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
2. Write some code!
using Eyeball;
using Eyeball.Messenger;
...
public void Run()
{
// Create a new Messenger server.
var server = new Eyeball.Messenger.Server();
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
server.EnableRelay((e) =>
{
// This is our authentication callback. It will be invoked
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.Username == "test")
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.CreateLongTermKey(e.Username, e.Realm, "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
//return new RelayAuthenticateResult("pa55w0rd!");
// This method of authentication is required by the TURN
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return null;
});
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
server.Start();
}
Creating a Server: Windows 8
Running an Messenger server on Windows 8 is simple. Remember that the server runs on port 3478 by
default, and this port must be publicly accessible for incoming UDP packets.
1. Add libraries to your project:
1. Eyeball.dll 2. Eyeball.Messenger.dll
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
2. Enable Capabilities in your package manifest (Package.appxmanifest):
0. Internet (Client) 1. Internet (Client & Server)
2. Home or Work Networking 3. Write some code!
using Eyeball;
using Eyeball.Messenger;
...
public void Run()
{
// Create a new Messenger server.
var server = new Eyeball.Messenger.Server();
// It is not always possible to establish a direct connection
// between peers. If both clients are using symmetric firewalls,
// or if one client is using a symmetric firewall and the other
// is using a port-restricted cone firewall, the only way for
// them to communicate is through a relay. An Messenger server is
// capable of relaying data packets, but it requires that client
// requests be authenticated. (Otherwise, a malicious user could
// launch a Denial-of-Service attack by sending a steady flow
// of unauthenticated socket allocation requests.) You can
// hard-code a username/password into your client application
// or use dynamic credentials, but some form of authentication
// must exist.
server.EnableRelay((e) =>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
// This is our authentication callback. It will be invoked
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.Username == "test")
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.CreateLongTermKey(e.Username, e.Realm, "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
//return new RelayAuthenticateResult("pa55w0rd!");
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// This method of authentication is required by the TURN
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return null;
});
// Start the Messenger server on the default port (3478). Make sure
// this port is open for incoming UDP packets in your firewall.
server.Start();
}
Enabling Logging: iOS and MAC
To enable logging for iOS or Mac, simply assign a log provider using [EyeballLog setProvider:]. An
EyeballNSLogProvider implementation is included that writes log statements to the output window.
#import "Eyeball.h"
...
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
- (void)run
{
[EyeballLog setProvider:[[[EyeballNSLogProvider alloc] initWithLogLevel:EyeballLogLevelInfo] autorelease]];
}
Enabling Logging: Java
To enable logging for Java, simply assign a log provider using Log.setProvider. A ConsoleLogProvider
implementation is included that writes log statements to the output window.
import eyeball.*;
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
...
public void run()
{
Log.setProvider(new ConsoleLogProvider(LogLevel.Info));
}
Enabling Logging: Javascript
To enable logging for JavaScript, simply assign a log provider using Log.setProvider. A domLogProvider
implementation is included that writes log statements to an element in the DOM (p, div, etc.).
<script type="text/javascript" src="eyeball.js"></script>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
...
<script type="text/javascript">
eyeball.log.setProvider(new eyeball.domLogProvider(document.getElementById('log'), eyeball.logLevel.Info));
</script>
Enabling Logging: Xamarin.Android
To enable logging for Xamarin.Android, simply assign a log provider to Log.Provider. A
ConsoleLogProvider implementation is included that writes log statements to the output window.
using Eyeball;
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
...
public void Run()
{
Log.Provider = new ConsoleLogProvider(LogLevel.Info);
}
Enabling Logging: Xamarin.iOS
To enable logging for Xamarin.iOS, simply assign a log provider to Log.Provider. A ConsoleLogProvider
implementation is included that writes log statements to the output window.
using Eyeball;
...
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
public void Run()
{
Log.Provider = new ConsoleLogProvider(LogLevel.Info);
}
Enabling Logging: .NET, .NET Compact, and Mono
To enable logging for .NET, .NET Compact, or Mono, simply assign a log provider to Log.Provider. A
ConsoleLogProvider implementation is included that writes log statements to the output window.
using Eyeball;
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
...
public void Run()
{
Log.Provider = new ConsoleLogProvider(LogLevel.Info);
}
Enabling Logging: Windows Phone
To enable logging for Windows Phone, simply assign a log provider to Log.Provider. A
ConsoleLogProvider implementation is included that writes log statements to the output window.
using Eyeball;
...
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
public void Run()
{
Log.Provider = new ConsoleLogProvider(LogLevel.Info);
}
Enabling Logging: Windows 8
To enable logging for Windows 8, simply assign a log provider to Log.Provider. A ConsoleLogProvider
implementation is included that writes log statements to the output window.
using Eyeball;
...
public void Run()
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
Log.Provider = new ConsoleLogProvider(LogLevel.Info);
}
Further Concepts
SDP
SDP stands for Session Description Protocol. It is a standard format (IETF RFC 4566) used for describing
media streams in real-time applications, and the one we use in Messenger for exchanging information
between two peers while establishing a direct connection.
When an Messenger connection is created, one peer takes the role of offerer (also known as the
controlling agent) and one peer takes the role of answerer (also known as the controlled agent) for the
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
purposes of exchanging information. The offerer has the responsibility of creating the initial offer as an
SDP message and sending it to the answerer through a third-party signalling mechanism (like WebSync
or SIP). When an offer is received, the answerer is reponsible for creating an answer, also as an SDP
message, and sending it back to the offerer, again through a third-party signalling mechanism (like
WebSync or SIP).
The SDP-formatted offers and answers include information about the identity of the peers, what media
streams and formats are supported, and any other information relevant to creating a new session. Media
streams are encrypted by default in Messenger, so the offer/answer will also typically include the
cryptographic details necessary for one peer to decrypt information sent by the other. For this reason,
we strongly recommend using an HTTPS (SSL) endpoint when creating the WebSync clients that will
exchange these messages.
After an offer or answer has been created, Messenger will start locating potential candidates (IP
address/port combinations) what could potentially be used for the peer-to-peer communication. Each
candidate Messenger finds is raised as an SDP attribute that must be sent to the peer through a third-
party signalling mechanism (like WebSync or SIP).
NAT
NAT stands for Network Address Translation. In general, it is the process used by routers to modify IP
information by translating local IP addresses on a private subnet to public IP addresses typically assigned
by an Internet service provider (ISP). They present a major challenge when attempting to establish direct
connections between clients on a network.
There are four types of NATs present in today's routers, presented in order from least restrictive to most
restrictive:
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Full cone
Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets
from iAddr:iPort will be sent through eAddr:ePort. Any external host can send packets to iAddr:iPort
by sending packets to eAddr:ePort.
Address-restricted cone
Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets
from iAddr:iPort will be sent through eAddr:ePort. An external host (hAddr:any) can send packets to
iAddr:iPort by sending packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to
hAddr:any. "Any" means the port number doesn't matter.
Port-restricted cone (like address-restricted cone, but the restriction includes port numbers)
Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets
from iAddr:iPort will be sent through eAddr:ePort. An external host (hAddr:hPort) can send packets to
iAddr:iPort by sending packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to
hAddr:hPort.
Symmetric
Each request from the same internal IP address and port to a specific destination IP address and port
is mapped to a unique external source IP address and port, if the same internal host sends a packet
even with the same source address and port but to a different destination, a different mapping is
used. Only an external host that receives a packet from an internal host can send a packet back.
The techniques necessary to establish a direct connection between peers become more challenging as
the NATs between them become more restrictive. In the worst case, a relay with a public IP address is
needed to exchange packets between peers.
Router/NAT Type Full Cone Address-Restricted Cone Port-Restricted Cone Symmetric
Full Cone Direct Direct Direct Direct
Address-Restricted Cone Direct Direct Direct Direct
Port-Restricted Cone Direct Direct Direct Relay
Symmetric Direct Direct Relay Relay
Direct links can be discovered using STUN alone. Relay links can only be discovered through the use of
TURN.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
STUN
STUN stands for Session Traversal Utilities for NAT. It is a network protocol/packet format (IETF RFC
5389) used by NAT traversal algorithms to assist in the discovery of network environment details.
Messenger uses STUN packets when communicating with the Messenger server and other Messenger
clients.
If the routers between peers use full cone, address-restricted, or port-restricted NAT, then a direct link
can be discovered with STUN alone. If either one of the routers use symmetric NAT, then a link can be
discovered with STUN packets only if the other router does not use symmetric or port-restricted NAT. Not
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
to fear, though! If this is the case, Messenger will automatically discover a relayed path through the use
of TURN.
TURN
TURN stands for Traversal Using Relays around NAT. Like STUN, it is a network protocol/packet format
(IETF RFC 5766) used to assist in the discovery of paths between peers on the Internet. It differs from
STUN in that it uses a public intermediary relay to relay packets between peers. Messenger uses TURN to
exchange media stream packets when no other option is available since it consumes server resources and
has an increased latency due to the extra step.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
The only time when TURN is necessary is when one of the peers is behind a symmetric NAT and the
other peer is behind either a symmetric NAT or port-restricted NAT. The frequency of cases where a relay
is necessary is difficult to pin down, but is estimated to be around 8% (source).
RTP/SRTP
RTP stands for Real-time Transport Protocol. It is a standard packet format (IETF RFC 3550) for
exchanging real-time data over streaming connections. SRTP stands for Secure Real-time Transport
Protocol. It is a standard RTP profile (IETF RFC 3711) that extends RTP by providing encryption and
message authentication for RTP and RTCP packets. Messenger uses SRTP for all media streams by
default. If encryption is disabled for a particular media stream, RTP is used instead.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Each RTP packet has a 7-bit payload type (0-127) and a binary payload. The payload type is used to
identify the format of the payload, and is defined when constructing an Messenger connection. IANA has
set aside some payload types for specific formats. If you are using an encoding format recognized by
IANA, it is recommended that you use the corresponding payload type. If you are using an encoding
format NOT recognized by IANA, it is recommended that you use a value in the range 96-127, per their
recommendations. The only restriction enforced by Messenger is that payload types 72-76 are reserved
for RTCP. These payload types may not be used as it would interfere with internal RTCP processing.
SRTP packets are secured using 128-bit AES encryption and either 80-bit (strong) or 32-bit (weak)
HMAC-SHA1 authentication. In general, we recommend using strong authentication for all media streams.
However, there are applications where weak authentication makes sense - where the packets are
frequent and small (and thus the extra 6 bytes cause a substantial increase in bandwidth) and where a
forged packet is unlikely and inconsequential. For example, a voice telephony application that uses 20-ms
packets will send 50 packets per second. Weak authentication will save 300 bytes per second in
bandwidth, with the likelihood of a forged packet being only one in 2^32 (IETF RFC 3711).
ICE
ICE stands for Interactive Connectivity Establishment. It defines a technique that uses SDP, STUN, and
TURN to discover a network path between peers on the Internet. Messenger implements the ICE
specification (IETF RFC 5245) and as such, is compatible with other clients that implement the same
spec.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Each RTP packet has a 7-bit payload type (0-127) and a binary payload. The payload type is used to
identify the format of the payload, and is defined when constructing an Messenger connection. IANA has
set aside some payload types for specific formats. If you are using an encoding format recognized by
IANA, it is recommended that you use the corresponding payload type. If you are using an encoding
format NOT recognized by IANA, it is recommended that you use a value in the range 96-127, per their
recommendations. The only restriction enforced by Messenger is that payload types 72-76 are reserved
for RTCP. These payload types may not be used as it would interfere with internal RTCP processing.
Platform Considerations
iOS and Mac
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Messenger requires an autorelease pool and run loop to function properly. Most iOS and Mac applications
take care of this automatically, but if your code is building and running, but not doing anything, make
sure that your application initializes an autorelease pool and runs a run loop in its startup code
(e.g. main.m):
// use autorelease
@autoreleasepool
{
Application *app = [Application new];
[app run];
[[NSRunLoop currentRunLoop] run];
}
Xamarin.Android
While Xamarin has done an excellent job integrating support for native libraries, it can still be finicky
when relying on native code like VP8 in the WebRTC example.
When including a native library (i.e. libvpx.so, libvpxJNI.so) in your project, make sure that the following
Properties are set:
1. Build Action: AndroidNativeLibrary
2. Copy to Output Directory: Copy always
If you don't do this, you will probably see messages like the following in your logs:
Cannot load library[1234]: 1985 'libvpxJNI.o failed'
.NET, .NET Compact, and Mono
.NET applications must use a Visual C++/CLI library to provide VP8 codec support. This library (Win.VP8)
is provided for you in the SDK. Make sure that the Win.VP8 project uses the correct Platform Toolset for
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
your installed version of Visual Studio. This can be configured in the Win.VP8 Project Properties under
General > Platform Toolset.
When building for distribution to computers that do not have Visual Studio installed, make
sure you:
1. Build in Release mode.
2. Package the Visual C++ runtime DLL with your executable.
1. msvcr90.dll when using the Visual Studio 2008 (9.0) Platform Toolset and .NET 3.5. 2. msvcr100.dll when using the Visual Studio 2010 (10.0) Platform Toolset and .NET 4.0.
3. msvcr110.dll when using the Visual Studio 2012 (11.0) Platform Toolset and .NET 4.5. 4. etc...
.NET
Version
Visual
Studio
Visual
C++ Platform
Toolset
Visual C++ Runtime DLL
3.5 2008 9.0 C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\redist\(architecture)\Microsoft.VC90.CRT\msvcr90.dll
4.0 2010 10.0 C:\Program Files (x86)\Microsoft Visual Studio
10.0\VC\redist\(architecture)\Microsoft.VC100.CRT\msvcr100.dll
4.5 2012 11.0 C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\redist\(architecture)\Microsoft.VC110.CRT\msvcr110.dll
Pay careful attention to warnings from Visual Studio when building applications in .NET that use Visual
C++ libraries. The application and library must be built using the same target architecture (x86/Win32 or
x64).
.NET Application Architecture Visual C++ Library Architecture
x86 Win32
x64 x64
Any CPU Incompatible
Code Snippets
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Audio-Only in Audio/Video Conference
Messenger's audio/video engine is completely pluggable, so if you want an audio-only client to join an
audio/video conference, this can be done relatively easily with a custom video capture provider.
(Consider, for example, a client that wants to join an audio/video conference, but doesn't have a webcam
or doesn't have the processing power to encode real-time video.)
// Get standard audio/video media.
UserMedia.GetMedia(new GetMediaArgs(true, true)
{
OnFailure = (fe1) =>
{
// The video device is most likely unavailable. Let's try
// again, but using a custom video provider that always
// succeeds, but never sends frames.
UserMedia.GetMedia(new GetMediaArgs(true, true)
{
VideoCaptureProvider = new EmptyVideoCaptureProvider(),
OnFailure = (fe2) =>
{
// The audio device is unavailable. :(
},
OnSuccess = (e) =>
{
// Good to go. Create AudioStream, VideoStream, and Conference.
// This code should be identical to the other OnSuccess handler (below).
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
});
},
OnSuccess = (e) =>
{
// Good to go. Create AudioStream, VideoStream, and Conference.
// This code should be identical to the other OnSuccess handler (above).
}
});
// Define this class somewhere. It's a custom video capture
// provider that does nothing but indicate that it works. It
// never calls RaiseFrame, so data is never sent to the
// processing engine and hence never sent to peers.
class EmptyVideoCaptureProvider : VideoCaptureProvider
{
public override void Initialize(VideoCaptureInitializeArgs e) { }
public override void Start() { RaiseReady(); }
public override void Stop() { }
public override void Destroy() { }
public override string[] GetDeviceNames() { return null; }
public override string GetLabel() { return "Empty"; }
}
Custom Layouts
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
The layout managers included with the WebRTC extension for Messenger, while completely optional, can
be extremely useful in rapidly developing your application. They provide a sensible default layout
algorithm together with control and UI thread management to make your development as easy as
possible. It is possible to customize this layout algorithm by hooking into the layout manager's OnLayout
callback. The Layout property of the event arguments defines the layout structure that will be executed
and can be customized to your heart's content.
layoutManager.OnLayout = (e) =>
{
// If there are remote video controls, always
// keep the local preview in the top left corner.
if (e.RemoteCount > 0)
{
e.Layout.LocalFrame.X = 10;
e.Layout.LocalFrame.Y = 10;
}
};
Custom Port Range
Messenger allows you to customize the port range used when opening UDP sockets for P2P
communications. Simply set the RtpPortMin and RtpPortMax properties at the conference- or link-level.
// constrain to 40XXX
conference.RtpPortMin = 40000; // minimum value, inclusive
conference.RtpPortMax = 40998; // maximum value, inclusive
Detecting Packet Loss
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Messenger's eventing system allows direct access to the underlying RTP packet stream. Simply attach to
the ReceiveRTP event at the layer of your choice (stream/conference/link). A common use case is to
detect packet loss or re-ordering and adjust the quality of the video codec in your application.
videoStream.OnLinkReceiveRTP += (e) =>
{
var lastSequenceNumber = e.NegotiatedStream.LastSequenceNumber;
if (lastSequenceNumber > -1)
{
var sequenceNumberDelta = RTPPacket.GetSequenceNumberDelta(
e.Packet.SequenceNumber, lastSequenceNumber);
if (sequenceNumberDelta > 1)
{
// a packet was lost or is arriving out of order
}
}
};
Disabling DTLS
DTLS provides secure key exchange over the P2P UDP connection, but adds additional overhead to the
connection establishment process. If your offers/answers are sent securely (for example, over HTTPS or
a secure WebSocket), then you can disable DTLS for faster connection establishment:
var audioStream = new AudioStream(localMedia, false); // set offerDtls to false
var videoStream = new VideoStream(localMedia, false); // set offerDtls to false
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Disabling Use of the Relay
Use of the relay can be disabled by suppressing relay candidates.
Simply set the SuppressRelayCandidates flag to true:
// By suppressing relay candidates, we
// effectively disable use of the relay
// for the current device.
conference.SuppressRelayCandidates = true;
Distributing Relay Load
The Messenger server is completely scalable. Depending on the size of your application, one server may
not be sufficient for relaying data packets. Exactly how many links can be relayed per server depends on
the bandwidth requirements of your application. Proper testing will help you discover the exact limits.
Each Messenger client is equipped with an Messenger server IP address when linking to a peer. At the
application-level, you can distribute relay load across your Messenger servers by handing out server IPs
in a round-robin fashion when initializing new Messenger clients. A simple HTTP request should be all
that is needed for a client to retrieve the next server IP address. A more robust solution would involve
Virtual IP (VIP) load balancing.
Within Messenger itself, there are mechanisms built-in that allow you to redirect clients to a new server under any condition that can be checked from code. In the RequestReceived event handler, allocate
requests can be redirected if the server is overloaded by simply throwing a 'Try-Alternate' exception with the IP address of a different server.
server.RequestReceived += (s, e) =>
{
// A STUNAllocateRequest is the STUN-compatible client request used to
// allocate a socket on the server for relaying. If the server is under
// heavy load when a new allocate request arrives, we can throw a
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// STUNTryAlternateException with an alternate server IP address. The
// client connection process will then attempt its socket allocation
// request on the alternate server.
if (e.Request is STUNAllocateRequest && s.RelayAllocationCount > 20000)
{
var alternateServer = new STUNAlternateServerAttribute("server IP address", 3478);
throw new STUNTryAlternateException(alternateServer);
}
};
Enabling Stale-Nonce Security
The TURN spec describes a nonce-based security algorithm. By default, it is disabled in our
implementation since it is not supported by any web browsers at the time of this writing, but it can be
enabled if desired.
server.StaleNonceSecurity = true;
Forcing Use of the Relay
Use of the relay can be forced by suppressing private and public link candidates (leaving only relayed
candidates).
Simply set the SuppressPrivateCandidates and SuppressPublicCandidates flags to true:
// By suppressing both private (host) and public
// (edge, a.k.a. server-reflexive) candidates, we
// are left with only relayed candidates for this
// device.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
conference.SuppressPrivateCandidates = true;
conference.SuppressPublicCandidates = true;
Getting a Conference Link
Individual links in a conference can be retrieved when needed.
conference.GetLink(peerId);
Multiple Servers
To address high-availability needs, it is possible to provide a Conference with the addresses of multiple
servers that will be tried in succession. It is also possible to have Messenger randomize the order of the
servers for each link that is created to distribute the load:
// Server addresses are formatted as {address} or
// {address}:{port}. If the port is not specified,
// it will default to 3478.
var conference = new Conference(new[] { "stun1.mydomain.com", "stun2.mydomain.com", "stun3.mydomain.com:19302" }, ...);
// Optionally, you can randomize the server list
// for each new link by setting the following flag.
conference.RandomizeServers = true;
Muting and Unmuting
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
The WebRTC extension allows you to mute/unmute audio/video at any time. To mute your local audio or
video, simply call LocalMediaStream.MuteAudio/Video. To unmute, call
LocalMediaStream.UnmuteAudio/Video:
// The local media stream is created by the call to UserMedia.GetMedia.
// Muting the video immediately halts the capturing of video frames
// from the local capture provider.
localStream.MuteVideo();
// Muting the audio immediately halts the capturing of audio frames
// from the local capture provider.
localStream.MuteAudio();
You can also stop the rendering of incoming remote audio/video by calling Link.MuteRemoteAudio/Video.
To start rendering again, call Link.UnmuteRemoteAudio/Video.
// Remote media streams are created when a link is established.
// Muting the video immediately halts the rendering of video frames
// from the remote peer.
link.MuteRemoteVideo(); // In Java: LinkExtensions.MuteRemoveVideo(link);
link.RenderVideoBuffer(blankImage); // In Java: LinkExtensions.RenderVideoBuffer(link, blankImage);
// Muting the audio immediately halts the rendering of audio frames
// from the remote peer.
link.MuteRemoteAudio(); // In Java: LinkExtensions.MuteRemoteAudio(link);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Note that we call RenderVideoBuffer after muting the remote video feed. The last received video frame
from the peer is displayed in the video control until a new image is supplied by this call.
Null Encryption
Messenger streams use AES 128-bit encryption by default with strong 80-bit HMAC-SHA1 message
authentication. To disable encryption and authentication, set the encryption mode
to EncryptionMode.Null when creating the stream definition. To disable just encryption and leave
message authentication enabled, use EncryptionMode.NullStrong. To disable encryption and weaken
message authentication from 80-bit to 32-bit, useEncryptionMode.NullWeak.
var stream = new Stream(StreamType.Text, new StreamFormat("utf8"), EncryptionMode.Null);
Warning: Unencrypted packets can be intercepted and read by a third-party listening to network traffic.
Using null encryption improves performance due to reduced computation, but do so at your
Parsing SDP Candidates
Messenger uses SDP for signalling (offers/answers and candidates) which makes it easy to parse SDP
candidates coming in and going out to determine local and remote IP addresses (and ports).
conference.OnLinkCandidate += (e) =>
{
// Parse the SDP candidate string.
var sdpCandidate = (SDPCandidateAttribute)SDPAttribute.Parse(candidate.SdpCandidateAttribute);
// "Host" candidates represent local/private IP address/port combinations.
if (sdpCandidate.CandidateType == SDPCandidateType.Host)
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
var localIP = sdpCandidate.ConnectionAddress;
var localPort = sdpCandidate.Port;
}
// "Reflexive" candidates represent public IP address/port combinations.
if (sdpCandidate.CandidateType == SDPCandidateType.ServerReflexive ||
sdpCandidate.CandidateType == SDPCandidateType.PeerReflexive)
{
var externalIP = sdpCandidate.ConnectionAddress;
var externalPort = sdpCandidate.Port;
var internalIP = sdpCandidate.RelatedAddress;
var internalPort = sdpCandidate.RelatedPort;
}
// "Relayed" candidates represent public IP address/port combinations *on a TURN relay*.
if (sdpCandidate.CandidateType == SDPCandidateType.Relayed)
{
var externalIP = sdpCandidate.ConnectionAddress;
var externalPort = sdpCandidate.Port;
var internalIP = sdpCandidate.RelatedAddress;
var internalPort = sdpCandidate.RelatedPort;
}
};
Relay Authentication
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
It is not always possible to establish a direct connection between peers. (This happens approximately 8%
of the time, according to Google.) If both clients are using symmetric firewalls, or if one client is using a
symmetric firewall and the other is using a port-restricted cone firewall, the only way for them to
communicate is through a relay. An Messenger server is capable of relaying data packets, but it requires
that client requests be authenticated. (Otherwise, a malicious user could launch a Denial-of-Service
attack by sending a steady flow of unauthenticated socket allocation requests.) You can hard-code a
username/password into your client application or use dynamic credentials, but some form of
authentication must exist.
server.EnableRelay((e) =>
{
// This is our authentication callback. It will be invoked
// whenever a request is received that changes the state of
// the relay (allocating a socket on the relay, creating a
// new permission for a socket on the relay, etc.). The
// Username and Realm properties of the event arguments are
// those supplied by the client. If the Username or Realm
// is invalid, simply return null. Otherwise, return a new
// RelayAuthenticateResult which will be used to authenticate
// the request.
if (e.Username == "test")
{
// Option A: return the MD5 hash of the following string:
// "username:realm:password"
// This is only possible if you are storing this hash
// somewhere, calculated at a time when the user supplied
// you momentarily with their password. This hash is
// known as a long-term key, and a utility method is
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// available in the STUN class to create one.
return new RelayAuthenticateResult(STUN.CreateLongTermKey(e.Username, e.Realm, "pa55w0rd!"));
// Option B: return the user's password in plain-text.
// This is only possible if you are storing the user's
// password somewhere in plain-text, so this approach
// has obvious security risk if your server is hacked.
//return new RelayAuthenticateResult("pa55w0rd!");
// This method of authentication is required by the TURN
// specification which Messenger implements for third-party
// compatibility. Either option is acceptable according to
// TURN, so the choice of which one to use is up to you.
}
return null;
});
Sending a Private Conference Packet
The usual intention when sending a packet to a conference is for the packet to be sent to all peer links.
However, an overload is provided that allows you to send to a specific link. All you need is the peer ID.
conference.SendRTP(stream.Format, packet, peerId);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Signing the Java Applet or ActiveX Control
The Enterprise editions of Messenger include an unsigned Java applet and unsigned ActiveX control so
you can use your own code-signing certificate.
To sign the Java applet, use the jarsigner utility included with the JDK.
jarsigner -storetype pkcs12 -keystore your_certificate.p12 -storepass your_password -tsa http://tsa.starfieldtech.com/ eyeball.messenger.webrtc.applet.jar keystore_alias
To sign the ActiveX control, use the SignTool utility included with the Windows SDK.
signtool.exe sign /f your_certificate.p12 /p your_password /t http://tsa.starfieldtech.com/ Eyeball.Messenger.WebRTC.ActiveX.cab
Supressing Candidates
Messenger defines three candidate types:
Private
IP address/port combinations on your host device (i.e. your LAN addresses)
Public
IP address/port combinations on your network edge device (e.g. your WAN addresses)
Relay
IP address/port combinations on your Messenger relay server
It can sometimes be desirable to suppress a specific candidate type from consideration. Say, for example,
that you wanted to record all audio/video conversations and archive them on your server. You could do
the recording client-side and upload afterwards, or you could suppress private and public candidates,
thereby forcing use of the relay, and do the recording server-side. Alternatively, if you don't want to host
a relay server and are willing to have your clients use UPnP or port forwarding to make themselves
publicly accessible, you could suppress relay candidates.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Whatever the situation, Messenger provides three properties on conferences and links that allow you to
suppress each of the three candidate types:
SuppressPrivateCandidates SuppressPublicCandidates
SuppressRelayCandidates
// force use of the relay for server recording
connection.SuppressPrivateCandidates = true;
connection.SuppressPublicCandidates = true;
Switching Cameras
Switching cameras and/or microphones in the middle of a live conference is easy. Several methods are
available on LocalMediaStream to assist you:
// To set the video device number, use:
localMedia.SetVideoDeviceNumber(...);
// You can get the front/rear video device numbers using:
localMedia.GetFrontVideoDeviceNumber();
localMedia.GetRearVideoDeviceNumber();
// We provide you with a couple shortcuts:
localMedia.UseFrontVideoDevice();
localMedia.UseRearVideoDevice();
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// Or if you just want to toggle between devices, use:
localMedia.UseNextVideoDevice();
Using Links Directly
While we normally recommend using the Conference API, there are some cases (like integration with
third-party software) that require you to go a level deeper and use the underlying Link API to fine-tune
behaviour.
Creating a Link
var link = new eyeball.messenger.link();
link.setPeerId(...); // optional, can be used to identify the peer associated with this link
link.setPeerState(...); // optional, can be used to store details about the peer (i.e. username)
link.initialize(serverAddress, serverPort, [ stream1, stream2, ... ], function(link)
{
// link is ready for negotiation!
});
Attaching Events
link.addOnInit(function(e)
{
// fires when the link is being set up.
// this is always the first event to fire.
});
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
link.addOnUp(function(e)
{
// fires when link is up and connected.
// this will only fire once, and always after Init.
});
link.addOnDown(function(e)
{
// fires when link is down and disconnected.
// this will only fire once, and always after Init or Up.
});
link.addOnCandidate(function(e)
{
// fires when a candidate has been discovered and should be sent to the peer (via signalling).
// this can fire multiple times, and always after Init.
// for convenience, we provide a simple way to serialize the candidate to JSON.
// (includes SDP candidate attribute and SDP media index)
var candidateJson = e.getCandidate().toJson();
});
Creating an Offer or Answer
// to create an offer, use link.createOffer. (link will be controlling)
// to create an answer, use link.createAnswer. (link will be controlled)
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// the syntax/arguments are identical.
link.createOffer({
onSuccess: function(e)
{
// fires when the offer (or answer) is ready.
// for convenience, we provide a simple way to serialize the offer (or answer) to JSON.
// (includes SDP offer (or answer), isOffer flag, and tie breaker string)
var offerAnswerJson = e.getOfferAnswer().toJson();
},
onFailure: function(e)
{
var error = e.getException().message;
alert(error);
}
});
Accepting an Offer or Answer
// to accept an offer (or answer), use link.accept.
// the offer (or answer) should arrive via signalling.
link.accept({
offerAnswer: eyeball.messenger.offerAnswer.fromJson(offerAnswerJson),
onSuccess: function(e)
{
// fires when the offer (or answer) has been accepted.
},
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
onFailure: function(e)
{
var error = e.getException().message;
alert(error);
}
});
Adding a Remote Candidate
// to add a remote candidate, use link.addRemoteCandidate.
// the candidate should arrive via signalling.
link.addRemoteCandidate(eyeball.messenger.candidate.fromJson(candidateJson));
Weak Encryption
Messenger streams use AES 128-bit encryption by default with strong 80-bit HMAC-SHA1 message
authentication. In some applications, it may be desirable to weaken the message authentication to 32-
bits, particularly if bandwidth is a concern and the cost of packet injection is not critical (for example, a
simple telephony application). To weaken authentication, simply set the encryption mode
to EncryptionMode.AES128Weak when creating the stream definition.
var stream = new Stream(StreamType.Audio, new StreamFormat("speex"), EncryptionMode.AES128Weak);
WebRTC Audio
WebRTC audio streams allow you to share an audio feed directly between peers, including web browsers.
To add an audio stream to your conference, first use UserMedia to get a reference to the local device
media stream, then create a WebRTC AudioStream.
Audio and video can be combined by requesting both audio and video from UserMedia.GetMedia.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
eyeball.messenger.webrtc.userMedia.getMedia({
audio: true,
onSuccess: function(e)
{
var audioStream = new eyeball.messenger.webrtc.audioStream(e.getLocalStream());
// the AudioStream can be used like any other Messenger stream
var conference = new eyeball.messenger.conference(serverAddressMessenger, serverPortMessenger, [ audioStream ]);
...
}
});
WebRTC Data Channels
WebRTC data channels allow you to transfer arbitrary data directly between peers, including web
browsers. To send messages between peers using data channels, create a WebRTC DataChannelStream.
TheDataChannelStream takes an array of DataChannelInfo instances, each of which describes a single
data channel with a handler for incoming messages.
var dataChannelInfo = new eyeball.messenger.webrtc.dataChannelInfo({
label: 'mydatachannel',
onReceive: function(e) {
alert(e.getPacket().getData());
}
});
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
var dataChannelStream = new eyeball.messenger.webrtc.dataChannelStream([ dataChannelInfo ]);
// the DataChannelStream can be used like any other Messenger stream
var conference = new eyeball.messenger.conference(serverAddressMessenger, serverPortMessenger, [ dataChannelStream ]);
...
To send data to a specific channel, use Conference.SendData(DataChannelInfo,
DataChannelPacket).
conference.sendData({
channelInfo: dataChannelInfo,
packet: 'Hello, WebRTC!'
});
WebRTC Video
WebRTC video streams allow you to share a video feed directly between peers, including web browsers.
To add a video stream to your conference, first use UserMedia to get a reference to the local device
media stream, then create a WebRTC VideoStream.
Video streams are more complicated than audio streams since they have to interact with your user
interface. To make this as easy as possible, Messenger includes a LayoutManager that automatically
positions your local video feed and any remote video feeds in a sensible manner. All it requires is a target
container in which to arrange the controls. In JavaScript, this can be any DOM element.
Audio and video can be combined by requesting both audio and video from UserMedia.GetMedia.
eyeball.messenger.webrtc.userMedia.getMedia({
video: true,
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
videoWidth: 320,
videoHeight: 240,
videoFrameRate: 15,
onSuccess: function(e)
{
var layoutManager = new eyeball.messenger.webrtc.layoutManager(document.getElementById('container'));
layoutManager.setLocalVideoControl(e.getLocalVideoControl());
var videoStream = new eyeball.messenger.webrtc.videoStream(e.getLocalStream());
videoStream.addOnLinkInit(function(e)
{
// Add the remote video control to the layout.
layoutManager.addRemoteVideoControl(e.getPeerId(), e.getLink().getRemoteVideoControl());
});
videoStream.addOnLinkDown(function(e)
{
// Remove the remote video control from the layout.
layoutManager.removeRemoteVideoControl(e.getPeerId());
});
// the VideoStream can be used like any other Messenger stream
var conference = new eyeball.messenger.conference(serverAddressMessenger, serverPortMessenger, [ videoStream ]);
...
}
});
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Diagrams
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Extensibility
WebRTC Capturing and Rendering
Messenger's WebRTC extension offers complete flexibility when capturing and rendering audio and video.
Some platforms, like iOS/Mac, provide built-in framework support to easily capture audio samples and
video frames from attached hardware. Other platforms, like Java, provide little to no framework support
by default and rely on native bindings or the Android SDK.
To allow for every scenario, we use a simple provider model - one provider each for audio/video
capturing/rendering.
AudioCaptureProvider VideoCaptureProvider AudioRenderProvider VideoRenderProvider
Writing a Provider
Let's walk through creating a set of providers that use NAudio for audio capture and
rendering, AForge.NET for video capture, and the WinForms PictureBox control for video rendering.
Create an AudioCaptureProvider
implementation
Writing an AudioCaptureProvider requires implementing five methods (presented in the order they are
invoked):
Initialize Initializes the provider. Arguments to this method include details about the desired device number,
output clock rate, and number of channels. Start
Starts polling the audio hardware for samples, invoking RaiseFrame whenever a buffer of samples
has been captured. GetLabel
Purely for debugging purposes. Should return the name of the audio device if possible. Stop
Called when the stream is no longer required - no more frames should be raised at this point. Destroy
Allows you to release unmanaged resources and tie up any loose ends.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
NAudio provides support for a slew of sound capture APIs. For this example, we'll use the WaveIn classes
since they have the broadest platform support, but there are a variety of other options that are less
universal, but more suitable for real-time audio recording (read: lower latency), such as ASIO or Core
Audio for Windows.
class NAudioCaptureProvider : AudioCaptureProvider
{
private WasapiCapture WasapiIn = null;
private WaveInEvent WaveIn = null;
private string Label = null;
private int? DesiredDeviceNumber = null;
private int ClockRate = 0;
private int Channels = 0;
public override void Initialize(AudioCaptureInitializeArgs captureArgs)
{
DesiredDeviceNumber = captureArgs.DeviceNumber;
ClockRate = captureArgs.ClockRate;
Channels = captureArgs.Channels;
}
public override void Start()
{
try
{
MMDevice selectedDevice = null;
var deviceEnumerator = new MMDeviceEnumerator();
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
if (DesiredDeviceNumber.HasValue)
{
var deviceCollection = deviceEnumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
if (DesiredDeviceNumber.Value < deviceCollection.Count)
{
selectedDevice = deviceCollection[DesiredDeviceNumber.Value];
}
}
if (selectedDevice == null)
{
selectedDevice = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, Role.Console);
}
Label = selectedDevice.DeviceFriendlyName;
WasapiIn = new WasapiCapture(selectedDevice);
if (WasapiIn.WaveFormat.BitsPerSample != 32 && WasapiIn.WaveFormat.BitsPerSample != 16)
{
throw new Exception(string.Format("WASAPI wave format not supported ({0} bits per sample).", WasapiIn.WaveFormat.BitsPerSample));
}
WasapiIn.DataAvailable += new EventHandler<WaveInEventArgs>(Capture_DataAvailable);
WasapiIn.StartRecording();
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
catch (Exception ex)
{
if (Log.IsDebugEnabled)
{
Log.DebugFormat("Could not load WASAPI audio capture. {0}", ex.Message);
}
WaveIn = new WaveInEvent();
WaveIn.BufferMilliseconds = 20;
WaveIn.DataAvailable += new EventHandler<WaveInEventArgs>(WaveIn_DataAvailable);
if (DesiredDeviceNumber.HasValue)
{
WaveIn.DeviceNumber = DesiredDeviceNumber.Value;
}
WaveIn.WaveFormat = new WaveFormat((int)ClockRate, 16, Channels);
Label = WaveInEvent.GetCapabilities(WaveIn.DeviceNumber).ProductName;
WaveIn.StartRecording();
}
}
private void Capture_DataAvailable(object sender, WaveInEventArgs e)
{
if (!IsMuted)
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
using (var inputStream = new MemoryStream())
{
if (WasapiIn.WaveFormat.BitsPerSample == 32)
{
// convert to shorts
int i, j;
var bytes = new byte[e.BytesRecorded / 2];
for (i = 0, j = 0; i < e.BytesRecorded; i += 4)
{
var floatValue = BitAssistant.ToFloat(e.Buffer, i);
var shortValue = (short)(floatValue * 32767.0f);
var shortBytes = BitAssistant.GetShortBytes(shortValue);
bytes[j++] = shortBytes[0];
bytes[j++] = shortBytes[1];
}
inputStream.Write(bytes, 0, bytes.Length);
}
else
{
inputStream.Write(e.Buffer, 0, e.BytesRecorded);
}
inputStream.Seek(0, SeekOrigin.Begin);
var inputStreamFormat = new WaveFormat(WasapiIn.WaveFormat.SampleRate, 16, WasapiIn.WaveFormat.Channels);
using (var inputWaveStream = new RawSourceWaveStream(inputStream, inputStreamFormat))
{
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
var outputStreamFormat = new WaveFormat(ClockRate, 16, Channels);
using (var outputStream = new WaveFormatConversionStream(outputStreamFormat, inputWaveStream))
{
byte[] output = new byte[outputStream.Length];
outputStream.Read(output, 0, (int)outputStream.Length);
RaiseFrame(new AudioBuffer(output, 0, (int)outputStream.Length));
}
}
}
}
}
private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
{
if (!IsMuted)
{
RaiseFrame(new AudioBuffer(e.Buffer, 0, e.BytesRecorded));
}
}
public override void Stop()
{
if (WasapiIn != null)
{
WasapiIn.StopRecording();
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
if (WaveIn != null)
{
WaveIn.StopRecording();
}
}
public override string GetLabel()
{
return Label;
}
public override void Destroy()
{
if (WasapiIn != null)
{
WasapiIn.Dispose();
}
if (WaveIn != null)
{
WaveIn.Dispose();
}
}
}
Create a VideoCaptureProvider
implementation
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Writing a VideoCaptureProvider requires implementing six methods (presented in the order they are
invoked):
Initialize Initializes the provider. Arguments to this method include details about the desired device number,
output clock rate, and desired frame rate/size. Start
Starts polling the video hardware for frames, invoking RaiseFrame whenever an image has been
captured. GetPreviewControl
Should return a control that always shows the latest captured frame. GetLabel
Purely for debugging purposes. Should return the name of the video device if possible. Stop
Called when the stream is no longer required - no more frames should be raised at this point. Destroy
Allows you to release unmanaged resources and tie up any loose ends.
One of AForge.NET's quirks is that it does not supply frames from camera devices that are in use by
another application. To work around this, we wait up to CameraTimeout milliseconds (2000) to receive a
frame before considering it locked and moving on to the next device.
class AForgeVideoCaptureProvider : VideoCaptureProvider
{
private VideoCaptureDevice CaptureDevice = null;
private string Label = null;
private int? DesiredDeviceNumber = null;
private int DesiredFrameRate = 0;
private Size DesiredFrameSize = Size.Empty;
private int ClockRate = 0;
private string AttemptedMonikerString = null;
private TimeoutTimer NoVideoTimer = null;
private List<string> NoVideoMonikerStrings = new List<string>();
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
private PictureBoxVideoRenderProvider Preview = null;
public AForgeVideoCaptureProvider()
{
Preview = new PictureBoxVideoRenderProvider();
}
public override void Initialize(VideoCaptureInitializeArgs captureArgs)
{
DesiredDeviceNumber = captureArgs.DeviceNumber;
DesiredFrameRate = captureArgs.FrameRate;
DesiredFrameSize = new Size(captureArgs.Width, captureArgs.Height);
ClockRate = captureArgs.ClockRate;
InitializeCamera();
}
private bool InitializeCamera()
{
var videoDevice = GetVideoDevice();
if (videoDevice == null)
{
return false;
}
CaptureDevice = new VideoCaptureDevice(videoDevice.MonikerString);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
CaptureDevice.DesiredFrameRate = DesiredFrameRate;
CaptureDevice.DesiredFrameSize = DesiredFrameSize;
CaptureDevice.NewFrame += CaptureDevice_NewFrame;
AttemptedMonikerString = videoDevice.MonikerString;
Label = videoDevice.Name;
return true;
}
private FilterInfo GetVideoDevice()
{
// try get to get the desired device first
var deviceNumber = 0;
var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (DesiredDeviceNumber.HasValue)
{
foreach (FilterInfo videoDevice in videoDevices)
{
if (deviceNumber == DesiredDeviceNumber.Value && !NoVideoMonikerStrings.Contains(videoDevice.MonikerString))
{
return videoDevice;
}
deviceNumber++;
}
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
// if that fails, get the first available
foreach (FilterInfo videoDevice in videoDevices)
{
if (!NoVideoMonikerStrings.Contains(videoDevice.MonikerString))
{
return videoDevice;
}
}
return null;
}
private void CaptureDevice_NewFrame(object sender, NewFrameEventArgs e)
{
if (NoVideoTimer != null)
{
if (!NoVideoTimer.Stop())
{
return;
}
RaiseReady();
NoVideoTimer = null;
}
if (!IsMuted)
{
// create video buffer
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
var videoBuffer = ImageUtility.BitmapToBuffer(e.Frame);
// update preview
Preview.RenderBitmap((Bitmap)e.Frame.Clone());
// raise event
RaiseFrame(videoBuffer);
}
}
public override void Start()
{
if (CaptureDevice != null)
{
NoVideoTimer = new TimeoutTimer();
NoVideoTimer.Start(CameraTimeout, NoVideoTimeout, null);
CaptureDevice.Start();
}
}
private void NoVideoTimeout(object state)
{
CaptureDevice.NewFrame -= CaptureDevice_NewFrame;
CaptureDevice.Stop();
// try again with the next one
NoVideoMonikerStrings.Add(AttemptedMonikerString);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
if (InitializeCamera())
{
Start();
}
else
{
RaiseBusy();
}
}
public override void Stop()
{
if (CaptureDevice != null)
{
CaptureDevice.SignalToStop();
}
}
public override string GetLabel()
{
return Label;
}
public override void Destroy()
{ }
public override object GetPreviewControl()
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
{
return Preview.GetControl();
}
}
Create an AudioRenderProvider implementation
Writing an AudioRenderProvider requires implementing three methods (presented in the order they are
invoked):
Initialize Initializes the provider. Arguments to this method include details about the output clock rate and
number of channels. Render
Should render an audio frame to the output stream. Destroy
Allows you to release unmanaged resources and tie up any loose ends.
NAudio provides support for a variety of sound render APIs. For this example, we'll use the WaveOut
classes since they have the broadest platform support, but there are a variety of other options that are
less universal, but more suitable for real-time audio recording (read: lower latency), such as ASIO or Core
Audio for Windows.
class NAudioRenderProvider : AudioRenderProvider
{
private WasapiOut WasapiOut = null;
private WaveOutEvent WaveOut = null;
private BufferedWaveProvider WaveProvider = null;
public override void Initialize(AudioRenderInitializeArgs renderArgs)
{
int clockRate = renderArgs.ClockRate;
int channel = renderArgs.Channels;
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
WaveProvider = new BufferedWaveProvider(new WaveFormat(clockRate, 16, channel));
WaveProvider.DiscardOnBufferOverflow = true;
// WaveProvider.BufferDuration = new TimeSpan(0, 0, 0, 0, 300);
try
{
WasapiOut = new WasapiOut(AudioClientShareMode.Shared, 0);
WasapiOut.Init(WaveProvider);
WasapiOut.Play();
}
catch (Exception ex)
{
if (Log.IsDebugEnabled)
{
Log.DebugFormat("Could not load WASAPI audio render. {0}", ex.Message);
}
WaveOut = new WaveOutEvent();
WaveOut.DesiredLatency = 100;
WaveOut.DeviceNumber = 0;
WaveOut.Init(WaveProvider);
WaveOut.Play();
}
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
public override void Render(AudioBuffer buffer)
{
WaveProvider.AddSamples(buffer.Data, buffer.Index, buffer.Length);
}
public override void Destroy()
{
if (WasapiOut != null)
{
WasapiOut.Stop();
WasapiOut.Dispose();
}
if (WaveOut != null)
{
WaveOut.Dispose();
}
}
}
Create a VideoRenderProvider
implementation
Writing a VideoRenderProvider requires implementing three methods (presented in the order they are
invoked):
Initialize Initializes the provider. Arguments to this method include details about the output clock rate.
GetControl Should return the video control.
Render Should render a video frame to the video control.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Destroy Allows you to release unmanaged resources and tie up any loose ends.
A VideoRenderProvider implementation could use any control that supports the display of images, from
the PictureBox control in .NET to the GLKView in iOS to the NSImageView in Mac OS X. For this
example, we'll use a PictureBox control. Remember to perform UI updates on the main thread!
public class PictureBoxVideoRenderProvider : VideoRenderProvider
{
private PictureBox PictureBox;
public PictureBoxVideoRenderProvider()
{
PictureBox = new PictureBox();
PictureBox.BackColor = Color.Black;
PictureBox.SizeMode = PictureBoxSizeMode.Zoom;
}
public override void Initialize(VideoRenderInitializeArgs renderArgs)
{ }
public override void Render(VideoBuffer frame)
{
RenderBitmap(ImageUtility.BufferToBitmap(frame));
}
public override void Destroy()
{ }
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
internal void RenderBitmap(Bitmap image)
{
if (image != null)
{
PictureBox.BeginInvoke(new Action(() =>
{
PictureBox.Image = image;
}));
}
}
public override object GetControl()
{
return PictureBox;
}
}
WebRTC Codec Registration
Out of the box, each WebRTC platform supports the following codecs:
Audio: PCMU and PCMA (G.711u and G.711a)
Video: Motion JPEG
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Adding additional codec support is easy.
1. Implement AudioCodec or VideoCodec. 2. Call AudioStream.RegisterCodec or VideoStream.RegisterCodec to register the codec.
When registering a codec, you need to specify the encoding name and a callback that will create an
instance of the codec when required. Optionally, you can set the preferred flag so the codec is
preferred over built-in codecs. The RTP payload type can be specified as well if the codec is registered
with the IANA. (Otherwise the payload type will be dynamically assigned.)
// For true WebRTC compatibility, we have to use VP8 as
// the preferred video codec. Unfortunately for .NET,
// VP8 is only available as a native library, and so
// we have to use a Visual C++ wrapper if we want VP8
// support. We've already done the work of wrapping it
// in the Win.VP8 project, so all that's left for us to
// do is register it as the preferred video codec.
VideoStream.RegisterCodec("VP8", () =>
{
return new Win.VP8.Codec();
}, true);
The VP8 video codec is a requirement for WebRTC compatibility, and for performance reasons, the codec
must run using native code. To provide you with flexibility in building your application, the core
Messenger libraries do not contain a hard link to VP8. Instead, each WebRTC example in the Messenger
SDK includes the VP8 codec and demonstrates how to use it (using VideoStream.RegisterCodec).
Configuration
Configuring your Messenger server is easy! You can set configuration options programatically or in
app.config.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
To configure Messenger programatically, simply assign values to the properties found
on Config.Current.Server (Eyeball.Messenger.Server).
To configure Messenger in app.config, create a new <messenger> section in app.config with
a <server> element.
<configuration>
<configSections>
<section name="messenger" type="Eyeball.Messenger.Server.Config, Eyeball.Messenger.Server" />
</configSections>
<!-- xmlns is optional -->
<messenger xmlns="http://schemas.eyeball.com/messenger">
<server />
</messenger>
</configuration>
Firewall
Allow incoming UDP traffic on port 3478.
<server> Attributes
Attribute Default Description
ipAddress IPAddress.Any The IP address to use when listening for client
STUN/TURN requests. If nothing is specified, the server
will listen on all IP addresses bound to the machine.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
logProviderType Eyeball.NullLogProvider The fully-qualified type name of the log provider for
TheRest.
When applicable, a logProviderSettings sub-element
defines additional properties specific to a particular log
provider.
port 3478 The port to use when listening for client STUN/TURN
requests.
Minimum value is 1024. Maximum value is 65535.
targetedScan auto Determines the nature of the automatic assembly scan at
startup that finds and registers server-side event code.
When set to auto, a smart algorithm is used that only
scans assemblies not belonging to the .NET framework.
When set to true, a strict algorithm is used that only
scans assemblies with the EventContainer assembly-
level attribute. When set to false, all assemblies are
scanned.
For optimal startup time, it is recommended to set this
value to true and add the EventContainer attribute to
all assemblies with server-side event code.
Sample App.config
This sample configuration uses the log4net log provider found in Eyeball.Log4Net.dll.
<?xml version="1.0"?>
<configuration>
<configSections>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
<section name="messenger" type="Eyeball.Messenger.Server.Config, Eyeball.Messenger.Server" />
<!-- For the log4net log provider -->
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<!-- xmlns is optional -->
<messenger xmlns="http://schemas.eyeball.com/messenger">
<server logProviderType="Eyeball.Log4Net.LogProvider" />
</messenger>
<!-- For the log4net log provider -->
<log4net debug="false">
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C:\\Logs\\Eyeball.Messenger.txt" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger %ndc - %message%newline" />
</layout>
</appender>
<root>
<level value="WARN" />
<appender-ref ref="LogFileAppender" />
</root>
</log4net>
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
</configuration>
Debugging
Debugging asynchronous Messenger applications isn't hard, but you need the right tools. It can also be
helpful to increase the connection's Timeout property before stepping into client and server events to
prevent timeouts from getting in the way.
Free IDEs are available for all the other platforms that can help you debug your code (Visual Studio,
Xcode, Eclipse, and many others). To inspect network traffic, we recommend Wireshark.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
No Audio? No Video?
A common problem on .NET/Windows is to have a connection establish, but with no video and possibly
with no audio as well. The solution to this problem is to make sure that the correct Visual C++
redistributable is installed. Both the VP8 and Opus codecs dip into unmanaged code, and without the
Visual C++ redistributable, they can silently fail.
Make sure you install the right version! If you are building your project using Visual Studio 2013, install
the Visual C++ 2013 redistributable.
Licensing
Edition Commercial
Use
Description
Community N Free for non-commercial use. WAN links are terminated after 30 seconds.
Enterprise Y Licensed for commercial applications.
Enterprise
OEM
Y Licensed for commercial applications and server distribution.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
All Messenger products are licensed per developer. A developer is defined as anyone who contributes to
the source code of a project that uses Messenger. Basically, if they need an IDE to contribute to the
project, they need a license for Messenger.
Additional details on the pricing model and licensing agreement are available upon request.
Messenger includes a copy of the following third-party libraries:
Apache log4net, distributed under the Apache license, used by the log provider found in
Eyeball.Log4Net.dll for outputting log statements. NAudio, distributed under the Microsoft Public License (Ms-PL), used by the .NET WebRTC library to
control the device microphone and speakers. AForge.NET, distributed under the GNU Lesser GPL License (LGPL), used by the .NET WebRTC library
to control the device camera.
Notes
Server-Side API for Visual Studio 2005/2008.
The server-side API makes use of optional arguments. If you are using Visual Studio 2005/2008 and don't
need to specify any of these arguments, you will have to pass in default values (eg. null) where
applicable.
MessengerServer.Start(null, 0);
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Converting Examples for Visual Studio 2005/2008
Our .NET examples make extensive use of the var keyword and lambda expressions for convenience.
You can easily convert to explicit typing and defined callbacks by simply changing var to the name of the
type and creating a new method with the contents of the lambda expression.
Consider this code snippet:
var conference = new Conference("serverAddress", videoStream);
conference.OnLinkInit += (e) =>
{
Console.WriteLine("Connecting to peer {0}...", e.PeerId);
};
Here is the equivalent code snippet using explicit typing and defined callbacks:
Conference conference = new Conference("serverAddress", videoStream);
conference.OnLinkInit += OnConferenceLinkInit;
...
private void OnConferenceLinkInit(LinkInitArgs e)
{
Console.WriteLine("Connecting to peer {0}...", e.PeerId);
}
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Converting Examples to Visual Basic
Our .NET examples are all written in C#. Since C# and VB are functionally equivalent, any of our
examples can also be modified to run using VB. There are several excellent tools available for converting
C# to Visual Basic automatically (here's a free online converter from Telerik), but to give you an idea, this
is what a CreateOffer snippet might look like in VB:
Dim connection = New Connection("192.168.0.1", 3478)
connection.CreateOffer(New CreateArgs(New () {New StreamDescription(StreamType.Video, New StreamFormat(100, "vp8"))}) With { _
.OnFailure = Function(e) Do
Console.WriteLine("Could not create offer (" + e.Exception.Message + ").")
End Function _
})
Server Requirements
Messenger server can run on even the most basic hardware. We offer server support for a wide range of
platforms. Messenger servers perform both public IP address discovery for clients as well as packet
relaying.
There are only two requirements for Messenger servers:
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
1. They must be publicly accessible for UDP traffic on port 3478. (Allow incoming UDP port 3478 in your
firewall.) 2. The must be edge devices, which means no routing or network address translation between
them and the public internet.
Java Applet
The Java applet creates multiple <applet> elements during normal use. To avoid unnecessary re-
downloading of the Java applet:
1. Make sure your webserver uses Content-Type: application/java-archive for the applet JAR file. Java will not cache applets if the MIME type is incorrect (i.e. Content-Type: application/octet-
stream). 2. Client-side caching should not be disabled on client machines. (It is enabled by default.)
Legal and Contact Information
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Confidential Information: This document contains confidential and proprietary information. The document has been provided to you in your capacity as a customer or evaluator of Eyeball Networks Inc.'s products. Unauthorized reproduction and distribution is prohibited unless specifically approved by Eyeball Networks Inc.
Copyright © 2002-2015 Eyeball Networks Inc. Patented and patents pending. All rights reserved.
Eyeball, Eyeball.com, its logos, AnyBandwidthTM and AnyFirewall™ are trademarks of Eyeball Networks Inc. All other referenced companies and product names may or may not be trademarks of their respective owners.
For more information visit Eyeball Networks Inc. at http://www.eyeball.com.
Department E-mail
Sales [email protected]
Technical Support [email protected]
Corporate Headquarters:
730 - 1201 West Pender Street
Vancouver, BC V6E 2V2
Canada
Tel. +1 604.921.5993
Fax +1 604.921.5909