WebRTC Plugin Guide
Table of Contents
- Introduction
- Plugin Overview
- Configuration
- Basic Usage
- Publishing
- Playing
- WHIP/WHEP Support
- Advanced Features
- Docker Notes
- STUN/TURN Reference
- FAQ
Introduction
WebRTC (Web Real-Time Communication) is an open standard jointly developed by W3C and IETF for real-time audio/video communication in browsers and mobile apps. Key characteristics include:
Highlights
- Peer-to-Peer Communication – Direct browser-to-browser connections reduce server load and latency.
- Low Latency – UDP transport and modern codecs deliver millisecond-level latency.
- Adaptive Bitrate – Automatically adjusts quality based on network conditions.
- NAT Traversal – ICE negotiation handles NAT/firewall traversal automatically.
WebRTC Flow
- Signaling – Exchange SDP (Session Description Protocol) and ICE candidates via HTTP/WebSocket, etc.
- ICE Candidate Gathering – Collect local and remote network candidates.
- Connection Establishment – Use ICE to traverse NAT and set up the P2P link.
- Media Transport – Stream encrypted audio/video over SRTP.
Connection Sequence Diagram
sequenceDiagram
participant Client as Client
participant Server as Monibuca Server
Note over Client,Server: 1. Signaling
Client->>Server: POST /webrtc/push/{streamPath}<br/>Body: SDP Offer
Server->>Server: Create PeerConnection
Server->>Server: Prepare SDP Answer
Server->>Client: HTTP 201 Created<br/>Body: SDP Answer
Note over Client,Server: 2. ICE Exchange
Client->>Client: Gather local ICE candidates
Server->>Server: Gather local ICE candidates
Client->>Server: Send ICE candidates
Server->>Client: Send ICE candidates
Note over Client,Server: 3. Connectivity Checks
Client->>Server: Attempt Host/SRFLX candidates
alt Connectivity succeeds
Client->>Server: Establish P2P connection
Server-->>Client: Confirm connection
else Continue negotiation
Client->>Server: Send additional candidates
Server-->>Client: Return negotiation result
end
Note over Client,Server: 4. Media Transport
Client->>Server: Send SRTP media
Server-->>Client: Send RTCP feedback
Plugin Overview
The Monibuca WebRTC plugin is built on Pion WebRTC v4 and provides complete WebRTC publishing/playing capabilities:
- ✅ Publishing via WHIP
- ✅ Playback via WHEP
- ✅ Video codecs: H.264, H.265, AV1, VP9
- ✅ Audio codecs: Opus, PCMA, PCMU
- ✅ TCP/UDP transport
- ✅ ICE server configuration
- ✅ DataChannel fallback
- ✅ Built-in test pages
Configuration
Basic Configuration
Example config.yaml snippet:
webrtc:
# Optional ICE servers. See "STUN/TURN Reference" for details.
iceservers: []
# Listening port options:
# - tcp:9000 (TCP port)
# - udp:9000 (UDP port)
# - udp:10000-20000 (UDP port range)
port: tcp:9000
# Interval for sending PLI after video packet loss
pli: 2s
# Enable DataChannel fallback when codecs are unsupported
enabledc: false
# MimeType filter; empty means no restriction
mimetype:
- video/H264
- video/H265
- audio/PCMA
- audio/PCMU
Parameter Details
ICE Servers
Configure the ICE server list for negotiation. See the STUN/TURN Reference section for full details and examples.
Port Configuration
- TCP Port – Suitable for restrictive firewalls.
port: tcp:9000 - UDP Port – Lower latency.
port: udp:9000 - UDP Range – Allocate ports per session.
port: udp:10000-20000
PLI Interval
Duration between Picture Loss Indication (PLI) requests, default 2s.
DataChannel
Fallback transport for unsupported codecs (e.g., MP4A). Data is sent as FLV over DataChannel.
MimeType Filter
Restrict allowed codec types. Leave empty to accept all supported codecs.
Public IP Configuration
Required when the server is behind NAT (e.g., Docker, private network).
Public IP Workflow
webrtc:
publicip: 203.0.113.1 # IPv4 address
publicipv6: 2001:db8::1 # Optional IPv6 address
port: tcp:9000
pli: 2s
enabledc: false
Diagram
┌─────────────────────────────────────────────────────────┐
│ Public Internet │
│ │
│ ┌──────────────┐ │
│ │ Client │ │
│ │ │ │
│ └──────┬───────┘ │
│ │ │
│ │ 1. Obtain public address information │
│ │ (via STUN/TURN if needed) │
│ │ │
└─────────┼───────────────────────────────────────────────┘
│
┌─────────▼───────────────────────────────────────────────┐
│ NAT / Firewall │
│ │
│ ┌──────────────┐ │
│ │ Monibuca │ │
│ │ Server │ │
│ │ │ │
│ │ Private IP: │ │
│ │ 192.168.1.100 │
│ │ │ │
│ │ PublicIP: 203.0.113.1 │
│ └──────────────┘ │
│ │
│ 2. Use PublicIP when creating ICE candidates │
│ 3. SDP answer contains public address │
│ 4. Client connects via public address │
└─────────────────────────────────────────────────────────┘
Notes
- Always configure
publicipif the server sits behind NAT. - Ensure the IP matches the actual public address.
- Verify port forwarding when using Docker or reverse proxies.
- Set both IPv4 and IPv6 if dual-stack connectivity is required.
Basic Usage
Start the Service
After enabling the WebRTC plugin, Monibuca exposes the following endpoints:
POST /webrtc/push/{streamPath}– WHIP publish endpointPOST /webrtc/play/{streamPath}– WHEP playback endpointGET /webrtc/test/{name}– Built-in test pages
Test Pages
- Publish test:
http://localhost:8080/webrtc/test/publish - Subscribe test:
http://localhost:8080/webrtc/test/subscribe - Screen share test:
http://localhost:8080/webrtc/test/screenshare
Publishing
Using the Test Page
- Visit
http://localhost:8080/webrtc/test/publish. - Allow camera/microphone permissions.
- Select a camera if multiple devices are available.
- The page automatically starts WebRTC publishing.
Custom Publishing
JavaScript Example
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
const pc = new RTCPeerConnection({
// Configure ICE servers if needed, see STUN/TURN reference
iceServers: [],
});
mediaStream.getTracks().forEach(track => {
pc.addTrack(track, mediaStream);
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const response = await fetch('/webrtc/push/live/test', {
method: 'POST',
headers: { 'Content-Type': 'application/sdp' },
body: offer.sdp,
});
const answerSdp = await response.text();
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
);
Forcing H.265
const transceiver = pc.getTransceivers().find(
t => t.sender.track && t.sender.track.kind === 'video'
);
if (transceiver) {
const capabilities = RTCRtpSender.getCapabilities('video');
const h265 = capabilities.codecs.find(
c => c.mimeType.toLowerCase() === 'video/h265'
);
if (h265) {
transceiver.setCodecPreferences([h265]);
}
}
Add ?h265 to the test page URL to attempt H.265 publishing: /webrtc/test/publish?h265.
Publish URL Parameters
streamPath– e.g.,live/testbearer– Bearer token for authentication
Example:
POST /webrtc/push/live/test?bearer=token
Stop Publishing
pc.close();
Playing
Using the Test Page
- Ensure a stream is already publishing.
- Visit
http://localhost:8080/webrtc/test/subscribe?streamPath=live/test. - The page automatically starts playback.
Custom Playback
JavaScript Example
const pc = new RTCPeerConnection({
// Configure ICE servers if needed, see STUN/TURN reference
iceServers: [],
});
pc.ontrack = event => {
if (event.streams.length > 0) {
videoElement.srcObject = event.streams[0];
videoElement.play();
}
};
pc.addTransceiver('video', { direction: 'recvonly' });
pc.addTransceiver('audio', { direction: 'recvonly' });
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const response = await fetch('/webrtc/play/live/test', {
method: 'POST',
headers: { 'Content-Type': 'application/sdp' },
body: offer.sdp,
});
const answerSdp = await response.text();
await pc.setRemoteDescription(
new RTCSessionDescription({ type: 'answer', sdp: answerSdp })
);
Playback URL Parameters
streamPath– e.g.,live/test
Example:
POST /webrtc/play/live/test
Stop Playback
pc.close();
WHIP/WHEP Support
WHIP (WebRTC-HTTP Ingestion Protocol)
Workflow
- Client creates PeerConnection and Offer.
- Client
POST /webrtc/push/{streamPath}with SDP Offer. - Server returns SDP Answer (HTTP 201 Created).
- Client sets Answer.
- Media streaming starts.
Sequence Diagram
sequenceDiagram
participant Client as Client
participant Server as Monibuca Server
Note over Client,Server: 1. Preparation
Client->>Client: getUserMedia()
Client->>Client: New RTCPeerConnection
Client->>Client: Add tracks
Note over Client,Server: 2. Create Offer
Client->>Client: createOffer()
Client->>Client: setLocalDescription()
Client->>Client: Gather ICE candidates
Note over Client,Server: 3. Send Offer
Client->>Server: POST /webrtc/push/{streamPath}
Server->>Server: Parse Offer
Server->>Server: New PeerConnection
Server->>Server: setRemoteDescription()
Server->>Server: Publish stream
Server->>Server: Gather ICE candidates
Note over Client,Server: 4. Server Answer
Server->>Server: createAnswer()
Server->>Server: setLocalDescription()
Server->>Client: HTTP 201 + SDP Answer
Server->>Client: Location: /webrtc/api/stop/push/{streamPath}
Note over Client,Server: 5. Client Processes Answer
Client->>Client: setRemoteDescription()
Client->>Client: ICE exchange
Server->>Client: ICE exchange
Note over Client,Server: 6. Connection
Client->>Server: ICE connected
Server->>Client: ICE connected
Client->>Server: Create DataChannel (optional)
Server-->>Client: DataChannel confirmation
Note over Client,Server: 7. Streaming
Client->>Server: Send SRTP media
Server-->>Client: RTCP feedback
Client Example
const pc = new RTCPeerConnection();
// Add tracks, create offer...
WHEP (WebRTC HTTP Egress Protocol)
Workflow
- Client creates PeerConnection and Offer (recvonly tracks).
- Client
POST /webrtc/play/{streamPath}with SDP Offer. - Server returns SDP Answer.
- Client sets Answer.
- Media streaming starts.
Sequence Diagram
sequenceDiagram
participant Client as Client
participant Server as Monibuca Server
Note over Client,Server: 1. Preparation
Client->>Client: New RTCPeerConnection
Client->>Client: Add recvonly transceivers
Client->>Client: Listen ontrack
Note over Client,Server: 2. Create Offer
Client->>Client: createOffer()
Client->>Client: setLocalDescription()
Client->>Client: Gather ICE candidates
Note over Client,Server: 3. Send Offer
Client->>Server: POST /webrtc/play/{streamPath}
Server->>Server: Parse Offer
Server->>Server: Ensure stream exists
alt Stream missing
Server->>Client: HTTP 404 Not Found
else Stream available
Server->>Server: New PeerConnection
Server->>Server: setRemoteDescription()
Server->>Server: Subscribe to stream
Server->>Server: Add tracks
Server->>Server: Gather ICE candidates
Note over Client,Server: 4. Server Answer
Server->>Server: createAnswer()
Server->>Server: setLocalDescription()
Server->>Client: HTTP 200 OK + SDP Answer
Note over Client,Server: 5. Client Processes Answer
Client->>Client: setRemoteDescription()
Client->>Client: ICE exchange
Server->>Client: ICE exchange
Note over Client,Server: 6. Connection
Client->>Server: ICE connected
Server->>Client: ICE connected
Client->>Server: DataChannel setup (optional)
Server-->>Client: DataChannel confirmation
Note over Client,Server: 7. Streaming
Server->>Client: Send SRTP media
Client->>Client: Play media
end
Acting as WHIP/WHEP Client
Pull via WHEP
pull:
streams:
- url: https://whep.example.com/play/stream1
streamPath: live/stream1
Push via WHIP
push:
streams:
- url: https://whip.example.com/push/stream1
streamPath: live/stream1
Advanced Features
Codec Support
- Video: H.264, H.265/HEVC, AV1, VP9
- Audio: Opus, PCMA (G.711 A-law), PCMU (G.711 μ-law)
DataChannel Transport
Enable DataChannel for unsupported codecs (e.g., MP4A audio) by setting enabledc: true. Data is encapsulated in FLV over the DataChannel.
NAT Traversal
Configure publicip/publicipv6 when running behind NAT; see Public IP Configuration.
Multi-stream Support
Use http://localhost:8080/webrtc/test/batchv2 to test multi-stream scenarios.
BatchV2 Mode
- Signaling channel: WebSocket endpoint
ws(s)://{host}/webrtc/batchv2(upgrade from HTTP). - Initial handshake:
- Create a
RTCPeerConnection, runcreateOffer/setLocalDescription. - Send
{ "type": "offer", "sdp": "..." }over the WebSocket. - Server replies
{ "type": "answer", "sdp": "..." }; callsetRemoteDescription.
- Create a
- Common commands (all JSON text frames):
getStreamListResponse example:{ "type": "getStreamList" }{ "type": "streamList", "streams": [{ "path": "live/cam1", "codec": "H264", "width": 1280, "height": 720, "fps": 25 }] }.subscribeServer renegotiates and returns{ "type": "subscribe", "streamList": ["live/cam1", "live/cam2"], "offer": "SDP..." }{ "type": "answer", "sdp": "..." }; callsetRemoteDescriptionagain.unsubscribe– same structure assubscribe, withstreamListcontaining the streams to remove.publishServer responds with a new SDP answer that must be applied client-side.{ "type": "publish", "streamPath": "live/cam3", "offer": "SDP..." }unpublishTriggers renegotiation; server returns a fresh answer.{ "type": "unpublish", "streamPath": "live/cam3" }ping:{ "type": "ping" }keeps the connection alive; server answers withpong.
- Media scope: current implementation subscribes video only (
SubAudiodisabled). Extend as needed if audio tracks are required. - Client helper:
web/BatchV2Client.tsimplements the browser-side workflow; seewebrtc/test/batchv2for a functional demo (stream list, publish, subscribe management). - Troubleshooting:
- Errors arrive as
{ "type": "error", "message": "..." }; inspect browser console/WebSocket inspector for details. - Each
subscribe/publishtriggers a new SDP cycle; ensure the app performssetLocalDescription→ send message →setRemoteDescriptionwithout skipping.
- Errors arrive as
Connection Monitoring
pc.oniceconnectionstatechange = () => {
console.log('ICE State:', pc.iceConnectionState);
// new, checking, connected, completed, failed, disconnected, closed
};
pc.onconnectionstatechange = () => {
console.log('Connection State:', pc.connectionState);
};
Docker Notes
1. Network Mode
Prefer host mode:
docker run --network host monibuca/monibuca
When using bridge mode:
- Map WebRTC ports (TCP/UDP)
- Configure correct public IP
- Ensure port mapping matches plugin config
docker run -p 8080:8080 -p 9000:9000/udp monibuca/monibuca
2. Port Mapping
TCP mode
docker run -p 8080:8080 -p 9000:9000/tcp monibuca/monibuca
UDP mode
docker run -p 8080:8080 -p 9000:9000/udp monibuca/monibuca
UDP range
docker run -p 8080:8080 -p 10000-20000:10000-20000/udp monibuca/monibuca
Note: Mapping large UDP ranges can be tricky; prefer a single UDP port or
hostmode when possible.
3. Public IP
Always set publicip when running inside Docker (container IPs are private).
curl ifconfig.me
dig +short myip.opendns.com @resolver1.opendns.com
Example configuration:
webrtc:
publicip: 203.0.113.1
port: udp:9000
4. Docker Compose Example
version: '3.8'
services:
monibuca:
image: monibuca/monibuca:latest
network_mode: host # recommended
# Or bridge mode:
# ports:
# - "8080:8080"
# - "9000:9000/udp"
volumes:
- ./config.yaml:/app/config.yaml
- ./logs:/app/logs
environment:
- PUBLICIP=203.0.113.1
5. Common Docker Issues
- Connection failures – Configure
publicip, preferhostnetwork, verify port mapping. - Unstable UDP mapping – Prefer
hostmode or TCP mode (port: tcp:9000); inspect firewall rules. - Multiple instances – Assign different ports (e.g.,
tcp:9000,tcp:9001) and map accordingly.
6. Best Practices
- Prefer
hostnetwork for better performance. - Always provide
publicip/publicipv6when behind NAT. - Switch to TCP mode if UDP mapping is problematic.
- Monitor WebRTC logs to track connection states.
- Configure TURN servers as a fallback to improve success rates.
STUN/TURN Reference
Why STUN/TURN Matters
- STUN (Session Traversal Utilities for NAT) helps endpoints discover public addresses and ports.
- TURN (Traversal Using Relays around NAT) relays media when direct connectivity fails (e.g., symmetric NAT).
- STUN is sufficient for most public/home networks; enterprise/mobile scenarios often require TURN fallback.
Configuration Example
webrtc:
iceservers:
- urls:
- stun:stun.l.google.com:19302
- urls:
- turn:turn.example.com:3478
username: user
credential: password
urlsaccepts multiple entries, mixingstun:,turn:,turns:URIs.- Rotate TURN credentials regularly; consider short-lived tokens (e.g., coturn REST API).
Deployment Tips
- Deploy close to users – Lower latency boosts stability.
- Reserve bandwidth – TURN relays bidirectional media streams.
- Secure access – Protect TURN credentials with authentication or token mechanisms.
- Monitor usage – Track sessions, bandwidth, failure rates, and alert on anomalies.
- Multi-region redundancy – Provide regional STUN/TURN nodes for global coverage.
FAQ
1. Connection Fails
Problem: WebRTC connection cannot be established.
Solutions:
- Verify ICE server configuration.
- Ensure firewall rules allow the configured ports.
- Try TCP mode:
port: tcp:9000. - Configure TURN as a relay fallback.
2. Video Not Displayed
Problem: Connection succeeds but no video is shown.
Solutions:
- Check browser console errors.
- Confirm the stream path is correct.
- Ensure the codec is supported.
- Test with the built-in subscribe page.
- Inspect the Network panel to confirm SDP responses and track creation.
- Run
pc.getReceivers().map(r => r.track)in the console and verify tracks arelive. - Review server logs to confirm the subscriber receives video frames.
- Use
chrome://webrtc-internalsoredge://webrtc-internalsfor detailed stats (bitrate, frame rate, ICE state).
3. H.265 Unsupported
Problem: Browser lacks H.265 decoding support.
Solutions:
- Enable DataChannel fallback:
enabledc: true. - Publish H.264 instead.
- Wait for browser support (Chrome 113+ provides partial support).
4. CORS Issues
Problem: Requests are blocked due to CORS.
Solutions:
- Configure correct CORS headers.
- Deploy under the same origin.
- Use a reverse proxy.
5. Port Already in Use
Problem: Configured port is unavailable.
Solutions:
- Change the port:
port: tcp:9001. - Check whether other services occupy the port.
- Use a UDP range:
port: udp:10000-20000.
6. Docker Connection Issues
Problem: WebRTC fails when running inside Docker.
Solutions:
- Configure
publicipcorrectly. - Prefer
hostnetwork mode. - Double-check port mappings (remember
/udp). - Ensure firewall rules allow the traffic.
- Review the Docker Notes.
7. PublicIP Not Working
Problem: Configured publicip, but clients still cannot connect.
Solutions:
- Verify the value matches the actual public address.
- Ensure port forwarding aligns with the plugin configuration.
- Test with
hostnetwork mode. - Inspect firewall/NAT rules.
- Check server logs to confirm ICE candidates include the public IP.
8. AAC Audio Not Playing
Problem: Audio unavailable when the source uses AAC/MP4A.
Solutions:
- The plugin currently supports Opus, PCMA, and PCMU.
- Options:
- Publish Opus instead of AAC.
- Enable DataChannel (
enabledc: true) to transport FLV with AAC. - Transcode audio to a supported codec before publishing.
Summary
The Monibuca WebRTC plugin delivers full WHIP/WHEP functionality for low-latency real-time streaming in modern browsers. Follow the configuration, deployment, and troubleshooting guidance above to integrate WebRTC publishing and playback into your Monibuca deployment quickly.
Further reading: