Fork me on GitHub
JavaScript API

Janus exposes, assuming the HTTP transport has been compiled, a pseudo-RESTful interface, and optionally also WebSocket/RabbitMQ/MQTT/Nanomsg/UnixSockets interfaces as well, all of which based on JSON messages. These interfaces are described in more detail in the Plain HTTP REST Interface WebSockets Interface RabbitMQ interface MQTT interface Nanomsg interface and UnixSockets interface documentation respectively, and all allow clients to take advantage of the features provided by Janus and the functionality made available by its plugins. Considering most clients will be web browsers, a common choice will be to rely on either the REST or the WebSockets interface for the purpose. 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 Janus core, 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. Notice that the janus.js library makes use of the features made available by the webrtc-adapter shim, which means that your web application should always include it as a dependency. For instance, all the demos link to it externally via cdnjs.com.

Note
The current janus.js library allows you to provide custom implementations of certain dependencies, in order to make it easier to integrate with other JavaScript libraries and frameworks. Using this feature you can ensure janus.js does not (implicitly) depend on certain global variables. Two implementations are included in janus.js itself:
  1. Janus.useDefaultDependencies which relies on native browser APIs, which in turn require somewhat more modern browsers
  2. Janus.useOldDependencies which uses jQuery (http://jquery.com/) instead, and should provide equivalent behaviour to previous versions of janus.js

By default Janus.useDefaultDependencies will be used, but you can override this when initialising the Janus library and pass a custom dependencies object instead. For details, refer to: Working with custom janus.js dependencies

In general, when using the Janus features, you would normally do the following:

  1. include the Janus JavaScript library in your web page;
  2. initialize the Janus JavaScript library and (optionally) passing its dependencies;
  3. connect to the server and create a session;
  4. create one or more handles to attach to a plugin (e.g., echo test and/or streaming);
  5. interact with the plugin (sending/receiving messages, negotiating a PeerConnection);
  6. eventually, close all the handles and shutdown the related PeerConnections;
  7. destroy the session.

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 server 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! :-)

Using janus.js

As a first step, you should include the Janus library in your project. Depending on your needs you can either use janus.js or one of the generated JavaScript module variants of it. For available module syntaxes and how to build the corresponding variants, see: Using janus.js as JavaScript module

<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, and what levels
    • true or "all": all debuggers enabled (Janus.trace, Janus.debug, Janus.log, Janus.warn, Janus.error)
    • array (e.g., ["trace", "warn"]): only enable selected debuggers (allowed tokens: trace, debug, log, warn, error)
    • false: disable all debuggers
  • callback: a user provided function that is invoked when the initialization is complete
  • dependencies: a user provided implementation of Janus library dependencies

Here's an example:

Janus.init({
   debug: true,
   dependencies: Janus.useDefaultDependencies(), // or: Janus.useOldDependencies() to get the behaviour of previous Janus versions
   callback: function() {
           // Done!
   }
});
Note
When using one of the JavaScript module variants of janus.js, you will need to import the Janus symbol from the module first. See also: Using janus.js as JavaScript module For example, using the ECMAScript module variant, the above example should be altered to:
import * as Janus from './janus.es.js'

