Fork me on GitHub
No Matches
Lua plugin documentation

This is a plugin that implements a simple bridge to Lua scripts. While the plugin implements low level stuff like media manipulation, routing, recording, etc., all the logic is demanded to an external Lua script. This means that the C code exposes functions to the Lua script (e.g., to dictate what to do with media, whether recording should be done, sending PLIs, etc.), while Lua 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 Lua 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 Lua script can be completely customized, so that it fits whatever requirement one has (e.g., something like the EchoTest, or something like the VideoRoom).

Lua interfaces

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

  • init(): called when janus_lua.c is initialized;
  • destroy(): called when janus_lua.c is deinitialized (Janus shutting down);
  • createSession(): called when a new user attaches to the Janus Lua plugin;
  • destroySession(): called when an attached user detaches from the Janus Lua plugin;
  • querySession(): called when an Admin API query for a specific user gets to the Janus Lua plugin;
  • handleMessage(): called when a user sends a message to the Janus Lua 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 Lua 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.lua) to see the exact signature for all the above callbacks.

Notice that, along the above mentioned callbacks, Lua 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, Lua 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 Lua plugin will return its own info (i.e., "janus.plugin.lua", etc.). Most of the times, Lua scripts will not need to override this information, unless they really want to register their own name spaces and versioning. Lua 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, Lia 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 Lua script needs to expose callbacks that the C code can invoke, the C code exposes methods as Lua functions accessible from the Lua script. This includes means to push events, configure how media should be routed without handling each packet in Lua, 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;
  • endSession(): force the detach of a plugin handle;
  • 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 Lua 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 Lua's resumeScheduler(), will be clearer in the next section.

Lua/C coroutines scheduler

Lua 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 Lua 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 Lua coroutines. This means that, whenever this C scheduler is awaken, it will call the resumeScheduler() function in the Lua script, thus allowing the Lua script to execute one or more pending coroutines. The C scheduler only acts when triggered, which means it's up to the Lua 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 Lua script to implement asynchronous behaviour, e.g.:

  1. Lua script needs to do something asynchronously;
  2. Lua script creates coroutine, and takes note of it somewhere;
  3. Lua 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. Lua script resumes the previously queued coroutine.

This simple mechanism is what the sample Lua 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 Lua/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.

Lua/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 Lua 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 nil 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 three arguments)
-- Invoke test() in 500 milliseconds
timeCallback("test", nil, 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", nil, 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 Lua plugin API section for more information on how you can register your own C functions.