Fork me on GitHub
No Matches
JavaScript (Duktape) plugin documentation

This is a plugin that implements a simple bridge to JavaScript via Duktape. While the plugin implements low level stuff like media manipulation, routing, recording, etc., all the logic is demanded to an external JavaScript script. This means that the C code exposes functions to the JavaScript script (e.g., to dictate what to do with media, whether recording should be done, sending PLIs, etc.), while JavaScript exposes functions to be notified by the C code about important events (e.g., new users, WebRTC state, incoming messages, etc.).

Considering the C code and the JavaScript script will need some sort of "contract" in order to be able to properly interact with each other, the interface (as in method names) must be consistent, but the logic in the JavaScript script can be completely customized, so that it fits whatever requirement one has (e.g., something like the EchoTest, or something like the VideoRoom).

JavaScript interfaces

Every JavaScript script that wants to implement a Janus plugin must provide the following functions as callbacks:

  • init(): called when janus_duktape.c is initialized;
  • destroy(): called when janus_duktape.c is deinitialized (Janus shutting down);
  • createSession(): called when a new user attaches to the Janus Duktape plugin;
  • destroySession(): called when an attached user detaches from the Janus Duktape plugin;
  • querySession(): called when an Admin API query for a specific user gets to the Janus Duktape plugin;
  • handleMessage(): called when a user sends a message to the Janus Duktape plugin;
  • setupMedia(): called when a users's WebRTC PeerConnection goes up;
  • hangupMedia(): called when a users's WebRTC PeerConnection goes down;
  • resumeScheduler(): called by the C scheduler to resume coroutines.

While init() expects a path to a config file (which you can ignore if unneeded), and destroy() and resumeScheduler() don't need any argument, all other functions expect at the very least a numeric session identifier, that will uniquely address a user in the plugin. Such a value is created dynamically by the C code, and so all the JavaScript script needs to do is track it as a unique session identifier when handling requests and pushing responses/events/actions towards the C code. Refer to the existing examples (e.g., echotest.js) to see the exact signature for all the above callbacks.

Notice that, along the above mentioned callbacks, JavaScript scripts can also implement functions like incomingRtp() incomingRtcp() incomingTextData() and incomingBinaryData() to handle those packets directly, instead of letting the C code worry about relaying/processing them. While it might make sense to handle incoming data channel messages with incomingTextData() or incomingBinaryData though, the performance impact of directly processing and manipulating RTP an RTCP packets is probably too high, and so their usage is currently discouraged. The dataReady() callback can be used to figure out when data can be sent. As an additional note, JavaScript scripts can also decide to implement the functions that return information about the plugin itself, namely getVersion() getVersionString() getDescription() getName() getAuthor() and getPackage(). If not implemented, the JavaScript plugin will return its own info (i.e., "janus.plugin.javascript", etc.). Most of the times, JavaScript scripts will not need to override this information, unless they really want to register their own name spaces and versioning. JavaScript scripts can also receive information on slow links via the slowLink() callback, in order to react accordingly: e.g., reduce the bitrate of a video sender if they, or their viewers, are experiencing issues. Finally, in case simulcast is used, JavaScript scripts may receive events on substream and/or temporal layer changes happening for receiving sessions via the substreamChanged() and the temporalLayerChanged() callbacks: this may be useful to track which layer is actually being sent, vs. what was requested.

C interfaces

Just as the JavaScript script needs to expose callbacks that the C code can invoke, the C code exposes methods as JavaScript functions accessible from the JavaScript script. This includes means to push events, configure how media should be routed without handling each packet in JavaScript, sending RTCP feedback, start/stop recording and so on.

