Skip to main content

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
Read the SDK docsView on PyPI

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:

  1. Go to Dashboard β†’ Realtime Messaging β†’ Keys β†’ Create key and choose the Publishable type.
  2. Under "What can this key do?", ensure Send is checked. Subscribe, Publish, and Presence are enabled by default β€” leave them on.
  3. Copy the pk_live_… value and pass it as api_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 requirementRaw aiortcmetered-realtime
Peer connection, SRTP, codecsβœ… (this is aiortc)βœ… (uses aiortc)
Signalling transportImplement yourselfβœ… Free, managed (WebSocket)
Offer/answer + ICE exchangeImplement yourselfβœ… Automatic (perfect negotiation)
Multi-peer channels + presenceImplement yourselfβœ… Join/leave events
Auto-reconnect on network changeImplement yourselfβœ… WebSocket + ICE restart
TURN credentialsFetch and embed yourselfβœ… Auto-injected (20 GB/mo free)
Browser / JavaScript interoperabilityImplement the protocolβœ… Same wire protocol
Async / asyncioβœ…βœ…
LicenseBSD-3MIT

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 RemotePeer references 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 key

One 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.

PlatformStatus
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.