This is a plugin implementing an audio conference bridge for Janus, specifically mixing Opus streams. This means that it replies by providing in the SDP only support for Opus, and disabling video. Opus encoding and decoding is implemented using libopus (http://opus.codec.org). The plugin provides an API to allow peers to join and leave conference rooms. Peers can then mute/unmute themselves by sending specific messages to the plugin: any way a peer mutes/unmutes, an event is triggered to the other participants, so that it can be rendered in the UI accordingly.
Rooms to make available are listed in the plugin configuration file. A pre-filled configuration file is provided in conf/janus.plugin.audiobridge.jcfg
and includes a demo room for testing.
To add more rooms or modify the existing one, you can use the following syntax:
room-<unique room ID>: { description = This is my awesome room is_private = true|false (private rooms don't appear when you do a 'list' request) secret = <optional password needed for manipulating (e.g. destroying) the room> pin = <optional password needed for joining the room> sampling_rate = <sampling rate> (e.g., 16000 for wideband mixing) spatial_audio = true|false (if true, the mix will be stereo to spatially place users, default=false) audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must be negotiated/used or not for new joins, default=true) audiolevel_event = true|false (whether to emit event to other users or not, default=false) audio_active_packets = 100 (number of packets with audio level, default=100, 2 seconds) audio_level_average = 25 (average value of audio level, 127=muted, 0='too loud', default=25) default_expectedloss = percent of packets we expect participants may miss, to help with FEC (default=0, max=20; automatically used for forwarders too) default_bitrate = default bitrate in bps to use for the all participants (default=0, which means libopus decides; automatically used for forwarders too) denoise = true|false (whether denoising via RNNoise should be performed for each participant by default) record = true|false (whether this room should be recorded, default=false) record_file = /path/to/recording.wav (where to save the recording) record_dir = /path/to/ (path to save the recording to, makes record_file a relative path if provided) mjrs = true|false (whether all participants in the room should be individually recorded to mjr files, default=false) mjrs_dir = "/path/to/" (path to save the mjr files to) allow_rtp_participants = true|false (whether participants should be allowed to join via plain RTP as well, rather than just WebRTC, default=false) groups = optional, non-hierarchical, array of groups to tag participants, for external forwarding purposes only [The following lines are only needed if you want the mixed audio to be automatically forwarded via plain RTP to an external component (e.g., an ffmpeg script, or a gstreamer pipeline) for processing. By default plain RTP is used, SRTP must be configured if needed] rtp_forward_id = numeric RTP forwarder ID for referencing it via API (optional: random ID used if missing) rtp_forward_host = host address to forward RTP packets of mixed audio to rtp_forward_host_family = ipv4|ipv6; by default, first family returned by DNS request rtp_forward_port = port to forward RTP packets of mixed audio to rtp_forward_ssrc = SSRC to use to use when streaming (optional: stream_id used if missing) rtp_forward_codec = opus (default), pcma (A-Law) or pcmu (mu-Law) rtp_forward_ptype = payload type to use when streaming (optional: only read for Opus, 100 used if missing) rtp_forward_group = group of participants to forward, if enabled in the room (optional: forwards full mix if missing) rtp_forward_srtp_suite = length of authentication tag, if SRTP is needed (32 or 80) rtp_forward_srtp_crypto = key to use as crypto, if SRTP is needed (base64 encoded key as in SDES) rtp_forward_always_on = true|false, whether silence should be forwarded when the room is empty (optional: false used if missing) }
The Audio Bridge API supports several requests, some of which are synchronous and some asynchronous. There are some situations, though, (invalid JSON, invalid request) which will always result in a synchronous error response even for asynchronous requests.
create
, edit
, destroy
, exists
, allowed
, kick
, list
, mute
, unmute
, mute_room
, unmute_room
, listparticipants
, resetdecoder
, rtp_forward
, stop_rtp_forward
, list_forwarders
, play_file
, is_playing
and stop_file
are synchronous requests, which means you'll get a response directly within the context of the transaction. create
allows you to create a new audio conference bridge dynamically, as an alternative to using the configuration file; edit
allows you to dynamically edit some room properties (e.g., the PIN); destroy
removes an audio conference bridge and destroys it, kicking all the users out as part of the process; exists
allows you to check whether a specific audio conference exists; allowed
allows you to edit who's allowed to join a room via ad-hoc tokens; list
lists all the available rooms, while listparticipants
lists all the participants of a specific room and their details; resetdecoder
marks the Opus decoder for the participant as invalid, and forces it to be recreated (which might be needed if the audio for generated by the participant becomes garbled); rtp_forward
allows you to forward the mix of an AudioBridge room via RTP to a separate component (e.g., for broadcasting it to a wider audience, or for processing/recording), whereas stop_rtp_forward
can remove an existing forwarder; a list of configured forwarders for a room can be retrieved using the list_forwarders
request; finally, play_file
allows you to reproduce an audio .opus file in a mix (e.g., to play an announcement or some background music), is_playing
checks if a specific file is still playing, while stop_file
will stop such a playback instead.
The join
, configure
, changeroom
and leave
requests instead are all asynchronous, which means you'll get a notification about their success or failure in an event. join
allows you to join a specific audio conference bridge; configure
can be used to modify some of the participation settings (e.g., mute/unmute); changeroom
can be used to leave the current room and move to a different one without having to tear down the PeerConnection and recreate it again (useful for sidebars and "waiting rooms"); finally, leave
allows you to leave an audio conference bridge for good.
The AudioBridge plugin also allows you to forward the mix to an external listener, e.g., a gstreamer/ffmpeg pipeline waiting to process the mixer audio stream. You can add new RTP forwarders with the rtp_forward
request; a stop_rtp_forward
request removes an existing RTP forwarder; listforwarders
lists all the current RTP forwarders on a specific AudioBridge room instance. As an alternative, you can configure a single static RTP forwarder in the plugin configuration file. A finer grained control of what to forward externally, in terms of participants mix, can be achieved using groups.
create
can be used to create a new audio room, and has to be formatted as follows:
{ "request" : "create", "room" : <unique numeric ID, optional, chosen by plugin if missing>, "permanent" : <true|false, whether the room should be saved in the config file, default=false>, "description" : "<pretty name of the room, optional>", "secret" : "<password required to edit/destroy the room, optional>", "pin" : "<password required to join the room, optional>", "is_private" : <true|false, whether the room should appear in a list request>, "allowed" : [ array of string tokens users can use to join this room, optional], "sampling_rate" : <sampling rate of the room, optional, 16000 by default>, "spatial_audio" : <true|false, whether the mix should spatially place users, default=false>, "audiolevel_ext" : <true|false, whether the ssrc-audio-level RTP extension must be negotiated for new joins, default=true>, "audiolevel_event" : <true|false (whether to emit event to other users or not)>, "audio_active_packets" : <number of packets with audio level (default=100, 2 seconds)>, "audio_level_average" : <average value of audio level (127=muted, 0='too loud', default=25)>, "default_expectedloss" : <percent of packets we expect participants may miss, to help with FEC (default=0, max=20; automatically used for forwarders too)>, "default_bitrate" : <bitrate in bps to use for the all participants (default=0, which means libopus decides; automatically used for forwarders too)>, "denoise" : <true|false, whether denoising via RNNoise should be performed for each participant by default, default=false>, "record" : <true|false, whether to record the room or not, default=false>, "record_file" : "</path/to/the/recording.wav, optional>", "record_dir" : "</path/to/, optional; makes record_file a relative path, if provided>", "mjrs" : <true|false (whether all participants in the room should be individually recorded to mjr files, default=false)>, "mjrs_dir" : "</path/to/, optional>", "allow_rtp_participants" : <true|false, whether participants should be allowed to join via plain RTP as well, default=false>, "groups" : [ non-hierarchical array of string group names to use to gat participants, for external forwarding purposes only, optional] }
A successful creation procedure will result in a created
response:
{ "audiobridge" : "created", "room" : <unique numeric ID>, "permanent" : <true if saved to config file, false if not> }
If you requested a permanent room but a false
value is returned instead, good chances are that there are permission problems.
An error instead (and the same applies to all other requests, so this won't be repeated) would provide both an error code and a more verbose description of the cause of the issue:
{ "audiobridge" : "event", "error_code" : <numeric ID, check Macros below>, "error" : "<error description as a string>" }
Notice that, in general, all users can create rooms. If you want to limit this functionality, you can configure an admin admin_key
in the plugin settings. When configured, only "create" requests that include the correct admin_key
value in an "admin_key" property will succeed, and will be rejected otherwise. Notice that you can optionally extend this functionality to RTP forwarding as well, in order to only allow trusted clients to use that feature.
Once a room has been created, you can still edit some (but not all) of its properties using the edit
request. This allows you to modify the room description, secret, pin and whether it's private or not: you won't be able to modify other more static properties, like the room ID, the sampling rate, the extensions-related stuff and so on. If you're interested in changing the ACL, instead, check the allowed
message. An edit
request has to be formatted as follows:
{ "request" : "edit", "room" : <unique numeric ID of the room to edit>, "secret" : "<room secret, mandatory if configured>", "new_description" : "<new pretty name of the room, optional>", "new_secret" : "<new password required to edit/destroy the room, optional>", "new_pin" : "<new PIN required to join the room, PIN will be removed if set to an empty string, optional>", "new_is_private" : <true|false, whether the room should appear in a list request>, "new_record_dir" : "<new path where new recording files should be saved>", "new_mjrs_dir" : "<new path where new MJR files should be saved>", "permanent" : <true|false, whether the room should be also removed from the config file, default=false> }
A successful edit procedure will result in an edited
response:
{ "audiobridge" : "edited", "room" : <unique numeric ID> }
On the other hand, destroy
can be used to destroy an existing audio room, whether created dynamically or statically, and has to be formatted as follows:
{ "request" : "destroy", "room" : <unique numeric ID of the room to destroy>, "secret" : "<room secret, mandatory if configured>", "permanent" : <true|false, whether the room should be also removed from the config file, default=false> }
A successful destruction procedure will result in a destroyed
response:
{ "audiobridge" : "destroyed", "room" : <unique numeric ID> }
This will also result in a destroyed
event being sent to all the participants in the audio room, which will look like this:
{ "audiobridge" : "destroyed", "room" : <unique numeric ID of the destroyed room> }
To enable or disable recording of mixed audio stream while the conference is in progress, you can make use of the enable_recording
request, which has to be formatted as follows:
{ "request" : "enable_recording", "room" : <unique numeric ID of the room>, "secret" : "<room secret; mandatory if configured>" "record" : <true|false, whether this room should be automatically recorded or not>, "record_file" : "<file where audio recording should be saved (optional)>", "record_dir" : "<path where audio recording file should be saved (optional)>" }
A room can also be recorded by saving the individual contributions of participants to separate MJR files instead, in a format compatible with the Recordings. While a recording for each participant can be enabled or disabled separately, there also is a request to enable or disable them in bulk, thus implementing a feature similar to enable_recording
but for MJR files, rather than for a .wav mix. This can be done using the
enable_mjrs
request, which has to be formatted as follows:
{ "request" : "enable_mjrs", "room" : <unique numeric ID of the room>, "secret" : "<room secret; mandatory if configured>" "mjrs" : <true|false, whether all participants in the room should be individually recorded to mjr files or not>, "mjrs_dir" : "<path where all MJR files should be saved to (optional)>" }
You can check whether a room exists using the exists
request, which has to be formatted as follows:
{ "request" : "exists", "room" : <unique numeric ID of the room to check> }
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID>, "exists" : <true|false> }
You can configure whether to check tokens or add/remove people who can join a room using the allowed
request, which has to be formatted as follows:
{ "request" : "allowed", "secret" : "<room secret, mandatory if configured>", "action" : "enable|disable|add|remove", "room" : <unique numeric ID of the room to update>, "allowed" : [ // Array of strings (tokens users might pass in "join", only for add|remove) ] }
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID>, "allowed" : [ // Updated, complete, list of allowed tokens (only for enable|add|remove) ] }
If you're the administrator of a room (that is, you created it and have access to the secret) you can kick participants using the kick
request. Notice that this only kicks the user out of the room, but does not prevent them from re-joining: to ban them, you need to first remove them from the list of authorized users (see allowed
request) and then kick
them. The kick
request has to be formatted as follows:
{ "request" : "kick", "secret" : "<room secret, mandatory if configured>", "room" : <unique numeric ID of the room>, "id" : <unique numeric ID of the participant to kick> }
A successful request will result in a success
response:
{ "audiobridge" : "success", }
If you're the administrator of a room (that is, you created it and have access to the secret) you can kick all participants using the kick_all
request. Notice that this only kicks all users out of the room, but does not prevent them from re-joining: to ban them, you need to first remove them from the list of authorized users (see allowed
request) and then perform kick_all
. The kick_all
request has to be formatted as follows:
{ "request" : "kick_all", "secret" : "<room secret, mandatory if configured>", "room" : <unique numeric ID of the room> }
A successful request will result in a success
response:
{ "audiobridge" : "success", }
Another option available for administrators is suspending participants: in that case, participants are not kicked from the room (they remain in), but their contribution is not added to the mix, and they don't receive any audio from the mix either. This is a useful option to temporarily detach a participant from the room (e.g., because they'll be busy somewhere else) while still allowing them to keep the existing PeerConnection up and running, so that it can be quickly restored when they're back; since they're not part of the mix and don't receive any audio, the CPU resources to manage them are reduced as well. By default these suspended users participants will still receive events related to changes in the room (e.g., participants joining and leaving, mutes and unmutes, etc.), but these can be disabled too in case saving unnecessary signalling is desired: in that case, a suspended participant will only receive a recap of the current status when resumed. The suspend
request must be formatted as follows:
{ "request" : "suspend", "secret" : "<room secret, mandatory if configured>", "room" : <unique numeric ID of the room>, "id" : <unique numeric ID of the participant to suspend>, "pause_events" : <whether room events should be paused while suspended; optional, false by default> "stop_record" : <whether the MIR recording of this participant should be stopped too; optional, false by default> }
A successful request will result in a success
response:
{ "audiobridge" : "success", }
Resuming a suspended participant means bringing them back in the audio mix, and allowing them to hear audio through the PeerConnection once more. In case events were paused, they'll be resumed and a recap will be sent. The resume
request must be formatted as follows:
{ "request" : "resume", "secret" : "<room secret, mandatory if configured>", "room" : <unique numeric ID of the room>, "id" : <unique numeric ID of the suspended participant to resume>, "record": <true|false, whether to record this resumed user's contribution to a .mjr file (mixer not involved); optional, false by default>, "filename": "<basename of the file to record to, -audio.mjr will be added by the plugin; optional, will be relative to mjrs_dir, if configured in the room>" }
A successful request will result in a success
response:
{ "audiobridge" : "success", }
Both suspend
and resume
on a participant will result in a notification to the other participants in the room, which means they'll all be notified about a participant being suspended or resumed.
To get a list of the available rooms (excluded those configured or created as private rooms) you can make use of the list
request, which has to be formatted as follows:
{ "request" : "list" }
A successful request will produce a list of rooms in a success
response:
{ "audiobridge" : "success", "rooms" : [ // Array of room objects { // Room #1 "room" : <unique numeric ID>, "description" : "<Name of the room>", "pin_required" : <true|false, whether a PIN is required to join this room>, "sampling_rate" : <sampling rate of the mixer>, "spatial_audio" : <true|false, whether the mix has spatial audio (stereo)>, "record" : <true|false, whether the room is being recorded>, "num_participants" : <count of the participants> }, // Other rooms ] }
To get a list of the available rooms (excluded those configured or created as private rooms) you can make use of the list
request, which has to be formatted as follows:
{ "request" : "list" }
A successful request will produce a list of rooms in a success
response:
{ "audiobridge" : "success", "rooms" : [ // Array of room objects { // Room #1 "room" : <unique numeric ID>, "description" : "<Name of the room>", "pin_required" : <true|false, whether a PIN is required to join this room>, "sampling_rate" : <sampling rate of the mixer>, "spatial_audio" : <true|false, whether the mix has spatial audio (stereo)>, "record" : <true|false, whether the room is being recorded>, "num_participants" : <count of the participants> }, // Other rooms ] }
To get a list of the participants in a specific room, instead, you can make use of the listparticipants
request, which has to be formatted as follows:
{ "request" : "listparticipants", "room" : <unique numeric ID of the room> }
A successful request will produce a list of participants in a participants
response:
{ "audiobridge" : "participants", "room" : <unique numeric ID of the room>, "participants" : [ // Array of participant objects { // Participant #1 "id" : <unique numeric ID of the participant>, "display" : "<display name of the participant, if any; optional>", "setup" : <true|false, whether user successfully negotiate a WebRTC PeerConnection or not>, "muted" : <true|false, whether user is muted or not>, "suspended" : <true|false, whether user is suspended or not>, "talking" : <true|false, whether user is talking or not (only if audio levels are used)>, "spatial_position" : <in case spatial audio is used, the panning of this participant (0=left, 50=center, 100=right)>, }, // Other participants ] }
To mark the Opus decoder context for the current participant as invalid and force it to be recreated, use the resetdecoder
request:
{ "request" : "resetdecoder" }
A successful request will produce a success
response:
{ "audiobridge" : "success" }
You can add a new RTP forwarder for an existing room using the rtp_forward
request, which has to be formatted as follows:
{ "request" : "rtp_forward", "room" : <unique numeric ID of the room to add the forwarder to>, "group" : "<group to forward, if enabled in the room (forwards full mix if missing)>", "ssrc" : <SSRC to use to use when streaming (optional: stream_id used if missing)>, "codec" : "<opus (default), pcma (A-Law) or pcmu (mu-Law)>", "ptype" : <payload type to use when streaming (optional: 100 used if missing)>, "host" : "<host address to forward the RTP packets to>", "host_family" : "<ipv4|ipv6, if we need to resolve the host address to an IP; by default, whatever we get>", "port" : <port to forward the RTP packets to>, "srtp_suite" : <length of authentication tag (32 or 80); optional>, "srtp_crypto" : "<key to use as crypto (base64 encoded key as in SDES); optional>", "always_on" : <true|false, whether silence should be forwarded when the room is empty> }
The concept of "groups" is particularly important, here, in case groups were enabled when creating a room. By default, in fact, if a room has groups disabled, then an RTP forwarder will simply relay the mix of all active participants; sometimes, though, an external application may want to only receive the mix of some of the participants, and not all of them. This is what groups are for: if you tag participants with a specific group name, then creating a new forwarder that explicitly references that group name will ensure that only a mix of the participants tagged with that name will be forwarded. As such, it's important to point out groups only impact forwarders, and NOT
participants or how they're mixed in main mix for the room itself. Omitting a group name when creating a forwarder for a room where groups are enabled will simply fall back to the default behaviour of forwarding the full mix.
Notice that, as explained above, in case you configured an admin_key
property and extended it to RTP forwarding as well, you'll need to provide it in the request as well or it will be rejected as unauthorized. By default no limitation is posed on rtp_forward
.
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID, same as request>, "group" : "<group to forward, same as request if provided>", "stream_id" : <unique numeric ID assigned to the new RTP forwarder>, "host" : "<host this forwarder is streaming to, same as request if not resolved>", "port" : <audio port this forwarder is streaming to, same as request> }
To stop a previously created RTP forwarder and stop it, you can use the stop_rtp_forward
request, which has to be formatted as follows:
{ "request" : "stop_rtp_forward", "room" : <unique numeric ID of the room to remove the forwarder from>, "stream_id" : <unique numeric ID of the RTP forwarder> }
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID, same as request>, "stream_id" : <unique numeric ID, same as request> }
To get a list of the forwarders in a specific room, instead, you can make use of the listforwarders
request, which has to be formatted as follows:
{ "request" : "listforwarders", "room" : <unique numeric ID of the room> }
A successful request will produce a list of RTP forwarders in a forwarders
response:
{ "audiobridge" : "forwarders", "room" : <unique numeric ID of the room>, "rtp_forwarders" : [ // Array of RTP forwarder objects { // RTP forwarder #1 "stream_id" : <unique numeric ID of the forwarder>, "group" : "<group that is being forwarded, if available>", "ip" : "<IP this forwarder is streaming to>", "port" : <port this forwarder is streaming to>, "ssrc" : <SSRC this forwarder is using, if any>, "codec" : <codec this forwarder is using, if any>, "ptype" : <payload type this forwarder is using, if any>, "srtp" : <true|false, whether the RTP stream is encrypted>, "always_on" : <true|false, whether this forwarder works even when no participant is in or not> }, // Other forwarders ] }
As anticipated, while the AudioBridge is mainly meant to allow real users to interact with each other by mixing their contributions, you can also start the playback of one or more pre-recorded audio files in a mix: this is especially useful whenever you have, for instance, to play an announcement of some sort, or when maybe you want to play some background music (e.g., some music on hold when the room is empty). You can start the playback of an .opus file in an existing room using the play_file
request, which has to be formatted as follows:
{ "request" : "play_file", "room" : <unique numeric ID of the room to play the file in>, "secret" : "<room password, if configured>", "group" : "<group to play in (for forwarding purposes only; optional, mandatory if enabled in the room)>", "file_id": "<unique string ID of the announcement; random if not provided>", "filename": "<path to the Opus file to play>", "loop": <true|false, depending on whether or not the file should be played in a loop forever> }
Notice that, as explained above, in case you configured an admin_key
property and extended it to RTP forwarding as well, you'll need to provide it in the request as well or it will be rejected as unauthorized. By default play_file
only requires the room secret, meaning only people authorized to edit the room can start an audio playback.
Also notice that the only supported files are .opus files: no other audio format will be accepted. Besides, the file must be reachable and available on the file system: network addresses (e.g., HTTP URL) are NOT supported.
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID, same as request>, "file_id" : "<unique string ID of the announcement, same as request if provided or randomly generated otherwise>" }
As soon as the playback actually starts (usually immediately after the request has been sent), an event is sent to all participants so that they're aware something is being played back in the room besides themselves:
{ "audiobridge" : "announcement-started", "room" : <unique numeric ID, same as request>, "file_id" : "<unique string ID of the announcement>" }
A similar event is also sent whenever the playback stops, whether it's because the file ended and loop
was FALSE
(which will automatically clear the resources) or because a stop_file
request asked for the playback to be interrupted:
{ "audiobridge" : "announcement-stopped", "room" : <unique numeric ID, same as request>, "file_id" : "<unique string ID of the announcement>" }
You can check whether a specific playback is still going on in a room, you can use the is_playing
request, which has to be formatted as follows:
{ "request" : "is_playing", "room" : <unique numeric ID of the room where the playback is taking place>, "secret" : "<room password, if configured>", "file_id" : "<unique string ID of the announcement>" }
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID>, "file_id" : "<unique string ID of the announcement>", "playing" : <true|false> }
As anticipated, when not looping a playback will automatically stop and self-destruct when it reaches the end of the audio file. In case you want to stop a playback sooner than that, or want to stop a looped playback, you can use the stop_file
request:
{ "request" : "stop_file", "room" : <unique numeric ID of the room where the playback is taking place>, "secret" : "<room password, if configured>", "file_id": "<unique string ID of the announcement>" }
A successful request will result in a success
response:
{ "audiobridge" : "success", "room" : <unique numeric ID, same as request>, "file_id" : "<unique string ID of the now interrupted announcement>" }
That completes the list of synchronous requests you can send to the AudioBridge plugin. As anticipated, though, there are also several asynchronous requests you can send, specifically those related to joining and updating one's presence as a participant in an audio room.
The way you'd interact with the plugin is usually as follows:
join
request to join an audio room, and wait for the joined
event; this event will also include a list of the other participants, if any;configure
request attached to an audio-only JSEP offer to start configuring your participation in the room (e.g., join unmuted or muted), and wait for the related event
, which will be attached to a JSEP answer by the plugin to complete the setup of the WebRTC PeerConnection;configure
requests (without any JSEP-related attachment) to mute/unmute yourself during the audio conference;joined
, leaving
) to notify you about users joining/leaving/muting/unmuting;leave
request to leave a room; if you leave the PeerConnection instance intact, you can subsequently join a different room without requiring a new negotiation (and so just use a join
+ JSEP-less configure
to join).Notice that there's also a changeroom
request available: you can use this request to immediately leave the room you're in and join a different one, without requiring you to do a leave
+ join
+ configure
round. Of course remember not to pass any JSEP-related payload when doing a changeroom
as the same pre-existing PeerConnection will be re-used for the purpose.
Notice that you can also ask the AudioBridge plugin to send you an offer, when you join, rather than providing one yourself: this means that the SDP offer/answer roles would be reversed, and so you'd have to provide an answer yourself in this case. Remember that, in case renegotiations or restarts take place, they MUST follow the same negotiation pattern as the one that originated the connection: it's an error to send an SDP offer to the plugin to update a PeerConnection, if the plugin sent you an offer originally. It's advised to let users generate the offer, and let the plugin answer: this reverserd role is mostly here to facilitate the setup of cascaded mixers, e.g., allow one AudioBridge to connect to the other via WebRTC (which wouldn't be possible if both expected an offer from the other). Refer to the AudioBridge-generated offers section for more details.
About the syntax of all the above mentioned requests, join
has to be formatted as follows:
{ "request" : "join", "room" : <numeric ID of the room to join>, "id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>, "group" : "<group to assign to this participant (for forwarding purposes only; optional, mandatory if enabled in the room)>", "pin" : "<password required to join the room, if any; optional>", "display" : "<display name to have in the room; optional>", "token" : "<invitation token, in case the room has an ACL; optional>", "muted" : <true|false, whether to start unmuted or muted>, "suspended" : <true|false, whether to start suspended or not (false by default)>, "pause_events" : <whether room events should be paused, if the user is joining as suspended; optional, false by default> "codec" : "<codec to use, among opus (default), pcma (A-Law) or pcmu (mu-Law)>", "bitrate" : <bitrate to use for the Opus stream in bps; optional, default=0 (libopus decides)>, "quality" : <0-10, Opus-related complexity to use, the higher the value, the better the quality (but more CPU); optional, default is 4>, "expected_loss" : <0-20, a percentage of the expected loss (capped at 20%), only needed in case FEC is used; optional, default is 0 (FEC disabled even when negotiated) or the room default>, "volume" : <percent value, <100 reduces volume, >100 increases volume; optional, default is 100 (no volume change)>, "spatial_position" : <in case spatial audio is enabled for the room, panning of this participant (0=left, 50=center, 100=right)>, "denoise" : <true|false, whether denoising via RNNoise should be performed for this participant (default=room value)>, "secret" : "<room management password; optional, if provided the user is an admin and can't be globally muted with mute_room>", "audio_level_average" : "<if provided, overrides the room audio_level_average for this user; optional>", "audio_active_packets" : "<if provided, overrides the room audio_active_packets for this user; optional>", "record": <true|false, whether to record this user's contribution to a .mjr file (mixer not involved)>, "filename": "<basename of the file to record to, -audio.mjr will be added by the plugin; will be relative to mjrs_dir, if configured in the room>" }
A successful request will produce a joined
event:
{ "audiobridge" : "joined", "room" : <numeric ID of the room>, "id" : <unique ID assigned to the participant>, "display" : "<display name of the new participant>", "participants" : [ // Array of existing participants in the room ] }
The other participants in the room will be notified about the new participant by means of a different joined
event, which will only include the room
and the new participant as the only object in a participants
array.
Notice that, while the AudioBridge assumes participants will exchange media via WebRTC, there's a less known feature that allows you to use plain RTP to join an AudioBridge room instead. This functionality may be helpful in case you want, e.g., SIP based endpoints to join an AudioBridge room, by crafting SDPs for the SIP dialogs yourself using the info exchanged with the plugin. In order to do that, you keep on using the API to join as a participant as explained above, but instead of negotiating a PeerConnection as you usually would, you add an rtp
object to the join
request, which needs to be formatted as follows:
{ "request" : "join", [..] "rtp" : { "ip" : "<IP address you want media to be sent to>", "port" : <port you want media to be sent to>, "payload_type" : <payload type to use for RTP packets (optional; only needed in case Opus is used, automatic for G.711)>, "audiolevel_ext" : <ID of the audiolevel RTP extension, if used (optional)>, "fec" : <true|false, whether FEC should be enabled for the Opus stream (optional; only needed in case Opus is used)> } }
In that case, the participant will be configured to use plain RTP to exchange media with the room, and the joined
event will include an rtp
object as well to complete the negotiation:
{ "audiobridge" : "joined", [..] "rtp" : { "ip" : "<IP address the AudioBridge will expect media to be sent to>", "port" : <port the AudioBridge will expect media to be sent to>, "payload_type" : <payload type to use for RTP packets (optional; only needed in case Opus is used, automatic for G.711)> } }
Notice that, after a plain RTP session has been established, the AudioBridge plugin will only start sending media via RTP after it has received at least a valid RTP packet from the remote endpoint.
At this point, whether the participant will be interacting via WebRTC or plain RTP, the media-related settings of the participant can be modified by means of a configure
request. The configure
request has to be formatted as follows (notice that all parameters except request
are optional, depending on what you want to change):
{ "request" : "configure", "muted" : <true|false, whether to unmute or mute>, "display" : "<new display name to have in the room>", "bitrate" : <new bitrate to use for the Opus stream (see "join" for more info)>, "quality" : <new Opus-related complexity to use (see "join" for more info)>, "expected_loss" : <new value for the expected loss (see "join" for more info)> "volume" : <new volume percent value (see "join" for more info)>, "spatial_position" : <in case spatial audio is enabled for the room, new panning of this participant (0=left, 50=center, 100=right)>, "denoise" : <true|false, whether denoising via RNNoise should be performed for this participant (default=room value)>, "record": <true|false, whether to record this user's contribution to a .mjr file (mixer not involved), "filename": "<basename of the file to record to, -audio.mjr will be added by the plugin; will be relative to mjrs_dir, if configured in the room>", "group" : "<new group to assign to this participant, if enabled in the room (for forwarding purposes)>" }
muted
instructs the plugin to mute or unmute the participant; quality
changes the complexity of the Opus encoder for the participant; record
can be used to record this participant's contribution to a Janus .mjr file, and filename
to provide a basename for the path to save the file to (notice that this is different from the recording of a whole room: this feature only records the packets this user is sending, and is not related to the mixer stuff). A successful request will result in a ok
event:
{ "audiobridge" : "event", "room" : <numeric ID of the room>, "result" : "ok" }
In case the muted
property was modified, the other participants in the room will be notified about this by means of a event
notification, which will only include the room
and the updated participant as the only object in a participants
array.
If you're the administrator of a room (that is, you created it and have access to the secret) you can mute or unmute individual participants using the mute
or unmute
request
{ "request" : "<mute|unmute, whether to mute or unmute>", "secret" : "<room secret, mandatory if configured>", "room" : <unique numeric ID of the room>, "id" : <unique numeric ID of the participant to mute|unmute> }
A successful request will result in a success response:
{ "audiobridge" : "success", }
To mute/unmute the whole room, use mute_room
and unmute_room
instead.
{ "request" : "<mute_room|unmute_room, whether to mute or unmute>", "secret" : "<room secret, mandatory if configured>", "room" : <unique numeric ID of the room> }
A successful request will result in a success response:
{ "audiobridge" : "success", }
As anticipated, you can leave an audio room using the leave
request, which has to be formatted as follows:
{ "request" : "leave" }
The leaving user will receive a left
notification:
{ "audiobridge" : "left", "room" : <numeric ID of the room>, "id" : <numeric ID of the participant who left> }
All the other participants will receive an event
notification with the ID of the participant who just left:
{ "audiobridge" : "event", "room" : <numeric ID of the room>, "leaving" : <numeric ID of the participant who left> }
For what concerns the changeroom
request, instead, it's pretty much the same as a join
request and as such has to be formatted as follows:
{ "request" : "changeroom", "room" : <numeric ID of the room to move to>, "id" : <unique ID to assign to the participant; optional, assigned by the plugin if missing>, "pin" : "<password required to join the room, optional>", "group" : "<group to assign to this participant (for forwarding purposes only; optional, mandatory if enabled in the new room)>", "display" : "<display name to have in the room; optional>", "token" : "<invitation token, in case the new room has an ACL; optional>", "muted" : <true|false, whether to start unmuted or muted>, "suspended" : <true|false, whether to start suspended or not>, "pause_events" : <whether room events should be paused, if the user is joining as suspended; optional, false by default> "bitrate" : <bitrate to use for the Opus stream in bps; optional, default=0 (libopus decides)>, "quality" : <0-10, Opus-related complexity to use, higher is higher quality; optional, default is 4>, "expected_loss" : <0-20, a percentage of the expected loss (capped at 20%), only needed in case FEC is used; optional, default is 0 (FEC disabled even when negotiated) or the room default>, "volume" : <new volume percent value (see "join" for more info)>, "spatial_position" : <in case spatial audio is enabled for the room, new panning of this participant (0=left, 50=center, 100=right)>, "denoise" : <true|false, whether denoising via RNNoise should be performed for this participant (default=room value, or whether it was active before)> }
Such a request will trigger all the above-described leaving/joined events to the other participants, as it is indeed wrapping a leave
followed by a join
and as such the other participants in both rooms need to be updated accordingly. The participant who switched room instead will be sent a roomchanged
event which is pretty similar to what joined
looks like:
A successful request will produce a joined
event:
{ "audiobridge" : "roomchanged", "room" : <numeric ID of the new room>, "id" : <unique ID assigned to the participant in the new room>, "display" : "<display name of the new participant>", "participants" : [ // Array of existing participants in the new room ] }
As a last note, notice that the AudioBridge plugin does support renegotiations, mostly for the purpose of facilitating ICE restarts: in fact, there isn't much need for renegotiations outside of that context, as PeerConnections here will typically always contain a single m-line for audio, and so adding/removing streams makes no sense; besides, muting and unmuting is available via APIs, meaning that updating the media direction via SDP renegotiations would be overkill.
To force a renegotiation, all you need to do is send the new JSEP offer together with a configure
request: this request doesn't need to contain any directive at all, and can be empty. A JSEP answer will be sent back along the result of the request, if successful.
As anticipated in the previous sections, by default the AudioBridge plugin expects an SDP offer from users interested to join a room, and generates an SDP answer to complete the WebRTC negotiation process: this SDP offer can be provided either in a join
request or a configure
one, depending on how the app is constructed.
It's worth pointing out that the AudioBridge plugin also supports reversed roles when it comes to negotiation: that is, a user can ask the plugin to generate an SDP offer first, to which they'd provide an SDP answer to. This slightly changes the way the negotiation works within the context of the AudioBridge API, as some messages may have to be used in a different way. More specifically, if a user wants the plugin to generate an offer, they'll have to include a:
[..] "generate_offer" : true, [..] }
property in the join
or configure
request used to setup the PeerConnection. This means that the user will receive a JSEP SDP offer as part of the related event: at this point, the user needs to prepare to send a JSEP SDP answer and send it back to the plugin to complete the negotiation. The user must use the configure
request to provide this SDP answer: no need to provide additional attributes in the request, unless it's needed for application related purposes (e.g., to start muted).
Notice that this does have an impact on renegotiations, e.g., for ICE restarts or changes in the media direction. As a policy, plugins in Janus tend to enforce the same negotiation pattern used to setup the PeerConnection initially for renegotiations too, as it reduces the risk of issues like glare: this means that users will NOT be able to send an SDP offer to the AudioBridge plugin to update an existing PeerConnection, if that PeerConnection had previously been originated by a plugin offer instead. The plugin will treat this as an error.