Installation Issues
Go Modules Not Enabled
Go Modules are mandatory for using Pion WebRTC.
Symptom : Import errors or cannot find package errors
Solution :
export GO111MODULE = on
go mod init your-project-name
go get github.com/pion/webrtc/v4
Version Conflicts
Symptom : Dependency resolution errors or incompatible versions
Solution :
# Clean module cache
go clean -modcache
# Update dependencies
go get -u ./...
go mod tidy
# Verify versions
go list -m all | grep pion
Build Failures
Symptom : Compilation errors after installation
Solution :
Ensure you’re using Go 1.24.0 or later:
Verify import paths include the version:
import " github.com/pion/webrtc/v4 " // Correct
import " github.com/pion/webrtc " // Incorrect
Clear build cache:
Connection Issues
ICE Connection Fails
Symptom : PeerConnection stays in “checking” or “failed” state
Check #1: Network Configuration
Verify your ICE servers are configured: config := webrtc . Configuration {
ICEServers : [] webrtc . ICEServer {
{
URLs : [] string { "stun:stun.l.google.com:19302" },
},
},
}
peerConnection , err := webrtc . NewPeerConnection ( config )
Common issues:
Firewall blocking UDP traffic
Symmetric NAT requiring TURN
Corporate network restrictions
Solution : Add TURN serverconfig := webrtc . Configuration {
ICEServers : [] webrtc . ICEServer {
{
URLs : [] string { "stun:stun.l.google.com:19302" },
},
{
URLs : [] string { "turn:turn.example.com:3478" },
Username : "username" ,
Credential : "password" ,
},
},
}
Check #3: Port Availability
Verify ports are not blocked: # Test STUN connectivity
nc -u -v stun.l.google.com 19302
# Check if ports are available
netstat -an | grep LISTEN
Monitor ICE candidate gathering: peerConnection . OnICECandidate ( func ( candidate * webrtc . ICECandidate ) {
if candidate != nil {
log . Printf ( "ICE Candidate: %s " , candidate . String ())
} else {
log . Printf ( "ICE Gathering Complete" )
}
})
If no candidates are gathered, check network interfaces.
Connection Timeouts
Symptom : Connection takes too long or times out
Solutions :
Implement Trickle ICE :
peerConnection . OnICECandidate ( func ( candidate * webrtc . ICECandidate ) {
if candidate != nil {
// Send candidate immediately, don't wait for all
sendCandidateToRemotePeer ( candidate )
}
})
Set Timeout Values :
s := webrtc . SettingEngine {}
s . SetICETimeouts (
5 * time . Second , // Disconnect timeout
10 * time . Second , // Failed timeout
2 * time . Second , // Keepalive interval
)
Signaling Issues
Symptom : Offer/Answer exchange fails
Signaling is not part of the WebRTC specification and must be implemented separately.
Common Mistakes :
Not waiting for gathering :
// Wrong - creates offer immediately
offer , _ := peerConnection . CreateOffer ( nil )
// Correct - wait for ICE gathering if not using Trickle ICE
gatherComplete := webrtc . GatheringCompletePromise ( peerConnection )
offer , _ := peerConnection . CreateOffer ( nil )
peerConnection . SetLocalDescription ( offer )
<- gatherComplete
// Now send offer
Setting remote description before local :
// Ensure proper ordering
// 1. Create offer/answer
// 2. Set local description
// 3. Send to remote peer
// 4. Receive from remote peer
// 5. Set remote description
No Audio/Video Received
Symptom : Connection established but no media flows
Ensure OnTrack handler is registered: peerConnection . OnTrack ( func ( track * webrtc . TrackRemote , receiver * webrtc . RTPReceiver ) {
log . Printf ( "Track received: %s " , track . Codec (). MimeType )
for {
rtp , _ , err := track . ReadRTP ()
if err != nil {
return
}
// Process RTP packet
}
})
OnTrack must be set BEFORE creating the answer or setting remote description.
Verify both peers support the same codecs: // Check supported codecs
capabilities := webrtc . RTPCodecCapability {
MimeType : webrtc . MimeTypeH264 ,
}
// Or use SettingEngine to restrict codecs
m := & webrtc . MediaEngine {}
m . RegisterCodec ( webrtc . RTPCodecParameters {
RTPCodecCapability : webrtc . RTPCodecCapability {
MimeType : webrtc . MimeTypeH264 ,
},
}, webrtc . RTPCodecTypeVideo )
Ensure tracks are properly added: // Create track
videoTrack , err := webrtc . NewTrackLocalStaticSample (
webrtc . RTPCodecCapability { MimeType : webrtc . MimeTypeH264 },
"video" ,
"pion" ,
)
if err != nil {
return err
}
// Add to PeerConnection
rtpSender , err := peerConnection . AddTrack ( videoTrack )
if err != nil {
return err
}
Poor Video Quality
Symptom : Choppy video, artifacts, or low frame rate
Solutions :
Check Bandwidth :
peerConnection . OnConnectionStateChange ( func ( state webrtc . PeerConnectionState ) {
if state == webrtc . PeerConnectionStateConnected {
stats := peerConnection . GetStats ()
// Analyze bandwidth statistics
}
})
Adjust Bitrate :
// Use SettingEngine to configure bitrate
s := webrtc . SettingEngine {}
api := webrtc . NewAPI ( webrtc . WithSettingEngine ( s ))
Enable NACK/FEC :
m := & webrtc . MediaEngine {}
m . RegisterCodec ( webrtc . RTPCodecParameters {
RTPCodecCapability : webrtc . RTPCodecCapability {
MimeType : webrtc . MimeTypeH264 ,
ClockRate : 90000 ,
SDPFmtpLine : "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f" ,
},
}, webrtc . RTPCodecTypeVideo )
Audio/Video Out of Sync
Symptom : Audio and video timestamps don’t match
Solution :
Ensure proper timestamp handling:
startTime := time . Now ()
// For video
videoSample := media . Sample {
Data : videoData ,
Duration : time . Millisecond * 33 , // 30fps
Timestamp : time . Since ( startTime ),
}
videoTrack . WriteSample ( videoSample )
// For audio - use synchronized timestamp
audioSample := media . Sample {
Data : audioData ,
Duration : time . Millisecond * 20 ,
Timestamp : time . Since ( startTime ),
}
audioTrack . WriteSample ( audioSample )
Data Channel Issues
Data Channel Not Opening
Symptom : OnOpen callback never fires
Checklist :
Ensure both peers create or handle the data channel
Wait for connection to be established
Check for errors
// Peer A - Creates channel
dataChannel , err := peerConnection . CreateDataChannel ( "data" , nil )
if err != nil {
log . Fatal ( err )
}
dataChannel . OnOpen ( func () {
log . Println ( "Data channel opened" )
})
dataChannel . OnError ( func ( err error ) {
log . Printf ( "Data channel error: %v " , err )
})
// Peer B - Handles channel
peerConnection . OnDataChannel ( func ( d * webrtc . DataChannel ) {
log . Printf ( "New DataChannel: %s " , d . Label ())
d . OnOpen ( func () {
log . Println ( "Data channel opened" )
})
})
Message Send Failures
Symptom : SendText or Send returns an error
Common Causes :
Channel not open :
if dataChannel . ReadyState () != webrtc . DataChannelStateOpen {
log . Println ( "Channel not open yet" )
return
}
err := dataChannel . SendText ( "message" )
Buffer overflow :
// Check buffered amount
if dataChannel . BufferedAmount () > 16 * 1024 * 1024 {
log . Println ( "Buffer full, waiting..." )
time . Sleep ( 100 * time . Millisecond )
}
dataChannel . SendText ( "message" )
Message too large :
// SCTP has message size limits (typically 256KB)
// Send large data in chunks
maxMessageSize := 64 * 1024
for i := 0 ; i < len ( data ); i += maxMessageSize {
end := i + maxMessageSize
if end > len ( data ) {
end = len ( data )
}
dataChannel . Send ( data [ i : end ])
}
High CPU Usage
Symptom : Excessive CPU consumption
Solutions :
Profile your application :
import _ " net/http/pprof "
go func () {
log . Println ( http . ListenAndServe ( "localhost:6060" , nil ))
}()
Then visit: http://localhost:6060/debug/pprof/
Optimize packet processing :
// Use buffered channels
packetChan := make ( chan * rtp . Packet , 100 )
// Batch processing
ticker := time . NewTicker ( 10 * time . Millisecond )
for range ticker . C {
// Process accumulated packets
}
Reduce logging :
// Disable verbose logging in production
s := webrtc . SettingEngine {}
s . LoggerFactory = nil // Or use custom logger with filtering
Memory Leaks
Symptom : Growing memory usage over time
Common Causes :
Not closing PeerConnections :
defer peerConnection . Close ()
Goroutine leaks :
// Always ensure goroutines can exit
ctx , cancel := context . WithCancel ( context . Background ())
defer cancel ()
go func () {
for {
select {
case <- ctx . Done ():
return
default :
// Process
}
}
}()
Track reader not stopping :
peerConnection . OnTrack ( func ( track * webrtc . TrackRemote , receiver * webrtc . RTPReceiver ) {
go func () {
defer func () {
log . Println ( "Track reader exiting" )
}()
for {
_ , _ , err := track . ReadRTP ()
if err != nil {
return // Exit on error
}
}
}()
})
High Memory Usage
Solutions :
Pool buffers :
var bufferPool = sync . Pool {
New : func () interface {} {
return make ([] byte , 1500 )
},
}
buffer := bufferPool . Get ().([] byte )
defer bufferPool . Put ( buffer )
Limit concurrent connections :
semaphore := make ( chan struct {}, 100 ) // Max 100 connections
semaphore <- struct {}{}
defer func () { <- semaphore }()
Debugging Tips
Enable Debug Logging
import " github.com/pion/logging "
logger := logging . NewDefaultLoggerFactory ()
s := webrtc . SettingEngine {}
s . LoggerFactory = logger
api := webrtc . NewAPI ( webrtc . WithSettingEngine ( s ))
Monitor Connection State
peerConnection . OnConnectionStateChange ( func ( state webrtc . PeerConnectionState ) {
log . Printf ( "Connection State: %s " , state . String ())
switch state {
case webrtc . PeerConnectionStateFailed :
log . Println ( "Connection failed" )
// Debug ICE state
case webrtc . PeerConnectionStateClosed :
log . Println ( "Connection closed" )
}
})
peerConnection . OnICEConnectionStateChange ( func ( state webrtc . ICEConnectionState ) {
log . Printf ( "ICE State: %s " , state . String ())
})
Use Wireshark
Capture and analyze WebRTC traffic:
# Capture STUN/TURN traffic
sudo tcpdump -i any -w webrtc.pcap udp port 3478 or udp port 19302
# Analyze in Wireshark with filters:
# stun
# rtp
# rtcp
Check Statistics
stats := peerConnection . GetStats ()
for _ , stat := range stats {
switch s := stat .( type ) {
case * webrtc . InboundRTPStreamStats :
log . Printf ( "Inbound: packets= %d , bytes= %d , lost= %d " ,
s . PacketsReceived , s . BytesReceived , s . PacketsLost )
case * webrtc . OutboundRTPStreamStats :
log . Printf ( "Outbound: packets= %d , bytes= %d " ,
s . PacketsSent , s . BytesSent )
}
}
Docker Networking
Symptom : Connections fail in Docker containers
Solution :
Use host networking:
docker run --network host your-image
Or properly map ports:
docker run -p 8080:8080 -p 50000-50010:50000-50010/udp your-image
Set NAT 1:1 mapping:
s := webrtc . SettingEngine {}
s . SetNAT1To1IPs ([] string { "your.public.ip" }, webrtc . ICECandidateTypeHost )
WebAssembly Issues
Symptom : WASM build or runtime errors
Solution :
Use correct build command:
GOOS = js GOARCH = wasm go build -o demo.wasm main.go
Include wasm_exec.js:
cp "$( go env GOROOT)/misc/wasm/wasm_exec.js" .
Serve with proper MIME type:
// In your HTTP server
if strings . HasSuffix ( path , ".wasm" ) {
w . Header (). Set ( "Content-Type" , "application/wasm" )
}
Getting More Help
Community Support Ask questions in Discord
FAQ Check frequently asked questions
GitHub Issues Report bugs or request features
Examples Study working examples
When asking for help, include:
Go version (go version)
Pion version (go list -m github.com/pion/webrtc/v4)
Minimal reproducible example
Relevant logs and error messages
Network topology (NAT, firewall, etc.)