Troubleshooting One-Way Audio and Video in Realtime Avatar Sessions with STUN/TURN

Debug one-way audio/video in WebRTC avatar sessions: ICE, STUN/TURN, candidate pairs, autoplay, and track plumbing.
Introduction
One-way audio or video in a realtime avatar session usually means the media path is only partially reachable. In practice, the signaling channel may be fine, the session may even “connect,” but one peer cannot successfully establish a usable UDP path for RTP/RTCP. With WebRTC-based systems, that almost always comes down to ICE candidate selection, NAT behavior, firewall policy, or a mismatched expectation about which side should be sending media.
In this post, I’ll focus on how to debug those failures systematically: how STUN and TURN actually fit into the connection setup, how to tell whether the problem is network traversal versus application logic, and what to check when an avatar session shows video but no audio, or audio but no video. I’ll also show where Protoface fits into the picture for developer-facing avatar sessions, especially when the avatar is embedded into a voice agent or web app.
Start with the WebRTC mental model
Realtime avatar sessions are typically delivered over WebRTC or a WebRTC-adjacent transport. That means you should think in terms of:
Signaling: exchanging session metadata, credentials, and SDP.
ICE: discovering possible network paths between endpoints.
STUN: helping each side learn its public-facing address/port mapping.
TURN: relaying media when direct peer-to-peer connectivity fails.
When everything works, the browser or agent endpoint gathers candidates, performs ICE checks, and selects a viable pair. If both sides are behind restrictive NATs or firewalls, the selected pair may be a TURN relay. If the network blocks UDP entirely, TURN over TCP or TLS may be the only workable path.
“One-way audio” and “one-way video” are often misdiagnosed as codec issues. In reality, codec mismatches usually prevent decoding on both sides, or produce obvious corruption. One-way media more commonly points to a transport path problem on one direction of the RTP flow, often because the sender can transmit but the receiver cannot be reached, or because the receiver cannot send RTCP feedback and the sender concludes the path is dead.
How to isolate the failure
Debugging should proceed from the outside in:
Verify signaling succeeds. If you never see SDP offer/answer exchange complete, you do not have a media problem yet.
Inspect ICE state. You want to know whether the connection is still gathering, checking, connected, completed, failed, or disconnected.
Identify the selected candidate pair. Direct host/srflx paths behave differently from TURN-relayed paths.
Confirm media directionality. Check whether audio/video tracks are actually being sent, received, and attached to the right sinks.
On the browser side, the most useful signals are the ICE connection state and the selected candidate pair. If you are debugging in a client application, log these early:
If the state gets to connected but media is still one-way, the path is nominally established and you should inspect track plumbing, autoplay policies, and whether the remote peer is actually producing media. If it never leaves checking or fails intermittently, the network path itself is suspect.
Also check whether the failure is asymmetric by direction. For example:
Avatar video visible, no audio: audio track may not be subscribed, the audio element may be blocked by autoplay restrictions, or the sender may be muted.
Audio works, no video: video track may not be negotiated, the browser may be dropping frames due to bandwidth constraints, or the remote video sink may not be attached.
Only one browser or one network breaks: NAT/firewall policy or UDP filtering is likely.
STUN: useful, but not sufficient
STUN exists to discover the public mapping created by your NAT. It does not relay media. So if a connection works on a home network but fails from enterprise Wi-Fi, it may be because the enterprise NAT is symmetric, the firewall is stateful in a way that blocks inbound responses, or UDP is rate-limited.
In practice, STUN is necessary for getting candidates and often enough for simple NATs, but it is not a robustness feature. If both peers can open usable UDP pinholes, direct connectivity is cheaper and lower latency. If not, STUN alone will not save you.
Two common misconceptions:
“We have a public IP, so STUN doesn’t matter.” It still may, because the effective route depends on the actual port mapping and the network in between.
“If signaling works, media should too.” Signaling is usually HTTPS/WSS; media is usually UDP and often subject to different firewall rules.
When you see a session connect and then one direction stays silent, ask whether the sender can reach the receiver on the candidate pair that ICE selected. If the answer is no, direct media is not viable and TURN becomes the fallback.
TURN: the fallback that actually fixes many “one-way” cases
TURN relays media through a server that both sides can reach. That makes it more expensive than direct connectivity, but it is the right tool when NAT traversal fails. A good mental model: STUN tells you “where you appear to be,” while TURN gives you “a place you can both reach.”
For troubleshooting, TURN is often the fastest way to distinguish network-traversal failure from application bugs. If forcing TURN makes the problem disappear, the avatar session itself is likely fine and the issue is direct path reachability.
In real deployments, TURN also helps with:
Corporate networks that block unknown UDP traffic
Mobile carriers with aggressive NAT behavior
Browser-to-browsing or browser-to-agent connections where both ends are behind NAT
Long-lived sessions where NAT mappings expire unexpectedly
If you operate your own WebRTC infrastructure, make sure your TURN servers support the transport modes your customers actually need. UDP is preferred for latency, but TCP and TLS relays are sometimes the only options. If the environment is hostile enough that UDP fails, video may become choppy while audio still works, or audio may work and video may not, depending on bandwidth and relay overhead.
Common causes of one-way media in avatar sessions
The same symptom can come from different layers. I’d check these in roughly this order:
Track attachment bugs: the remote track is received but never attached to the audio element or render pipeline.
Autoplay restrictions: browsers may block audio playback until the page gets a user gesture.
ICE candidate mismatch: the connection succeeded with one candidate pair, but that pair cannot sustain media.
Firewall asymmetry: outbound media works but return traffic is dropped.
Codec or SDP negotiation errors: especially if one track type negotiates and the other does not.
Bandwidth adaptation: the path exists, but the video sender is being throttled hard enough that it looks absent.
When debugging browser playback, don’t forget the simple stuff. If the remote audio track is present in the WebRTC stats but the user hears nothing, inspect the actual sink element and browser autoplay status before blaming networking. Similarly, if the remote video track is arriving but the UI shows a blank box, check whether the render surface is hidden, zero-sized, or never attached.
Useful browser-side checks
For a quick first pass, compare what the browser thinks is happening with what the user experiences. You can inspect stats and track state directly:
If packetsReceived stays at zero for one media kind, the issue is upstream of rendering. If packets arrive but the user still hears or sees nothing, the problem is downstream in the application layer.
Also watch the candidate types:
host: local interface address; often not usable across NATs
srflx: server-reflexive, learned via STUN; useful across many NATs
relay: TURN-relayed; slower, but reliable
If a session only works when the selected pair is relay, you have a network traversal problem, not an avatar problem.
What this looks like in a realtime avatar integration
For developer-facing avatar sessions, the practical fix is often to use an integration that already handles the media plumbing and exposes the right diagnostics. In the LiveKit path, the Protoface plugin for Pipecat and the broader agent stack let you embed an avatar into a voice agent without reimplementing the video face pipeline yourself. The key is still the same: confirm whether the session is reaching a direct ICE path or falling back to relay, and then validate that the audio/video tracks are actually attached and flowing.
For example, if you are wiring an agent in Python, the code pattern should make it easy to separate session creation from media debugging:
Or, if you’re doing session management via the REST API, you can verify that the backend is creating the session you expect before you start chasing browser state:
The point of using the API or SDK here is not that they magically fix transport issues. It is that they give you a consistent session lifecycle so you can focus on the media layer: whether the avatar session started, whether the client joined, and whether the media path is direct or relayed. If you need implementation details for the exact fields and integration points, the docs are the right source of truth: docs.protoface.com.
Practical remediation checklist
When a customer reports one-way audio or video, I’d usually go through this sequence:
Reproduce on a known-good network first, then on the problematic network.
Log ICE state transitions and selected candidate pairs.
Confirm whether the working case uses host/srflx and the broken case fails before relay.
Force TURN temporarily; if the problem disappears, the network path is the issue.
Check browser autoplay, track attachment, and element visibility.
Inspect WebRTC stats to see whether packets are arriving for the missing direction.
If you operate your own TURN infrastructure, keep an eye on relay capacity and transport support. If you rely on a managed avatar stack, make sure your embedding or agent integration preserves the browser’s ability to negotiate relay candidates and that your users’ networks aren’t blocking the chosen transport.
Conclusion
One-way audio and video are almost always symptoms of a broken media path, not a mysterious avatar bug. The fastest way to debug them is to separate signaling from media, inspect ICE state and candidate selection, and use TURN as a deliberate fallback when direct traversal fails. Then confirm that the track is actually attached, rendered, and allowed to play.
If you’re building realtime avatar sessions and want a clean integration surface for sessions, agents, or browser embeds, start with the docs and quickstarts, reproduce the issue on a controlled network, and check whether the session is going direct or relayed. That usually turns an opaque “it only fails for some users” report into a concrete network or application-layer fix.
For implementation details and examples, see docs.protoface.com and the relevant quickstarts in the GitHub repos linked there.
