Documentation Index
Fetch the complete documentation index at: https://mintlify.com/jitsi/lib-jitsi-meet/llms.txt
Use this file to discover all available pages before exploring further.
XMPP Signaling
lib-jitsi-meet uses XMPP with the Jingle extension (XEP-0166) for WebRTC signaling. The library implements this using Strophe.js with custom plugins.
Architecture Overview
The XMPP signaling layer consists of:
- Strophe.js - Core XMPP client library (Jitsi fork)
- JingleConnectionPlugin - Handles Jingle session management
- JingleSessionPC - Manages individual peer connections
- Strophe Plugins - Protocol extensions (disco, jingle, etc.)
- SignalingLayer - Abstraction for protocol details
Source: CLAUDE.md:94-99
Jingle Protocol
Jingle (XEP-0166) is an XMPP extension for establishing peer-to-peer media sessions.
Session Lifecycle
1. Session Initiate
case 'session-initiate': {
logger.info('(TIME) received session-initiate:\t', now);
const pcConfig = isP2P ? this.p2pIceConfig : this.jvbIceConfig;
sess = new JingleSessionPC(
sid, // Session ID
iq.getAttribute('to'), // Local JID
fromJid, // Remote JID
this.connection, // XMPP connection
this.mediaConstraints, // Media constraints
cloneDeep(pcConfig), // ICE config
isP2P, // P2P or JVB
false // Not initiator
);
this.sessions[sess.sid] = sess;
this.eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleElement, now);
break;
}
Source: modules/xmpp/strophe.jingle.js:181-200
2. Session Accept
case 'session-accept': {
const ssrcs = [];
// Extract SSRCs from session-accept (P2P)
findAll(iq, 'jingle>content').forEach(content => {
const ssrc = getAttribute(findFirst(content, 'description'), 'ssrc');
ssrc && ssrcs.push(ssrc);
});
logger.debug(`Received ${action} from ${fromJid} with ssrcs=${ssrcs}`);
this.eventEmitter.emit(XMPPEvents.CALL_ACCEPTED, sess, jingleElement);
break;
}
Source: modules/xmpp/strophe.jingle.js:202-214
3. Transport Info
ICE candidates are exchanged via transport-info actions:
case 'transport-info': {
const candidates = _parseIceCandidates(
findFirst(iq, 'jingle>content>transport')
);
logger.debug(`Received ${action} from ${fromJid} for candidates=${candidates.join(', ')}`);
this.eventEmitter.emit(XMPPEvents.TRANSPORT_INFO, sess, jingleElement);
break;
}
Source: modules/xmpp/strophe.jingle.js:221-226
4. Session Terminate
case 'session-terminate': {
logger.info('terminating...', sess.sid);
let reasonCondition = null;
let reasonText = null;
const reasonElement = findFirst(iq, ':scope>jingle>reason');
if (reasonElement) {
const firstReasonChild = reasonElement.children?.length > 0
? reasonElement.children[0] : undefined;
reasonCondition = firstReasonChild ? firstReasonChild.tagName : null;
reasonText = getText(findFirst(iq, ':scope>jingle>reason>text'));
}
logger.debug(`Received ${action} from ${fromJid} disconnect reason=${reasonText}`);
this.terminate(sess.sid, reasonCondition, reasonText);
this.eventEmitter.emit(XMPPEvents.CALL_ENDED, sess, reasonCondition, reasonText);
break;
}
Source: modules/xmpp/strophe.jingle.js:228-245
Source Signaling
Source Add
case 'source-add':
sess.addRemoteStream(findAll(iq, ':scope>jingle>content'));
break;
Source: modules/xmpp/strophe.jingle.js:250-252
Source Remove
case 'source-remove':
sess.removeRemoteStream(findAll(iq, ':scope>jingle>content'));
break;
Source: modules/xmpp/strophe.jingle.js:253-255
P2P vs JVB Detection
lib-jitsi-meet distinguishes P2P from JVB connections by checking the resource:
const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';
Jicofo (the JVB focus component) always has the resource “focus”, while P2P connections use participant resources.
Source: modules/xmpp/strophe.jingle.js:152
JSON Message Encoding
For large conferences, lib-jitsi-meet uses JSON encoding to compress source information:
const jsonMessages = findAll(iq, 'jingle>json-message');
if (jsonMessages?.length) {
let audioVideoSsrcs;
logger.info(`Found a JSON-encoded element in ${action}, translating to standard Jingle.`);
for (let i = 0; i < jsonMessages.length; i++) {
// Translate JSON to standard Jingle source elements
audioVideoSsrcs = expandSourcesFromJson(iq, jsonMessages[i]);
}
if (audioVideoSsrcs?.size) {
const logMessage = [];
for (const endpoint of audioVideoSsrcs.keys()) {
logMessage.push(`${endpoint}:[${audioVideoSsrcs.get(endpoint)}]`);
}
logger.debug(`Received ${action} from ${fromJid} with sources=${logMessage.join(', ')}`);
}
}
Source: modules/xmpp/strophe.jingle.js:156-174
ICE Candidate Parsing
ICE candidates are extracted from transport elements:
function _parseIceCandidates(transport) {
const candidates = findAll(transport, ':scope>candidate');
const parseCandidates = [];
// Extract candidate attributes
candidates.forEach(candidate => {
const attributes = candidate.attributes;
const candidateAttrs = [];
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
candidateAttrs.push(`${attr.name}: ${attr.value}`);
}
parseCandidates.push(candidateAttrs.join(' '));
});
return parseCandidates;
}
Source: modules/xmpp/strophe.jingle.js:26-44
Session Management
Creating P2P Sessions
newP2PJingleSession(me, peer) {
const sess = new JingleSessionPC(
RandomUtil.randomHexString(12), // Random session ID
me, // Local JID
peer, // Remote JID
this.connection, // XMPP connection
this.mediaConstraints, // Media constraints
this.p2pIceConfig, // P2P ICE config
true, // Is P2P
true // Is initiator
);
this.sessions[sess.sid] = sess;
return sess;
}
Source: modules/xmpp/strophe.jingle.js:277-292
Session Validation
Session Not Found
if (!sess) {
ack.attrs({ type: 'error' });
ack.c('error', { type: 'cancel' })
.c('item-not-found', {
xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
})
.up()
.c('unknown-session', {
xmlns: 'urn:xmpp:jingle:errors:1'
});
logger.warn(`invalid session id: ${sid}`);
this.connection.send(ack);
return true;
}
Source: modules/xmpp/strophe.jingle.js:99-114
JID Mismatch
if (fromJid !== sess.remoteJid) {
logger.warn('jid mismatch for session id', sid, sess.remoteJid, iq);
ack.attrs({ type: 'error' });
ack.c('error', { type: 'cancel' })
.c('item-not-found', {
xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
})
.up()
.c('unknown-session', {
xmlns: 'urn:xmpp:jingle:errors:1'
});
this.connection.send(ack);
return true;
}
Source: modules/xmpp/strophe.jingle.js:117-132
Strophe.js Integration
lib-jitsi-meet uses a custom fork of Strophe.js with Jitsi-specific modifications.
Connection Plugin Pattern
export default class JingleConnectionPlugin extends ConnectionPlugin {
constructor(xmpp, eventEmitter, iceConfig) {
super();
this.xmpp = xmpp;
this.eventEmitter = eventEmitter;
this.sessions = {};
this.jvbIceConfig = iceConfig.jvb;
this.p2pIceConfig = iceConfig.p2p;
this.mediaConstraints = {
offerToReceiveAudio: true,
offerToReceiveVideo: true
};
}
init(connection) {
super.init(connection);
this.connection.addHandler(
this.onJingle.bind(this),
'urn:xmpp:jingle:1', // Namespace
'iq', // Stanza type
'set', // IQ type
null, // ID
null // From
);
}
}
Source: modules/xmpp/strophe.jingle.js:49-78
IQ Acknowledgment
All Jingle IQs must be acknowledged:
const ack = $iq({
id: iq.getAttribute('id'),
to: fromJid,
type: 'result'
});
// ... process the Jingle action ...
this.connection.send(ack);
return true; // Keep handler
Source: modules/xmpp/strophe.jingle.js:91-94, 265-267
Default media constraints for Jingle sessions:
this.mediaConstraints = {
offerToReceiveAudio: true,
offerToReceiveVideo: true
};
Source: modules/xmpp/strophe.jingle.js:64-67
ICE Configuration
Separate ICE configurations for JVB and P2P:
constructor(xmpp, eventEmitter, iceConfig) {
// ...
this.jvbIceConfig = iceConfig.jvb; // JVB connection config
this.p2pIceConfig = iceConfig.p2p; // P2P connection config
}
ICE config is cloned before use to prevent mutation:
const pcConfig = isP2P ? this.p2pIceConfig : this.jvbIceConfig;
sess = new JingleSessionPC(
// ...
cloneDeep(pcConfig), // Deep clone prevents side effects
// ...
);
Source: modules/xmpp/strophe.jingle.js:62-63, 194
Strophe Plugins
lib-jitsi-meet includes several Strophe plugins for XMPP protocol extensions:
Available Plugins
- strophe.jingle.js - Jingle session management (XEP-0166)
- strophe.disco.js - Service discovery (XEP-0030)
- moderator.js - Moderator functionality
- Lobby.js - Lobby/waiting room support
- BreakoutRooms.js - Breakout room management
Source: modules/xmpp/ directory listing
Plugin Architecture
All plugins extend ConnectionPlugin:
import ConnectionPlugin from './ConnectionPlugin';
export default class MyPlugin extends ConnectionPlugin {
init(connection) {
super.init(connection);
// Register handlers, etc.
}
}
Source: modules/xmpp/strophe.jingle.js:9, 49
XMPP Events
The Jingle plugin emits events via the EventEmitter:
import { XMPPEvents } from '../../service/xmpp/XMPPEvents';
// Session events
this.eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleElement, now);
this.eventEmitter.emit(XMPPEvents.CALL_ACCEPTED, sess, jingleElement);
this.eventEmitter.emit(XMPPEvents.CALL_ENDED, sess, reasonCondition, reasonText);
this.eventEmitter.emit(XMPPEvents.TRANSPORT_INFO, sess, jingleElement);
Source: modules/xmpp/strophe.jingle.js:5, 199, 213, 225, 244
Session ID CollisionIf a session-initiate is received with an existing session ID, lib-jitsi-meet rejects it:if (sess !== undefined) {
ack.attrs({ type: 'error' });
ack.c('error', { type: 'cancel' })
.c('service-unavailable', {
xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
});
logger.warn('duplicate session id', sid, iq);
this.connection.send(ack);
return true;
}
This prevents out-of-order message issues.Source: modules/xmpp/strophe.jingle.js:133-146
XML Utilities
lib-jitsi-meet provides XML helpers for parsing Jingle IQs:
import { findAll, findFirst, getAttribute, getText } from '../util/XMLUtils';
// Find elements
const jingleElement = findFirst(iq, 'jingle');
const contents = findAll(iq, 'jingle>content');
// Get attributes
const sid = getAttribute(jingleElement, 'sid');
const action = getAttribute(jingleElement, 'action');
// Get text content
const reasonText = getText(findFirst(iq, ':scope>jingle>reason>text'));
Source: modules/xmpp/strophe.jingle.js:7, 85-88, 239
XMPP Connection Management
The signaling layer is part of the broader XMPP architecture:
Module Structure
- ChatRoom.js - Multi-user chat room with presence
- JingleSessionPC.js - Jingle protocol for media negotiation
- SignalingLayerImpl.js - Abstraction layer for signaling
- Strophe plugins - Protocol extensions
Transport Support
- BOSH (Bidirectional-streams Over Synchronous HTTP)
- WebSocket
Source: CLAUDE.md:94-99
Session Timing
const now = window.performance.now();
logger.info('(TIME) received session-initiate:\t', now);
lib-jitsi-meet tracks high-resolution timestamps for session events to measure signaling latency.
Source: modules/xmpp/strophe.jingle.js:147, 182
P2P Optimization
For P2P sessions, detailed logging is conditional:
isP2P && logger.debug(`Received ${action} from ${fromJid}`);
Source: modules/xmpp/strophe.jingle.js:184
Next Steps