metered-realtime β Python WebRTC Library: Signalling, TURN & Multi-Peer on aiortc
metered-realtime is a free, open-source (MIT) Python WebRTC library built on aiortc, the standard asynchronous WebRTC implementation for Python. It provides the production capabilities aiortc leaves to the application β signalling, multi-peer orchestration, auto-reconnect, and TURN β behind a concise asyncio API. Where aiortc manages a single RTCPeerConnection, metered-realtime manages every peer in a channel: it discovers them over managed WebSocket signalling and injects free TURN credentials at connect time, allowing a headless Python service to participate in the same channel as your browser clients.
Async/asyncio-native, production-stable (1.0.0), MIT.
pip install metered-realtime
Server-side and headless WebRTCβ
The browser SDK targets interactive, user-facing applications. metered-realtime targets everything else β servers, containers, edge devices, and CI environments β enabling WebRTC participants that cannot run in a browser:
- AI voice agents β transcribe a caller's audio (STT), generate a response with an LLM, synthesize it (TTS), and stream it back into the call as a participant.
- IoT and telemetry bridges β publish sensor data or device audio/video from a headless device.
- Recording and transcription services β a passive participant that subscribes to every track and writes it to storage.
- Server-authoritative media β mix, transcode, or moderate streams before they reach other peers.
Because it implements the same wire protocol as the JavaScript SDK, a Python peer and a browser peer share the same channel β the agent communicates directly with users, with no intermediary gateway.
Joining a channel and receiving mediaβ
With raw aiortc, you create an RTCPeerConnection and then implement the signalling channel, the offer/answer exchange, ICE handling, peer-presence tracking, reconnection, and TURN-credential retrieval yourself. With metered-realtime, you join() a channel and respond to peers through event handlers:
import asyncio
from metered_realtime import MeteredPeer, PeerJoined, Track, iter_frames
async def main():
async with MeteredPeer(api_key="pk_live_...") as peer:
@peer.on(PeerJoined)
def on_peer(ev):
print(f"{ev.peer.id} joined")
@ev.peer.on(Track)
async def on_track(t):
# t.track is an aiortc MediaStreamTrack
async for frame in iter_frames(t.track):
process(frame) # feed STT, write to disk, transcodeβ¦
await peer.join("room-42") # presence + discovery; TURN auto-applied
await asyncio.Future() # run until cancelled
asyncio.run(main())
Signalling runs over the free Metered Realtime server, and TURN is provided by Open Relay and injected automatically. There is no infrastructure to host.
Publishing audio (AI voice agents)β
To send media β a synthesized TTS reply, a generated voice, or a file β push PCM into an AudioSource and add it as a track. Every peer in the channel then receives it as it would any other participant's microphone:
import asyncio
from metered_realtime import MeteredPeer, AudioSource
async def main():
async with MeteredPeer(api_key="pk_live_...") as peer:
source = AudioSource(input_rate=16_000) # 16 kHz mono PCM in
peer.add_track(source) # sent to every peer in the channel
await peer.join("room-42")
async for chunk in tts_stream(reply_text): # your STT β LLM β TTS pipeline
await source.push(chunk) # emitted as real-time 48 kHz audio
source.end()
asyncio.run(main())
The remote browser peer receives this like any other participant; no special handling is required on the JavaScript side. A file or IP camera can be streamed the same way with from_file(...) or from_rtsp(...).
Get your free keyβ
The SDK's api_key is your signalling publishable key (pk_live_...) β the same key the JavaScript SDK uses, separate from the TURN REST API_KEY used to fetch Open Relay credentials directly. With metered-realtime the publishable key is all you need: TURN is auto-injected, so there is no second key to manage. Both are free. Sign up for a free Metered account, then:
- Go to Dashboard β Realtime Messaging β Keys β Create key and choose the Publishable type.
- Under "What can this key do?", ensure
Sendis checked.Subscribe,Publish, andPresenceare enabled by default β leave them on. - Copy the
pk_live_β¦value and pass it asapi_key.
Send is off by default β enable it
For publishable keys, Send is unchecked by default. metered-realtime uses it to exchange SDP and ICE candidates between peers, so without it peer.join() and peer.add_track() succeed but the media never negotiates β peers never establish their connection. This is the most common reason a first call connects with no audio.
Full walkthrough: WebRTC quickstart β
Built on aiortcβ
metered-realtime does not replace aiortc; it builds on it. Every peer connection, track, and codec is aiortc, and you retain full access to it (see "Can I still use aiortc directly?" below). The library adds the capabilities aiortc intentionally leaves to the application:
| Production WebRTC requirement | Raw aiortc | metered-realtime |
|---|---|---|
| Peer connection, SRTP, codecs | β (this is aiortc) | β (uses aiortc) |
| Signalling transport | Implement yourself | β Free, managed (WebSocket) |
| Offer/answer + ICE exchange | Implement yourself | β Automatic (perfect negotiation) |
| Multi-peer channels + presence | Implement yourself | β Join/leave events |
| Auto-reconnect on network change | Implement yourself | β WebSocket + ICE restart |
| TURN credentials | Fetch and embed yourself | β Auto-injected (20 GB/mo free) |
| Browser / JavaScript interoperability | Implement the protocol | β Same wire protocol |
| Async / asyncio | β | β |
| License | BSD-3 | MIT |
If you already use aiortc directly, metered-realtime is the layer you would otherwise implement by hand. It can be adopted incrementally, and you can drop down to the underlying aiortc objects whenever required.
Auto-reconnect and perfect negotiationβ
Long-running connections fail when the underlying network changes. metered-realtime recovers automatically across three layers:
- WebSocket reconnect with exponential backoff.
- ICE-restart ladder β re-establishes the media path after NAT rebinds and network changes.
- Identity-preserving reconcile β your
RemotePeerreferences remain valid through a transient drop; the underlying connection is replaced transparently.
It also implements perfect negotiation, eliminating glare and initiator-role race conditions β particularly important server-side, where a single process may answer many peers concurrently.
Sending data messagesβ
Send structured messages to every peer in a channel, or to a single peer:
from metered_realtime import Data
@peer.on(Data)
def on_data(ev):
print(ev.sender_peer_id, ev.data)
await peer.send({"type": "chat", "text": "hi from the agent"}) # broadcast
await peer.send_to(other_peer_id, {"ping": 1}) # direct to one peer
The whole stack is freeβ
- SDK β free, open source (MIT), built on aiortc.
- Signalling β Metered Realtime, free up to 100 connections and 100,000 messages per month.
- TURN β Open Relay, 20 GB/month free.
No credit card required to get started.
Get a free keyOne SDK family, every platformβ
Each SDK shares the same wire protocol and the same free signalling and TURN, so a Python service and a browser application interoperate without additional glue code.
| Platform | Status |
|---|---|
| Python (server / headless, on aiortc) | β Available now |
| JavaScript / TypeScript (browser + Node 18+) | β Available now |
| React Native | β Available now |
| Flutter | π Coming soon |
| iOS (Swift) / Android (Kotlin) | π Coming soon |
FAQβ
Is it production-ready?
Yes β metered-realtime is 1.0.0, production-stable, MIT-licensed, and built on the mature aiortc library. The signalling and TURN it depends on are the same managed services behind Metered's production browser SDK.
Does it work with FastAPI, Django, or other asyncio applications?
Yes. It is async/asyncio-native and integrates with any event-loop application β run a peer inside a FastAPI lifespan, a Django ASGI application, a worker process, or a plain asyncio.run(). It does not own the event loop, so it runs alongside your other coroutines.
Can I still use aiortc directly?
Yes. Tracks you receive are aiortc MediaStreamTrack instances, and you can access the underlying connection when you need a codec, transceiver, or statistic the high-level API does not expose. metered-realtime adds a layer without restricting access to the one beneath it.
How does this differ from using aiortc on its own?
aiortc manages a single peer connection and deliberately leaves signalling, multi-peer channels, reconnection, and TURN to the application. metered-realtime provides exactly those β managed WebSocket signalling, presence, auto-reconnect, and auto-injected free TURN β so you do not implement a signalling server and reconnection logic for each project. See the comparison above.
Can a Python peer join the same channel as my browser users? Yes. The Python and JavaScript SDKs implement the same wire protocol, so a headless Python service and a browser participant are peers in the same channel β suitable for AI voice agents, recording services, and server-side mixing.
Do I need to run a signalling server or a TURN server? No. Signalling is the managed, free Metered Realtime service, and Open Relay TURN credentials are auto-injected at connect time (20 GB/month free). There is no infrastructure to host.
metered-realtime is built on aiortc, the free Metered Realtime signalling server, and the Open Relay TURN server β a complete, free, open-source-first WebRTC stack for Python.