Overview
ProxyConnectionService manages peer-to-peer WebRTC connections for proxying media streams between participants. Itβs designed primarily for screen sharing scenarios where one participant forwards their screen to another peer through a direct P2P connection, bypassing the main conference infrastructure.
Key Features
- Direct P2P connections using WebRTC
- Jingle-based signaling via XMPP IQ stanzas
- Automatic TURN credentials fetching
- Converts remote streams to JitsiLocalTrack for local usage
- Single active connection management (only one peer at a time)
Constructor
new ProxyConnectionService(options: ProxyConnectionOptions)
Configuration options for the proxy connection service
JitsiConnection instance used to fetch TURN credentials for the P2P connection
RTCConfiguration for the WebRTC peer connection. If not provided and jitsiConnection is available, P2P ICE config will be used automatically.
options.convertVideoToDesktop
Whether to convert received video to a desktop (screen share) stream type
Callback invoked when a remote stream is received. The stream is converted to a JitsiLocalTrack and passed to this callback.Signature: (jitsiLocalTrack: JitsiLocalTrack) => void
Callback invoked when a signaling message needs to be sent to the peer.Signature: (peerJid: string, message: { iq: string }) => void
options.onConnectionClosed
Callback invoked when the proxy connection is closedSignature: () => void
Example:
import ProxyConnectionService from '@jitsi/lib-jitsi-meet/modules/proxyconnection/ProxyConnectionService';
const proxyService = new ProxyConnectionService({
jitsiConnection: connection,
convertVideoToDesktop: true,
onRemoteStream: (track) => {
console.log('Received proxied track:', track.getType());
// Use the track (e.g., add to conference)
conference.addTrack(track);
},
onSendMessage: (peerJid, message) => {
// Send signaling message via XMPP
connection.xmpp.connection.send(message.iq);
},
onConnectionClosed: () => {
console.log('Proxy connection closed');
}
});
Methods
Initiates a new proxy peer connection as the initiator and sends local tracks to the peer.
start(peerJid: string, localTracks?: JitsiLocalTrack[]): void
The full JID of the remote peer to connect to
localTracks
JitsiLocalTrack[]
default:"[]"
Array of local media tracks to send to the peer (typically screen share tracks)
This method:
- Creates a new ProxyConnectionPC instance as initiator
- Sets source names for all local tracks
- Initiates the WebRTC negotiation
- Sends tracks through the peer connection
Example:
import JitsiMeetJS from '@jitsi/lib-jitsi-meet';
// Get screen share track
const screenTracks = await JitsiMeetJS.createLocalTracks({
devices: ['desktop']
});
// Start proxy connection to forward screen share
const peerJid = '[email protected]/resource';
proxyService.start(peerJid, screenTracks);
console.log(`Started proxy connection to ${peerJid}`);
processMessage
Processes incoming signaling messages from the peer to establish or update the proxy connection.
processMessage(message: ProxyMessage): void
Signaling message received from the peer
Message data containing the IQ
Stringified Jingle IQ stanza for connection negotiation
Jingle Actions Handled:
session-initiate - Creates a new peer connection (as answerer)
session-accept - Accepts the connection offer
transport-info - Exchanges ICE candidates
session-terminate - Terminates the connection
connection-error - Handles connection errors
unavailable - Peer unavailable
Example:
// Handle incoming XMPP messages
connection.xmpp.connection.addHandler((iq) => {
const from = iq.getAttribute('from');
const jingleElement = iq.querySelector('jingle');
if (jingleElement) {
const iqString = new XMLSerializer().serializeToString(iq);
proxyService.processMessage({
from: from,
data: { iq: iqString }
});
}
return true; // Keep handler active
}, null, 'iq', 'set');
If a proxy connection is already active with a different peer, incoming messages from other peers are automatically rejected with a connection-error action.
Terminates the active proxy peer connection and cleans up resources.
Example:
// Stop screen sharing proxy
proxyService.stop();
console.log('Proxy connection stopped');
Complete Examples
Screen Share Proxy (Sender)
import JitsiMeetJS from '@jitsi/lib-jitsi-meet';
import ProxyConnectionService from '@jitsi/lib-jitsi-meet/modules/proxyconnection/ProxyConnectionService';
class ScreenShareProxy {
constructor(connection) {
this.connection = connection;
this.proxyService = null;
this.screenTracks = [];
}
async shareScreenToPeer(peerJid) {
// Create proxy service
this.proxyService = new ProxyConnectionService({
jitsiConnection: this.connection,
onSendMessage: (jid, message) => {
// Send via custom XMPP message
const iq = this.connection.xmpp.connection.createIQ({
to: jid,
type: 'set'
});
iq.c('proxy', { xmlns: 'jitsi:proxy' })
.c('data')
.t(message.iq);
this.connection.xmpp.connection.send(iq);
},
onConnectionClosed: () => {
console.log('Screen share proxy closed');
this.cleanup();
}
});
// Get screen share
this.screenTracks = await JitsiMeetJS.createLocalTracks({
devices: ['desktop'],
desktopSharingSourceDevice: 'screen'
});
// Start proxying
this.proxyService.start(peerJid, this.screenTracks);
console.log(`Sharing screen to ${peerJid}`);
}
stopSharing() {
if (this.proxyService) {
this.proxyService.stop();
this.cleanup();
}
}
cleanup() {
this.screenTracks.forEach(track => track.dispose());
this.screenTracks = [];
this.proxyService = null;
}
}
// Usage
const screenProxy = new ScreenShareProxy(connection);
await screenProxy.shareScreenToPeer('[email protected]/abc123');
// Later...
screenProxy.stopSharing();
Screen Share Proxy (Receiver)
import ProxyConnectionService from '@jitsi/lib-jitsi-meet/modules/proxyconnection/ProxyConnectionService';
class ScreenShareReceiver {
constructor(connection, conference) {
this.connection = connection;
this.conference = conference;
this.proxyService = null;
this.receivedTrack = null;
}
setupReceiver() {
// Create proxy service (receiver mode)
this.proxyService = new ProxyConnectionService({
jitsiConnection: this.connection,
convertVideoToDesktop: true, // Treat as desktop stream
onRemoteStream: (track) => {
console.log('Received proxied screen share');
this.receivedTrack = track;
// Display the screen share
const videoElement = document.getElementById('screen-share-video');
track.attach(videoElement);
// Or add to conference
// this.conference.addTrack(track);
},
onSendMessage: (jid, message) => {
// Send response via XMPP
const iq = this.connection.xmpp.connection.createIQ({
to: jid,
type: 'set'
});
iq.c('proxy', { xmlns: 'jitsi:proxy' })
.c('data')
.t(message.iq);
this.connection.xmpp.connection.send(iq);
},
onConnectionClosed: () => {
console.log('Screen share ended');
this.cleanup();
}
});
// Listen for incoming proxy messages
this.connection.xmpp.connection.addHandler(
(iq) => this.handleProxyMessage(iq),
'jitsi:proxy',
'iq',
'set'
);
}
handleProxyMessage(iq) {
const from = iq.getAttribute('from');
const dataElement = iq.querySelector('data');
if (dataElement) {
const iqData = dataElement.textContent;
this.proxyService.processMessage({
from: from,
data: { iq: iqData }
});
}
return true;
}
cleanup() {
if (this.receivedTrack) {
this.receivedTrack.dispose();
this.receivedTrack = null;
}
}
stop() {
if (this.proxyService) {
this.proxyService.stop();
}
this.cleanup();
}
}
// Usage
const receiver = new ScreenShareReceiver(connection, conference);
receiver.setupReceiver();
// Now ready to receive proxied screen shares
Bidirectional Audio/Video Proxy
import ProxyConnectionService from '@jitsi/lib-jitsi-meet/modules/proxyconnection/ProxyConnectionService';
import JitsiMeetJS from '@jitsi/lib-jitsi-meet';
class BidirectionalProxy {
constructor(connection) {
this.connection = connection;
this.proxyService = null;
this.localTracks = [];
this.remoteTracks = [];
}
async startCall(peerJid) {
// Get local audio/video
this.localTracks = await JitsiMeetJS.createLocalTracks({
devices: ['audio', 'video']
});
// Setup proxy service
this.proxyService = new ProxyConnectionService({
jitsiConnection: this.connection,
convertVideoToDesktop: false, // Regular video
onRemoteStream: (track) => {
console.log(`Received ${track.getType()} track from peer`);
this.remoteTracks.push(track);
// Attach to DOM
const container = track.isVideoTrack()
? '#remote-video'
: '#remote-audio';
track.attach(document.querySelector(container));
},
onSendMessage: (jid, message) => {
this.sendSignalingMessage(jid, message);
},
onConnectionClosed: () => {
console.log('Call ended');
this.endCall();
}
});
// Attach local tracks to UI
this.localTracks.forEach(track => {
if (track.isVideoTrack()) {
track.attach(document.querySelector('#local-video'));
}
});
// Start P2P connection
this.proxyService.start(peerJid, this.localTracks);
}
sendSignalingMessage(jid, message) {
// Implementation depends on your signaling method
this.connection.xmpp.connection.send(/* formatted message */);
}
handleIncomingMessage(message) {
if (this.proxyService) {
this.proxyService.processMessage(message);
}
}
endCall() {
if (this.proxyService) {
this.proxyService.stop();
}
this.localTracks.forEach(track => track.dispose());
this.remoteTracks.forEach(track => track.dispose());
this.localTracks = [];
this.remoteTracks = [];
}
}
Important Notes
ProxyConnectionService manages only one active peer connection at a time. If a connection exists and a message arrives from a different peer, it will be automatically rejected.
The service uses Jingle protocol over XMPP IQ stanzas for signaling. Ensure your XMPP server and message handlers support custom IQ namespaces.
Remote streams are converted to JitsiLocalTrack instances with a special deviceId format: proxy:{peerJid} and sourceType: 'proxy'
Connection Lifecycle
- Initiator calls
start(peerJid, tracks) β Sends session-initiate
- Receiver processes message β Creates connection β Sends
session-accept
- Both peers exchange ICE candidates via
transport-info
- Connection established β Media flows
- Either peer can send
session-terminate to close
onConnectionClosed callback triggered on both sides
Error Handling
The service handles several error scenarios:
- Rejected connections - Messages from unexpected peers are rejected with
connection-error
- Missing peer JID - Throws error if
start() called without peer JID
- Connection errors - Automatically closes and cleans up
- Malformed XML - Logs error and returns null for invalid IQ messages
proxyService.on('error', (error) => {
console.error('Proxy connection error:', error);
// Handle error (e.g., retry, notify user)
});
- Uses P2P connection, bypassing media server (lower latency, reduced server load)
- Requires TURN servers for connections behind restrictive NATs
- ICE configuration automatically fetched from JitsiConnection
- Single connection limit prevents resource exhaustion
Browser Support
Requires WebRTC support:
- Chrome/Edge 56+
- Firefox 44+
- Safari 11+
- Opera 43+