The gateway exposes both a pseudo-RESTful interface, and optionally a WebSocket interface as well, both of which based on JSON messages. These interfaces are described in more detail in the Plain HTTP REST Interface and WebSockets Interface documentation respectively, and both allow web applications to take advantage of the features provided by Janus and the functionality made available by its plugins. To make things easier for web developers, a JavaScript library (janus.js
) is available that can make use of both interfaces using exactly the same API. This library eases the task of creating sessions with the gateway, attaching WebRTC users to plugins, send and receive requests and events to the plugins themselves and so on. For real examples of how this library can be used, check the demos in the html folder of this package.
janus.js
library makes use of jQuery (http://jquery.com/) as a support. We're considering preparing versions that make use of different libraries as well (e.g., Prototype, Dojo, Script.aculo.us, etc.) in case your web application us based on something that cannot make use of jQuery. Of course, if you happen to prepare one yourself in the meanwhile don't hesitate and let us know! :-)In general, when using the gateway features, you would normally do the following:
The above steps will be presented in order, describing how you can use the low level API to accomplish them. Consider that in the future we might provide higher level wrappers to this API to address specific needs, e.g., a higher level API for each plugin: this would make it even easier to use the gateway features, as a high level API for the streaming plugin, for instance, may just ask you to provide the server address and the ID of the <video>
element to display the stream in, and would take care of all the above mentioned steps on your behalf. Needless to say, you're very welcome to provide wrapper APIs yourself, if you feel a sudden urge to do so! :-)
As a first step, you should include the janus.js
library in your project:
<script type="text/javascript" src="janus.js" ></script>
The core of the JavaScript API is the Janus
object. This object needs to be initialized the first time it is used in a page. This can be done using the static init
method of the object, which accepts the following options:
debug:
whether debug should be enabled on the JavaScript console (true/false, default=false)callback:
a user provided function that is invoked when the initialization is complete.Here's an example:
Janus.init({ debug: true, callback: function() { // Done! });
Once the library has been initialized, you can start creating sessions. Normally, each browser tab will need a single session with the gateway: in fact, each gateway session can contain several different plugin handles at the same time, meaning you can start several different WebRTC sessions with the same or different plugins for the same user using the same gateway session. That said, you're free to set up different gateway sessions in the same page, should you prefer so.
Creating a session is quite easy. You just need to use the new
constructor to create a new Janus
object that will handle your interaction with the gateway. Considering the dynamic and asynchronous nature of Janus sessions (events may occur at any time), there are several properties and callbacks you can configure when creating a session:
server:
the address of the gateway as a specific address (e.g., http://yourserver:8088/janus to use the plain HTTP API or ws://yourserver:8188/ for WebSockets) or as an array of addresses to try sequentially to allow automatic for fallback/failover during setup;iceServers:
a list of STUN/TURN servers to use (a default STUN server will be used if you skip this property);success:
the session was successfully created and is ready to be used;error:
the session was NOT successfully created;destroyed:
the session was destroyed and can't be used any more.These properties and callbacks are passed to the method as properties of a single parameter object: that is, the Janus
constructor takes a single parameter, which although acts as a container for all the available options. The success
callback is where you tipically start your application logic, e.g., attaching the peer to a plugin and start a media session.
Here's an example:
var janus = new Janus( { server: 'http://yourserver:8088/janus', success: function() { // Done! attach to plugin XYZ }, error: function(cause) { // Error, can't go on... }, destroyed: function() { // I should get rid of this } });
As anticipated, the server may be a specific address, e.g.:
var janus = new Janus( { server: 'http://yourserver:8088/janus', // or server: 'ws://yourserver:8188/', [..]
or an array of addresses. Such an array can be especially useful if you want the library to first check if the WebSockets server is reachable and, if not, fallback to plain HTTP, or just to provide a link multiple instances to try for failover. This is an example of how to pass a 'try websockets and fallback to HTTP' array:
var janus = new Janus( { server: ['ws://yourserver:8188/','http://yourserver:8088/janus'], [..]
Once created, this object represents your session with the gateway. you can interact with a Janus
object in several different ways. In particular, the following properties and methods are defined:
getServer()
: returns the address of the gateway;isConnected()
: returns true
if the Janus instance is connected to the gateway, false
otherwise;getSessionId()
: returns the unique gateway session identifier;attach(parameters)
: attaches the session to a plugin, creating an handle; more handles to the same or different plugins can be created at the same time;destroy(parameters)
: destroys the session with the gateway, and closes all the handles (and related PeerConnections) the session may have with any plugin as well.The most important property is obviously the attach()
method, as it's what will allow you to exploit the features of a plugin to manipulate the media sent and/or received by a PeerConnection in your web page. This method will create a plugin handle you can use for the purpose, for which you can configure properties and callbacks when calling the attach()
method itself. As for the Janus
constructor, the attach()
method takes a single parameter that can contain any of the following properties and callbacks:
plugin:
the unique package name of the plugin (e.g., janus.plugin.echotest
); will be used if you skip this property)success:
the handle was successfully created and is ready to be used;error:
the handle was NOT successfully created;consentDialog:
this callback is triggered just before getUserMedia
is called (parameter=true) and after it is completed (parameter=false); this means it can be used to modify the UI accordingly, e.g., to prompt the user about the need to accept the device access consent requests;onmessage:
a message/event has been received from the plugin;onlocalstream:
a local MediaStream
is available and ready to be displayed;onremotestream:
a remote MediaStream
is available and ready to be displayed;ondataopen:
a Data Channel is available and ready to be used;ondata:
data has been received through the Data Channel;oncleanup:
the WebRTC PeerConnection with the plugin was closed;detached:
the plugin handle has been detached by the plugin itself, and so should not be used anymore.Here's an example:
// Attach to echo test plugin, using the previously created janus instance janus.attach( { plugin: "janus.plugin.echotest", success: function(pluginHandle) { // Plugin attached! 'pluginHandle' is our handle }, error: function(cause) { // Couldn't attach to the plugin }, consentDialog: function(on) { // e.g., Darken the screen if on=true (getUserMedia incoming), restore it otherwise }, onmessage: function(msg, jsep) { // We got a message/event (msg) from the plugin // If jsep is not null, this involves a WebRTC negotiation }, onlocalstream: function(stream) { // We have a local stream (getUserMedia worked!) to display }, onremotestream: function(stream) { // We have a remote stream (working PeerConnection!) to display }, oncleanup: function() { // PeerConnection with the plugin closed, clean the UI // The plugin handle is still valid so we can create a new one }, detached: function() { // Connection with the plugin closed, get rid of its features // The plugin handle is not valid anymore } });
So the attach()
method allows you to attach to a plugin, and specify the callbacks to invoke when anything relevant happens in this interaction. To actively interact with the plugin, you can use the Handle
object that is returned by the success
callback (pluginHandle in the example).
This Handle
object has several methods you can use to interact with the plugin or check the state of the session handle:
getId()
: returns the unique handle identifier;getPlugin()
: returns the unique package name of the attached plugin;send(parameters)
: sends a message (with or without a jsep to negotiate a PeerConnection) to the plugin;createOffer(callbacks)
: asks the library to create a WebRTC compliant OFFER;createAnswer(callbacks)
: asks the library to create a WebRTC compliant ANSWER;handleRemoteJsep(callbacks)
: asks the library to handle an incoming WebRTC compliant session description;dtmf(parameters)
: sends a DTMF tone on the PeerConnection;data(parameters)
: sends data through the Data Channel, if available;getBitrate()
: gets a verbose description of the currently received stream bitrate (only available on Chrome, for now);hangup()
: tells the library to close the PeerConnection;detach(parameters)
: detaches from the plugin and destroys the handle, tearing down the related PeerConnection if it exists.While the Handle
API may look complex, it's actually quite straightforward once you get the concept. The only step that may require a little more effort to understand is the PeerConnection negotiation, but again, if you're familiar with the WebRTC API, the Handle
actually makes it a lot easier.
The idea behind it's usage is the following:
attach()
to create a Handle
object;success
callback, your application logic can kick in: you may want to send a message to the plugin ( send(msg)
), negotiate a PeerConnection with the plugin right away ( createOffer
followed by a send(msg, jsep)
) or wait for something to happen to do anything;onmessage
callback tells you when you've got messages from the plugin; if the jsep
parameter is not null, just pass it to the library, which will take care of it for you; if it's an OFFER use createAnswer
(followed by a send(msg, jsep)
to close the loop with the plugin), otherwise use handleRemoteJsep
;onlocalstream
and/or the onremotestream
callbacks will provide you with a stream you can display in your page;send
method and onmessage
callback will allow you to handle this interaction (e.g., to tell the plugin to mute your stream, or to be notified about someone joining a virtual room), while the ondata
callback is triggered whenever data is received on the Data Channel, if available (and the ondataopen
callback will tell you when a Data Channel is actually available).The following paragraphs will delve a bit deeper in the negotiation mechanism provided by the Handle
API, in particular describing the properties and callbacks that may be involved. To follow the approach outlined by the W3C WebRTC API, this negotiation mechanism is heavily based on asynchronous methods as well.
createOffer
takes a single parameter, that can contain any of the following properties and callbacks:media:
you can use this property to tell the library which media (audio/video/data) you're interested in, and whether you're going to send and/or receive any of them; by default audio and video are enabled in both directions, while the Data Channels are disabled; this option is an object that can take any of the following properties:audioSend:
true/false
(do or do not send audio);audioRecv:
true/false
(do or do not receive audio);audio:
true/false
(do or do not send and receive audio, takes precedence on the above);videoSend:
true/false
(do or do not send video);videoRecv:
true/false
(do or do not receive video);video:
true/false
(do or do not send and receive video, takes precedence on the above);video:
"lowres"/"lowres-16:9"/"stdres"/"stdres-16:9"/"hires"/"hires-16:9"
(send a 320x240/320x180/640x480/640x360/1280x720 video, takes precedence on the above; default is "stdres"
) this property will affect the resulting getUserMedia that the library will issue; please notice that Firefox doesn't support the "16:9"
variants, which will fallback to the ones; besides, "hires"
and "hires-16:9"
are currently synonymous, as there's no 4:3 high resolution constraint as of now;video:
"screen"
(use screensharing for video, disables audio, takes precedence on both audio and video);data:
true/false
(do or do not use Data Channels, default is false)trickle:
true/false
, to tell the library whether you want Trickle ICE to be used (true, the default) or not (false);success:
the session description was created (attached as a parameter) and is ready to be sent to the plugin;error:
the session description was NOT successfully created;createAnswer
takes the same options as createOffer, but requires an additional one as part of the single parameter argument:jsep:
the session description sent by the plugin (e.g., as received in an onmessage
callback) as its OFFER.Whether you use createOffer
or createAnswer
depending on the scenario, you should end up with a valid jsep
object returned in the success
callback. You can attach this jsep
object to a message in a send
request to pass it to the plugin, and have the gateway negotiate a PeerConnection with your application.
Here's an example of how to use createOffer
, taken from the Echo Test demo page:
// Attach to echo test plugin janus.attach( { plugin: "janus.plugin.echotest", success: function(pluginHandle) { // Negotiate WebRTC echotest = pluginHandle; var body = { "audio": true, "video": true }; echotest.send({"message": body}); echotest.createOffer( { // No media property provided: by default, // it's sendrecv for audio and video success: function(jsep) { // Got our SDP! Send our OFFER to the plugin echotest.send({"message": body, "jsep": jsep}); }, error: function(error) { // An error occurred... } }); }, [..] onmessage: function(msg, jsep) { // Handle msg, if needed, and check jsep if(jsep !== undefined && jsep !== null) { // We have the ANSWER from the plugin echotest.handleRemoteJsep({jsep: jsep}); } }, [..] onlocalstream: function(stream) { // Invoked after createOffer // This is our video }, onremotestream: function(stream) { // Invoked after handleRemoteJsep has got us a PeerConnection // This is the remote video }, [..]
This, instead, is an example of how to use createAnswer
, taken from the Streaming demo page:
// Attach to echo test plugin janus.attach( { plugin: "janus.plugin.streaming", success: function(pluginHandle) { // Handle created streaming = pluginHandle; [..] }, [..] onmessage: function(msg, jsep) { // Handle msg, if needed, and check jsep if(jsep !== undefined && jsep !== null) { // We have an OFFER from the plugin streaming.createAnswer( { // We attach the remote OFFER jsep: jsep, // We want recvonly audio/video media: { audioSend: false, videoSend: false }, success: function(ourjsep) { // Got our SDP! Send our ANSWER to the plugin var body = { "request": "start" }; streaming.send({"message": body, "jsep": ourjsep}); }, error: function(error) { // An error occurred... } }); } }, [..] onlocalstream: function(stream) { // This will NOT be invoked, we chose recvonly }, onremotestream: function(stream) { // Invoked after send has got us a PeerConnection // This is the remote video }, [..]
Of course, these are just a couple of examples where the scenarios assumed that one plugin would only receive (Echo Test) or generate (Streaming) offers. A more complex example (e.g., a Video Call plugin) would involve both, allowing you to either send offers to a plugin, or receive some from them. Handling this is just a matter of checking the type
of the jsep
object and reacting accordingly.
This is it! For more information about the API, have a look at the demo pages that are available in the html folder in this package.