The following are the functions the C code exposes:

  • pushEvent(): push an event to the user via Janus API;
  • eventsIsEnabled(): check if Event Handlers are enabled in the core;
  • notifyEvent(): send an event to Event Handlers;
  • closePc(): force the closure of a PeerConnection;
  • configureMedium(): specify whether audio/video/data can be received/sent;
  • addRecipient(): specify which user should receive a user's media;
  • removeRecipient(): specify which user should not receive a user's media anymore;
  • setBitrate(): specify the bitrate to force on a user via REMB feedback;
  • setPliFreq(): specify how often the plugin should send a PLI to this user;
  • setSubstream(): set the target simulcast substream;
  • setTemporalLayer(): set the target simulcast temporal layer;
  • sendPli(): send a PLI (keyframe request);
  • startRecording(): start recording audio, video and or data for a user;
  • stopRecording(): start recording audio, video and or data for a user;
  • pokeScheduler(): notify the C code that there's a coroutine to resume;
  • timeCallback(): trigger the execution of a JavaScript function after X milliseconds.

As anticipated in the previous section, almost all these methods also expect the unique session identifier to address a specific user in the plugin. This is true for all the above methods expect eventsIsEnabled and, more importantly, both timeCallback() and pokeScheduler() which, together with JavaScript's resumeScheduler(), will be clearer in the next section.

JavaScript/C coroutines scheduler

Duktape is a single threaded environment. While it has a concept similar to threads called coroutines, these are not threads as known in C. In order to allow for an easy to implement asynchronous behaviour in JavaScript scripts, you can leverage a scheduler implemented in the C code.

More specifically, when the plugin starts a dedicated thread is devoted to the only purpose of acting as a scheduler for JavaScript coroutines. This means that, whenever this C scheduler is awaken, it will call the resumeScheduler() function in the JavaScript script, thus allowing the JavaScript script to execute one or more pending coroutines. The C scheduler only acts when triggered, which means it's up to the JavaScript script to tell it when to wake up: this is possible via the pokeScheduler() function, which does nothing more than sending a simple signal to the C scheduler to wake it up. As such, it's easy for the JavaScript script to implement asynchronous behaviour, e.g.:

  1. JavaScript script needs to do something asynchronously;
  2. JavaScript script creates coroutine, and takes note of it somewhere;
  3. JavaScript script calls pokeScheduler();
  4. C code sends signal to the thread acting as a scheduler;
  5. when the scheduling thread wakes up, it calls resumeScheduler();
  6. JavaScript script resumes the previously queued coroutine.

This simple mechanism is what the sample JavaScript scripts provided in this repo use, for instance, to handle incoming messages asynchronously, so you can refer to those to have an idea of how it can be used. The next section will address JavaScript/C time-based scheduler instead.

You can implement asynchronous behaviour any way you want, and you're not required to use this C scheduler. Anyway, you must implement a method called resumeScheduler() anyway, as the C code checks for its presence and fails if it's not there. If you don't need it, just create an empty function that does nothing and you'll be fine.

JavaScript/C time-based scheduler

Another helpful way to implement asynchronous behaviour is with the help of the timeCallback() function. Specifically, this function implements a mechanism to ask for a specific JavaScript method to be invoked after a provided amount of time. To specify the function to invoke, an optional argument to pass (which MUST be a string) and the time to wait to do that. This is particularly helpful when you're handling asynchronous behaviour that you want to inspect on a regular basis.

The timeCallback() function expects three arguments:

timeCallback(function, argument, milliseconds);

The only mandatory parameter is function: if you set argument to null no argument will be passed to function when it's executed; it milliseconds is 0, function will be executed as soon as possible.

// This will cause an error (timeCallback needs a function)
// Invoke test() in 500 milliseconds
timeCallback("test", null, 500);
// Invoke test("ciccio") in 2 seconds
timeCallback("test", "ciccio", 2000);

Notice that timeCallback() allows you to formally recreate the mechanism pokeScheduler() and resumeScheduler() implement, as the following is pretty much an equivalent of that:

timeCallback("resumeScheduler", null, 0);

Anyway, pokeScheduler() and resumeScheduler() is much more compact and less verbose, and as such is preferred in cases where timing and opaque arguments are not needed.

Refer to the Duktape plugin API section for more information on how you can register your own C functions.