Janus.init({
   debug: true,
   dependencies: Janus.useDefaultDependencies(), // or: Janus.useOldDependencies() to get the behaviour of previous Janus versions
   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 server: in fact, each Janus 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 Janus session. That said, you're free to set up different Janus 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 server. 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 server 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);
  • withCredentials: whether the withCredentials property of XHR requests should be enabled or not (false by default, and only valid when using HTTP as a transport, ignored for WebSockets);
  • max_poll_events: the number of events that should be returned when polling; the default is 1 (polling returns an object), passing a higher number will have the backend return an array of objects instead (again, only valid for HTTP usage as this is strictly related to long polling, ignored for WebSockets);
  • destroyOnUnload: whether we should destroy automatically try and destroy this session via Janus API when onbeforeunload is called (true by default);
  • token , apisecret: optional parameters only needed in case you're Authenticating the Janus API ;
  • a set of callbacks to be notified about events, namely:
    • 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 typically 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 server. 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 server;
  • isConnected(): returns true if the Janus instance is connected to the server, false otherwise;
  • getSessionId(): returns the unique Janus session identifier;
  • attach(parameters): attaches the session to a plugin, creating a handle; more handles to the same or different plugins can be created at the same time;
  • destroy(parameters): destroys the session with the server, 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 );
  • opaqueId: an optional opaque string meaningful to your application (e.g., to map all the handles of the same user);
  • a set of callbacks to be notified about events, namely:
    • 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;
    • webrtcState: this callback is triggered with a true value when the PeerConnection associated to a handle becomes active (so ICE, DTLS and everything else succeeded) from the Janus perspective, while false is triggered when the PeerConnection goes down instead; useful to figure out when WebRTC is actually up and running between you and Janus (e.g., to notify a user they're actually now active in a conference); notice that in case of false a reason string may be present as an optional parameter;
    • iceState: this callback is triggered when the ICE state for the PeerConnection associated to the handle changes: the argument of the callback is the new state as a string (e.g., "connected" or "failed");
    • mediaState: this callback is triggered when Janus starts or stops receiving your media: for instance, a mediaState with mid=0, type=audio and on=true means Janus started receiving the audio stream identified by mid b in the offer/answer exchange and transceivers (or started getting them again after a pause of more than a second); a mediaState with type=video and on=false means Janus hasn't received any video from you in the last second, after a start was detected before; useful to figure out when Janus actually started handling your media, or to detect problems on the media path (e.g., media never started, or stopped at some time);
    • slowLink: this callback is triggered when Janus reports trouble either sending or receiving media on the specified PeerConnection, typically as a consequence of too many NACKs received from/sent to the user in the last second: for instance, a slowLink with uplink=true means you notified several missing packets from Janus, while uplink=false means Janus is not receiving all your packets; useful to figure out when there are problems on the media path (e.g., excessive loss), in order to possibly react accordingly (e.g., decrease the bitrate if most of our packets are getting lost);
    • onmessage: a message/event has been received from the plugin;
    • onlocaltrack: a local MediaStreamTrack is available and ready to be displayed;
    • onremotetrack: a remote MediaStreamTrack 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
        },
        onlocaltrack: function(track, added) {
            // A local track to display has just been added (getUserMedia worked!) or removed
        },
        onremotetrack: function(track, mid, added) {
            // A remote track (working PeerConnection!) with a specific mid has just been added or removed
        },
        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;
  • replaceTracks(tracks, callbacks): asks the library to replace local tracks without renegotiating (no other offer/answer);
  • dtmf(parameters): sends a DTMF tone on the PeerConnection;
  • data(parameters): sends data through the Data Channel, if available;
  • getBitrate(mid): gets a verbose description of the currently received video stream bitrate (optional mid to specify the stream, first video stream if missing);
  • getLocalTracks(): returns an array of the tracks that are currently being sent on this PeerConnection, as basic objects including, e.g., type, mid and label of the track;
  • getRemoteTracks(): returns an array of the tracks that are currently being received on this PeerConnection, as basic objects including, e.g., type, mid and label of the track;
  • muteAudio , unmuteAudio , isAudioMuted , muteVideo , unmuteVideo , isVideoMuted: a set of helper functions to mute, unmute or check the muted status of a specific audio or video track that is currently being sent (optional mid to specify the stream, first audio/video stream if missing);
  • getLocalVolume , getRemoteVolume: a couple of helper functions to get the current volume of the local or remote track; notice that this isn't supported on Firefox at the moment (optional mid to specify the stream, first audio stream if missing);
  • hangup(sendRequest): tells the library to close the PeerConnection; if the optional sendRequest argument is set to true, then a hangup Janus API request is sent to Janus as well (disabled by default, Janus can usually figure this out via DTLS alerts and the like but it may be useful to enable it sometimes);
  • 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:

  1. you use attach() to create a Handle object;
  2. in the 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;
  3. the 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 ;
  4. whether you took the initiative to set up a PeerConnection or the plugin did, the onlocaltrack and/or the onremotetrack callbacks will provide you with info on media tracks you can display or play in your page;
  5. each plugin may allow you to manipulate what should flow through the PeerConnection channel: the 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. Notice that the following paragraphs address the first negotiation step, that is the one to create a new PeerConnection from scratch: to know how to originate or handle a renegotiation instead (e.g., to add/remove/replace a media source, or force an ICE restart) check the Updating an existing PeerConnection (with or without renegotiations) section instead.

  • createOffer takes a single parameter, that can contain any of the following properties and callbacks:
    • tracks: 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 no device is captured, and Data Channels are disabled as well; incoming audio and video is instead autoaccepted unless you tell the library otherwose; the same property can also be used to update sessions (e.g., to add/remove/replace tracks); this option is an array of objects, where each object can take any of the following properties:
      • type: mandatory, must be one of "audio", "video", "screen" and "data";
      • mid: to address existing tracks (e.g., when answering or updating sessions), the mid property specifies that the info in the related track object are specific to that target; notice that this is ignored in a first createOffer , as mid values are created by the browser automatically and cannot be forced;
      • capture: in case something must be captured (e.g., a microphone for "audio" or a "webcam" for video), the capture property can be used to dictate what and how; passing true asks for the default device, but getUserMedia (for audio/video) or getDisplayMedia (for screen sharing) constraints can be passed as well as objects, and in case those will be used instead; passing a MediaStreamTrack instance will tell the library not to capture anything, but use the provided track as a source instead;
      • simulcast: true/false, for video, whether simulcast should be used for this track;
      • svc: for video, the scalability mode to enable, in case SVC needs to be used for this track; notice that SVC support is experimental in Janus, and not fully supported in all browsers either;
      • recv: true/false , whether audio or video should be received as well; in case capture is set but recv is false, then this means you're asking for a "sendonly" track;
      • add: true/false , whether a new track should be added (default is true for offers);
      • replace: true/false , whether a provided capture should replace what is being captured for the specified track;
      • remove: true/false , whether the local track that's currently captured should be removed, meaning nothing will be sent for that specific stream;
      • dontStop: true/false , whether a track that is being added right now should NOT be stopped when the track is removed; this is helpful whenever, for instance, an external track is being provided to capture , and the application wants to keep control on the life cycle of the MediaStreamTrack instance;
      • transforms: in case Insertable Streams need to be used (e.g., for end-to-end encryption), the sender and/or receiver transform functions for this track can be provided in the transforms property;
    • trickle: true/false, to tell the library whether you want Trickle ICE to be used (true, the default) or not (false);
    • a set of callbacks to be notified about the result, namely:
      • 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;
      • customizeSdp: you can modify the sdp generated by the webrtc engine if you need;
  • 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 Janus 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;
            echotest.createOffer(
                {
                    // We want bidirectional audio and video, plus data channels
                    tracks: [
                        { type: 'audio', capture: true, recv: true },
                        { type: 'video', capture: true, recv: true },
                        { type: 'data' },
                    ],
                    success: function(jsep) {
                        // Got our SDP! Send our OFFER to the plugin
                        echotest.send({ message: body, jsep: jsep });
                    },
                    error: function(error) {
                        // An error occurred...
                    },
                    customizeSdp: function(jsep) {
                        // if you want to modify the original sdp, do as the following
                        // oldSdp = jsep.sdp;
                        // jsep.sdp = yourNewSdp;
                    }
                });
        },
        [..]
        onmessage: function(msg, jsep) {
            // Handle msg, if needed, and check jsep
            if(jsep) {
                // We have the ANSWER from the plugin
                echotest.handleRemoteJsep({jsep: jsep});
            }
        },
        [..]
        onlocaltrack: function(track, added) {
            // Invoked after createOffer
            // This is info on a local track: when added, we can choose to render
        },
        onremotetrack: function(track, mid, added) {
            // Invoked after handleRemoteJsep has got us a PeerConnection
            // This is info on a remote track: when added, we can choose to render
        },
        [..]

