Flutter SDK — metered_realtime
The official Flutter / Dart SDK for Metered Realtime Messaging. Speaks the same wire protocol as the JavaScript SDK and the raw WebSocket path — framing, reconnection, ack correlation, presence diffing, perfect-negotiation WebRTC, and TURN credential injection are all done for you, behind an idiomatic Dart API.
Published on pub.dev as metered_realtime.
dependencies:
metered_realtime: ^0.1.0
flutter_webrtc: ^1.4.1
WebRTC is provided by flutter_webrtc, so the SDK runs everywhere flutter_webrtc does: Android, iOS, web, macOS, Windows, and Linux. The signalling layer is pure Dart with a single injectable seam for testing.
Pick your path
The SDK ships two public classes. Pick by what you're building:
| You're building… | Use | Why |
|---|---|---|
| WebRTC video / voice call, screen share, peer-to-peer game | MeteredPeer | Joins a channel, discovers peers via presence, manages each RTCPeerConnection for you, fans media out automatically, recovers ICE on network changes |
| Live chat with presence, classroom roster, multiplayer lobby | MeteredPeer | The onPeerJoined / onPeerLeft streams drive your UI; peer.send(data) broadcasts or directs |
| IoT telemetry, MQTT-style pub/sub, AI agent message bus, collaborative cursors | SignallingClient | Pub/sub + directed messages without the WebRTC overhead. Smaller, simpler, multiple channels per connection |
Not sure? Start with MeteredPeer. Drop down to SignallingClient later if you find you don't need the channel-+-peer model.
Events are Dart Streams
The biggest difference from the JavaScript SDK: where JS uses an EventEmitter (peer.on('peer-joined', …)), the Flutter SDK exposes idiomatic broadcast Stream getters you .listen() to.
// JavaScript: peer.on('peer-joined', ({ peer: remote }) => …)
// Flutter:
peer.onPeerJoined.listen((remote) { … });
Each event maps to a Stream:
| Concept | JavaScript | Flutter |
|---|---|---|
| Peer joined | peer.on('peer-joined', …) | peer.onPeerJoined.listen((remote) {…}) |
| Inbound data | peer.on('data', …) | peer.onData.listen((MeteredData m) {…}) |
| State change | peer.on('state-change', …) | peer.stateChanges.listen((c) {…}) |
| Remote stream | remote.on('stream-added', …) | remote.onStreamAdded.listen((ev) {…}) |
Listeners returned by .listen() are StreamSubscriptions — cancel them in your widget's dispose() just like any other Dart stream. The SDK closes its controllers when you close() the peer.
What the SDK does for you
These are the things you'd write yourself if you used flutter_webrtc + a raw WebSocket — the SDK handles them:
- Framing + ack correlation — every
subscribe,publish,sendreturns aFuturethat completes when the server acks - Auto-reconnect with exponential backoff, jitter, and close-code-aware behaviour (terminal codes stop retrying, rate-limit codes slow-backoff)
- Token refresh — your
tokenProvideris re-invoked on every reconnect so JWT-rotated TURN creds + permissions land automatically - Inactivity watchdog — if the server stops sending frames, the SDK closes-and-reconnects instead of leaving you on a half-dead socket
- Presence diff — translates raw
presenceevents into per-peeronPeerJoined/onPeerLeft - Perfect negotiation — automatic SDP + ICE handling with a tie-breaker that prevents glare (including the native-platform rollback that browsers do implicitly)
- ICE-restart ladder — 9 retries spanning ~121 s for WebRTC-level recovery (Wi-Fi → cellular roam, TURN failover)
- Channel-level reconcile — when the signalling socket drops, your
RemotePeerreferences survive the reconnect; only the underlyingRTCPeerConnectionis swapped (see Reconnect Best Practices)
What your code still owns
The SDK does not make decisions on your behalf for things that affect UX or billing. You still own:
- Platform permissions — camera / microphone strings in the Android manifest + iOS
Info.plist. See Platform Setup - Rendering —
flutter_webrtcgives youRTCVideoRenderer+RTCVideoView; you decide the layout. See WebRTC Video Call - Auth mode + token minting — see Authentication
- Reconnect UI — the SDK fires
stateChanges; how (or whether) you surface a "reconnecting…" banner is up to you. See Reconnect Best Practices - Renderer re-bind on reconnect — after a reconnect the SDK surfaces a fresh
MediaStreamobject (same id). Re-assignrenderer.srcObjecton everyonStreamAdded
Where to start
| If you want to… | Go here |
|---|---|
| Get something running in 10 minutes | Getting Started |
| Configure Android / iOS / web | Platform Setup |
| Build a WebRTC video call | Guide: WebRTC Video Call |
| Build chat / presence / a lobby | Guide: Presence & Chat |
| Open a low-latency P2P data channel | Guide: Data Channels & Low Latency |
| Handle reconnects properly (production-grade) | Guide: Reconnect Best Practices |
Port from raw flutter_webrtc | Migration: from flutter_webrtc |
| Look up a method / stream | API Reference: MeteredPeer · SignallingClient |
Same protocol, every platform
A Flutter peer, a browser peer (JS SDK), and a raw-WebSocket client can all be peers in the same channel — the wire protocol is identical byte-for-byte. Build your call on Flutter and let web users join from the browser with no extra work.