Migrating from raw flutter_webrtc
If you've built WebRTC in Flutter directly on flutter_webrtc, you already wrote — or copied — a signalling server, an SDP/ICE exchange, peer-discovery logic, reconnection handling, and TURN credential plumbing. metered_realtime keeps your media + rendering code and replaces all of that plumbing with peer.join(channel).
This isn't a rip-and-replace: metered_realtime depends on flutter_webrtc. You keep getUserMedia, RTCVideoRenderer, RTCVideoView, and your widget tree. You delete the negotiation + signalling layer.
What you keep
| Your existing code | Still used |
|---|---|
navigator.mediaDevices.getUserMedia(...) | Yes — unchanged |
RTCVideoRenderer + RTCVideoView | Yes — unchanged |
track.enabled toggles for mute | Yes — unchanged |
getDisplayMedia for screen share | Yes — unchanged |
Android manifest / iOS Info.plist permissions | Yes — see Platform Setup |
What you delete
| Your existing code | Replaced by |
|---|---|
| Your signalling WebSocket + message protocol | The SDK's connection (auto-reconnect, ack, presence) |
RTCPeerConnection construction + lifecycle, one per peer | MeteredPeer manages one per remote |
createOffer / createAnswer / setLocalDescription / setRemoteDescription | Handled (perfect negotiation, including native rollback) |
onIceCandidate → send → addCandidate routing | Handled (ICE trickle over signalling) |
| Glare handling / "who offers first" tie-break | Handled (politeness from peer ids) |
| ICE restart on network change | Handled (9-attempt restart ladder) |
| Reconnect + re-negotiation after a drop | Handled (channel reconcile; your RemotePeer refs survive) |
Manual TURN credential fetch + RTCConfiguration | Auto-injected into the welcome |
Before / after
Adding your camera
// Before — you manage the PC and add tracks yourself, per peer:
final pc = await createPeerConnection(config);
final stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true});
for (final track in stream.getTracks()) {
await pc.addTrack(track, stream);
}
// ...then createOffer, setLocalDescription, send the SDP over your socket, etc.
// After — one call fans out to every current and future peer:
final stream = await navigator.mediaDevices.getUserMedia({'video': true, 'audio': true});
await peer.addStream(wrapMediaStream(stream));
The one new step: wrap the flutter_webrtc MediaStream with wrapMediaStream(...) before handing it to the SDK. (And wrapMediaStreamTrack(...) for a single track via addTrack.)
Receiving a remote track
// Before — your onTrack handler on each PC you created:
pc.onTrack = (RTCTrackEvent event) {
if (event.streams.isNotEmpty) {
remoteRenderer.srcObject = event.streams[0];
}
};
// After — one stream per remote, surfaced by the SDK:
remote.onStreamAdded.listen((ev) {
remoteRenderer.srcObject = (ev.stream as FlutterWebrtcMediaStream).native;
});
Inbound streams are flutter_webrtc-backed; unwrap with .native to get the same MediaStream you'd assign to a renderer.
Discovering peers
// Before — your own signalling messages drive this:
socket.onMessage = (msg) {
if (msg.type == 'peer-joined') createPeerConnectionFor(msg.peerId);
if (msg.type == 'peer-left') teardownPeerConnection(msg.peerId);
};
// After — presence-driven, with a ready-made RemotePeer:
peer.onPeerJoined.listen((remote) { /* wire renderers */ });
peer.onPeerLeft.listen((remote) { /* dispose renderer */ });
The mental-model shifts
- One
MeteredPeer= one channel, N connections. You don't think in individualRTCPeerConnections anymore; you think in peers in a channel. The PC is still there atremote.pcif you need it (e.g.getStats, data channels). - Media survives reconnects; you re-bind renderers. Your
RemotePeerreferences and added streams persist across a network drop — but theMediaStreamobject is fresh after a reconcile, so re-assignrenderer.srcObjecton everyonStreamAdded. This is the one habit to build. See Reconnect Best Practices. createDataChannelis still yours. If you used raw data channels, keep them — open viaremote.pc.createDataChannel(...)(now async) and optionally wrap withDataChannelfor backpressure. See Data Channels & Low Latency.- Signalling and media are independent.
peer.send/sendToare server-routed and work before (and regardless of) ICE — so chat/control messages don't depend on the P2P connection succeeding.
Keeping a custom WebRTC stack
If your peer topology doesn't fit MeteredPeer's 1:N channel model (an SFU client, a mesh with custom rules), you can still use the SDK purely as the signalling layer: drive SignallingClient directly for pub/sub + directed messages, and keep your own RTCPeerConnection management. You get auto-reconnect, ack correlation, presence, and TURN injection without the per-peer abstraction.
See also
- Getting Started — install + first call
- WebRTC Video Call — the full SDK-based call
MeteredPeerreference ·RemotePeerreference