Skip to main content

WebRTC / WHEP Proxy

App Container includes an embedded WHEP (WebRTC HTTP Egress Protocol) server engine inside its Go backend daemon. This service bridges local RTSP IP camera streams to native browser-compatible WebRTC, allowing web applications to stream live video directly inside any standard HTML <video> element — without plugins or external media servers.


⚡ Key Highlights

  • Direct RTP Packet Forwarding: Zero video transcoding overhead. Packets are rewritten (SSRC/PT/SN) and forwarded directly to WebRTC peer connections for minimal CPU consumption.
  • H.264 Video Support: Fully compatible with H.264 network camera streams. (Note: H.265 is currently unsupported over WebRTC).
  • High Concurrency: Supports up to 8 simultaneous active stream sessions per device.
  • Zero Configuration ICE: Operates on localhost ICE candidate loops — because both the web application runtime and the Go container backend run locally on the same device, external STUN or TURN servers are completely unnecessary.

📡 WHEP API Endpoints

The WHEP proxy service runs at the container base address:

MethodEndpointDescription
POST/api/stream/whep/{stream-id}Start a new WHEP WebRTC stream session
DELETE/api/stream/whep/{stream-id}Stop an active WHEP stream session
GET/api/stream/sessionsList all active stream sessions (max 8)

🚀 Starting a WHEP Session

To establish a stream connection:

  1. Create a local browser RTCPeerConnection.
  2. Generate an SDP offer with video/audio receive-only transceivers.
  3. Send the SDP offer along with the target RTSP stream URL to POST /api/stream/whep/{stream-id}.
  4. Set the returned SDP answer as the remote description on your peer connection.

Request Format

POST /api/stream/whep/cam1
Content-Type: application/json

{
"url": "rtsp://192.168.1.100:554/stream",
"sdp": "v=0\r\no=- 123456 2 IN IP4 127.0.0.1..."
}

Response (200 OK)

{
"sdp": "v=0\r\no=- 654321 2 IN IP4 127.0.0.1..."
}

💻 Full Browser JavaScript Example

Below is a production-ready JavaScript snippet for mounting an RTSP camera stream directly onto an HTML <video> element using WHEP:

async function startWhepStream(streamId, rtspUrl, videoElement) {
// 1. Create RTCPeerConnection
const pc = new RTCPeerConnection({
iceServers: [] // Localhost ICE connection only
});

// 2. Add receive-only transceivers for video and audio
pc.addTransceiver('video', { direction: 'recvonly' });
pc.addTransceiver('audio', { direction: 'recvonly' });

// 3. Attach incoming media track to <video> element
pc.ontrack = (event) => {
if (event.streams && event.streams[0]) {
videoElement.srcObject = event.streams[0];
}
};

// 4. Create and set local SDP offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

// 5. Exchange SDP offer/answer with App Container WHEP backend
const response = await fetch(`http://localhost:8080/api/stream/whep/${streamId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: rtspUrl,
sdp: offer.sdp
})
});

if (!response.ok) {
throw new Error(`WHEP session failed: ${response.statusText}`);
}

// 6. Apply returned SDP answer
const { sdp } = await response.json();
await pc.setRemoteDescription({
type: 'answer',
sdp: sdp
});

return pc;
}

// Example usage:
const videoEl = document.getElementById('camera-preview');
startWhepStream('cam1', 'rtsp://192.168.1.100:554/stream', videoEl)
.then(() => console.log('WHEP stream connected successfully'))
.catch(err => console.error('WHEP stream error:', err));

🛑 Terminating a Session

To stop a stream and free backend resources:

async function stopWhepStream(streamId, pc) {
if (pc) {
pc.close();
}

await fetch(`http://localhost:8080/api/stream/whep/${streamId}`, {
method: 'DELETE'
});
}