Pure-Go WebRTC. Pass ICEServers in your webrtc.Configuration.
// go get github.com/pion/webrtc/v4
package main
import "github.com/pion/webrtc/v4"
func newPC() (*webrtc.PeerConnection, error) {
cfg := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{ URLs: []string{"stun:stun.expressturn.com:3478"} },
{
URLs: []string{
"turn:relay1.expressturn.com:3478?transport=udp",
"turn:relay1.expressturn.com:3478?transport=tcp",
"turns:relay1.expressturn.com:443?transport=tcp",
},
Username: "YOUR_EXPRESSTURN_USERNAME",
Credential: "YOUR_EXPRESSTURN_PASSWORD",
},
},
ICETransportPolicy: webrtc.ICETransportPolicyAll,
}
return webrtc.NewPeerConnection(cfg)
}
cfg.ICETransportPolicy = webrtc.ICETransportPolicyRelay
This forces every connection through TURN, useful for catching configuration mistakes early.
If you're running an authoritative game server (Pion is popular for this), the server's PeerConnection usually doesn't need TURN, it has a public address. Add TURN to the client's ICE config so connecting players get a relay path.
Embedded uses (IoT cameras, set-top boxes) are different, those endpoints sit behind NAT and absolutely need TURN.
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"time"
)
func turnCreds(secret, userID string) (username, credential string) {
expiry := time.Now().Unix() + 3600
username = fmt.Sprintf("%d:%s", expiry, userID)
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(username))
credential = base64.StdEncoding.EncodeToString(h.Sum(nil))
return
}
See shared-secret examples in other languages.
v2/webrtc. The API for ICEServers is largely the same, but check your import paths.defer pc.Close() on connection failure paths.Related: Janus recipe · TURN for multiplayer games · TURN for IP cameras