This, instead, is an example of how to use createAnswer, taken from the Streaming demo page:

// Attach to the Streaming 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) {
                // We have an OFFER from the plugin
                streaming.createAnswer(
                    {
                        // We attach the remote OFFER
                        jsep: jsep,
                        // We only specify data channels here, as this way in
                        // case they were offered we'll enable them. Since we
                        // don't mention audio or video tracks, we autoaccept them
                        // as recvonly (since we won't capture anything ourselves)
                        tracks: [
                            { type: 'data' }
                        ],
                        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...
                        }
                    });
            }
        },
        [..]
        onlocaltrack: function(track, added) {
            // This will NOT be invoked, we chose recvonly
        },
        onremotetrack: function(track, mid, added) {
            // Invoked after send has got us a PeerConnection
            // This is info on a remote track: when added, we can choose to render
        },
        [..]

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 call using the Video Call, SIP or NoSIP plugin, or a videoconfecence using the Video Room) 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.

Updating an existing PeerConnection (with or without renegotiations)

While the JavaScript APIs described above will suffice for most of the common scenarios, there are cases when updates on a PeerConnection may be needed. This can happen whenever, for instance, you want to add a new media source (e.g., add video to an audio only call), replace an existing one (e.g., switch from capturing the camera to sharing your screen), or trigger an ICE restart because of a network change. While adding or removing tracks requires a renegotiation, which means a new SDP offer/answer round to update the existing PeerConnection, just replacing a track can be done without any further SDP exchange. In both cases, the tracks property introduced before can be used to update a session.

To just replace an existing track (e.g., changing the camera used to capture the video we're sending) the replaceTracks handle method can be used. Using it is quite trivial, since you simply need to provide a list of tracks you want to update, identify them somehow (e.g., via their mid ), and provide the new capture property. A simple example that shows how to replace a video track with a specific camera identified by its deviceId is the following:

echotest.replaceTracks([
    {
        type: 'video',
        mid: '1',    // We assume mid 1 is video
        capture: { deviceId: { exact: videoDeviceId } }
    }
]);

In case a new track needs to be added to a session, or an existing track be removed, instead, a renegotiation is indeed needed, because it requires changes to be signalled to the other party. In that case, a renegotiation happens exactly as new sessions do, meaning that you either perform a new createOffer followed by a handleRemoteJsep with the updated answer, or you handle an incoming updated offer and provide an updated answer via createAnswer : the library will automatically understand from context if you're creating a new session or updating an existing one.

When updating a session to add or remove tracks, you need to provide an updated tracks property as well. It's important to only include in this tracks property a list of tracks you want to change: omitting existing tracks will result in the library not making any change to them. This means that a createOffer without a tracks property, for instance, will simply generate a new offer without applying any change (which, as we'll see later, can be useful in some cases, e.g., ICE restarts).

Depending on what you want to update, you'll need to set one of the following properties to true in the related track object:

  • add: adds a new track to the session;
  • replace: replaces an existing track in the session (similar to what replaceTracks does, but within the context of a renegotiation);
  • remove: removes an existing track from a session (the direction of the related m-line will be changed to either "recvonly" or "inactive", depending on whether there's incoming media as well or not).

Notice that these properties are only processed when you're trying a renegotiation, and will be ignored when creating a new PeerConnection. These properties don't replace the existing track properties, but go along with them. For instance, when adding a new video stream, or replacing an existing one, you can still use the video related properties as before, e.g., to pass a specific device ID or asking for a screenshare instead of a camera. As anticipated, omitting info on existing tracks will leave them untouched.

It's important to point out that, as for negotiations that result in the creation of a new PeerConnection in the first place, how to perform a renegotiation in practice will typically vary depending on the plugin that you're trying to do it for. Some plugins may allow you to offer a renegotiation, others may require you to send a different request instead in order to trigger a renegotiation from the plugin. As it will be clearer later, this is especially true for ICE restarts. As such, apart from the generic and core-related definitions introduced in this section, please refer to the documentation for each individual plugin for more information about how to perform renegotiations in specific use cases.

Here's a simple example of how you can remove the local video capture in a session, e.g., in the EchoTest demo:

// Remove local video
echotest.createOffer(
    {
        tracks: [{ type: 'video', mid: '1', remove: true }],
        success: function(jsep) {
            echotest.send({ message: { video: true }, jsep: jsep })
        }
        error: function(error) {
            bootbox.alert('WebRTC error... ' + error.message);
        }
    });

This other example shows how you can add a new video stream, e.g., a screen share, to an existing PeerConnection instead:

// Add local video
echotest.createOffer(
    {
        tracks: [{ type: 'screen', add: true, capture: true }],
        success: function(jsep) {
            echotest.send({ message: { video: true }, jsep: jsep })
        }
        error: function(error) {
            bootbox.alert('WebRTC error... ' + error.message);
        }
    });

Notice that renegotiations involving media changes (both local and remote) will likely result in new calls to the onlocaltrack and onremotetrack application callbacks: as such, be prepared to see those callbacks called for the same PeerConnection more than once during the course of a media session.

ICE restarts

While ICE restarts can be achieved with a renegotiation, they're complex enough to deserve a specific subsection. In fact, ICE restarts don't address changes in the media, but in the underlying transport itself. They're used, for instance, when there's a network change (e.g., the IP address changed, or the user switched from WiFi to 4G). In order for this to work, new candidates must be exchanged, and connectivity checks must be restarted in order to find the new optimal path.

With janus.js, you can only force an ICE restart when sending a new offer. In order to do so, all you need to do is add iceRestart :true to your createOffer call, and an ICE restart will be requested. The following example shows how this can be done with the EchoTest:

echotest.createOffer(
    {
        iceRestart: true,
        success: function(jsep) {
            echotest.send({ message: { audio: true, video: true }, jsep: jsep});
        }
    });

Notice how, in this particular example, we're not asking for any change on the media streams, but just an ICE restart, which is why we don't provide a tracks property at all: as a consequence, the library won't make any changes to the existing streams, but only generate a new offer. If successful, as soon as the answer is received, the client and Janus will restart the ICE process and find a new path for the media packets.

Notice that, with Janus and its plugins, you won't always be able to force an ICE restart by sending a new SDP offer yourself: some plugins, like the Streaming plugin for instance, will want to always send an offer themselves, which means they'll be the ones actually forcing the ICE restart from a negotiation perspective. In order to still allow users to actually originate the process, all the stock Janus plugins that assume they'll be sending offers for some or all of their media streams also expose APIs to force an ICE restart from the server side. You can learn more about this on a plugin level basis here and here. Besides, make sure you read the documentation for each of the plugins you're interested in using ICE restarts for, as the details for how to perform it properly are typically provided there.


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.