-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Open
Description
Your environment.
- Version: v4.1.3
- Browser: Microsoft Edge v138.0.3351.65
- Browser is running on Windows 11.
- pion is running on the same machine inside WSL1 (Ubuntu 22.04).
What did you do?
When connecting from a browser to a pion, the time from ICEConnectionState: Checking
to Connected
differs significantly depending on who sends the offer.
I've attached the log:
when the browser issued the offer (browser_offer.txt), the connection was established in 3.9 ms,
but when the pion issued the offer (pion_offer.txt), it took 1.0 s.
What did you expect?
Similar ICE connection times regardless of which side acts as the offerer.
Reproducible Code
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"sync"
"time"
"github.com/pion/logging"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media"
"github.com/pion/webrtc/v4/pkg/media/h264reader"
"github.com/gorilla/websocket"
)
const (
videoFileName = "video.h264"
h264FrameDuration = time.Millisecond * 33
port = 8081
)
type Request struct {
Request string `json:"request"`
}
func printLog(message string) {
now := time.Now()
timestamp := now.Format("15:04:05.000")
fmt.Printf("[%s] %s\n", timestamp, message)
}
type customLogger struct {
subsystem string
}
func (c customLogger) log(level, msg string) {
printLog(fmt.Sprintf("[%s]<%s>%s", c.subsystem, level, msg))
}
func (c customLogger) logf(level, format string, args ...any) {
c.log(level, fmt.Sprintf(format, args...))
}
func (c customLogger) Trace(msg string) { c.log("trace", msg) }
func (c customLogger) Tracef(format string, args ...any) { c.logf("trace", format, args...) }
func (c customLogger) Debug(msg string) { c.log("debug", msg) }
func (c customLogger) Debugf(format string, args ...any) { c.logf("debug", format, args...) }
func (c customLogger) Info(msg string) { c.log("info", msg) }
func (c customLogger) Infof(format string, args ...any) { c.logf("info", format, args...) }
func (c customLogger) Warn(msg string) { c.log("warn", msg) }
func (c customLogger) Warnf(format string, args ...any) { c.logf("warn", format, args...) }
func (c customLogger) Error(msg string) { c.log("error", msg) }
func (c customLogger) Errorf(format string, args ...any) { c.logf("error", format, args...) }
type customLoggerFactory struct{}
func (c customLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
return customLogger{subsystem: subsystem}
}
func must(err error) {
if err != nil {
panic(err)
}
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Printf("Upgrade error:%v\n", err)
return
}
defer conn.Close()
var wsMutex sync.Mutex
m := &webrtc.MediaEngine{}
err = m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
RTCPFeedback: nil},
PayloadType: 96,
}, webrtc.RTPCodecTypeVideo)
must(err)
s := webrtc.SettingEngine{
LoggerFactory: customLoggerFactory{},
}
peerConnection, err := webrtc.NewAPI(
webrtc.WithMediaEngine(m),
webrtc.WithSettingEngine(s),
).NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
})
must(err)
iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background())
{
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, "video", "pion")
must(err)
_, err = peerConnection.AddTrack(videoTrack)
must(err)
go func() {
file, err := os.Open(videoFileName)
must(err)
h264, err := h264reader.NewReader(file)
must(err)
printLog("Waiting connection")
<-iceConnectedCtx.Done()
printLog("Start streaming")
ticker := time.NewTicker(h264FrameDuration)
for ; true; <-ticker.C {
nal, h264Err := h264.NextNAL()
if errors.Is(h264Err, io.EOF) {
printLog("All video frames parsed and sent")
os.Exit(0)
}
must(err)
err = videoTrack.WriteSample(media.Sample{Data: nal.Data, Duration: h264FrameDuration})
must(err)
}
}()
}
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
return
}
printLog(fmt.Sprintf("\n\nLocal ICE candidate:\n%v\n\n", candidate))
wsMutex.Lock()
err = conn.WriteJSON(candidate.ToJSON())
wsMutex.Unlock()
must(err)
})
var iceStartTime time.Time
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
switch connectionState {
case webrtc.ICEConnectionStateChecking:
iceStartTime = time.Now()
case webrtc.ICEConnectionStateConnected:
printLog(fmt.Sprintf("ICE connection time=%s", time.Since(iceStartTime)))
}
})
peerConnection.OnConnectionStateChange(func(connectionState webrtc.PeerConnectionState) {
if connectionState == webrtc.PeerConnectionStateConnected {
iceConnectedCtxCancel()
}
})
for {
_, message, err := conn.ReadMessage()
if err != nil {
printLog(fmt.Sprintf("Read error:%v", err))
break
}
var (
candidate webrtc.ICECandidateInit
sdp webrtc.SessionDescription
request Request
)
switch {
case json.Unmarshal(message, &sdp) == nil && sdp.SDP != "":
printLog(fmt.Sprintf("Remote SDP(%s):\n%s", sdp.Type, sdp.SDP))
err = peerConnection.SetRemoteDescription(sdp)
must(err)
if sdp.Type == webrtc.SDPTypeOffer {
answer, err := peerConnection.CreateAnswer(nil)
must(err)
err = peerConnection.SetLocalDescription(answer)
must(err)
printLog(fmt.Sprintf("Local SDP(%s):\n%s\n", answer.Type, answer.SDP))
wsMutex.Lock()
err = conn.WriteJSON(answer)
wsMutex.Unlock()
must(err)
}
case json.Unmarshal(message, &candidate) == nil && candidate.Candidate != "":
printLog(fmt.Sprintf("\n\nRemote ICE candidate:\n%v\n\n", candidate))
err = peerConnection.AddICECandidate(candidate)
must(err)
case json.Unmarshal(message, &request) == nil && request.Request != "":
if request.Request == "offer" {
offer, err := peerConnection.CreateOffer(nil)
must(err)
err = peerConnection.SetLocalDescription(offer)
must(err)
printLog(fmt.Sprintf("Local SDP(%s):\n%s\n", offer.Type, offer.SDP))
wsMutex.Lock()
err = conn.WriteJSON(offer)
wsMutex.Unlock()
must(err)
}
default:
printLog(fmt.Sprintf("Unknown message %s", message))
}
}
}
func main() {
if _, err := os.Stat(videoFileName); err != nil {
panic("Missing video file: " + videoFileName)
}
http.Handle("/", http.FileServer(http.Dir(".")))
http.HandleFunc("/websocket", wsHandler)
printLog(fmt.Sprintf("Open http://localhost:%d to access this demo", port))
panic(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
html:
<html>
<body>
<div id="remoteVideos"></div>
<br />
<div><button type="button" class="btn" onclick="sendOffer()">browser offer</button></div>
<br />
<div><button type="button" class="btn" onclick="requestOffer()">pion offer</button></div>
</body>
<script>
function sendOffer() {
pc.createOffer().then(offer => {
pc.setLocalDescription(offer)
socket.send(JSON.stringify(offer))
})
}
function requestOffer() {
socket.send(JSON.stringify({ "request": "offer" }))
}
const socket = new WebSocket(`ws://${window.location.host}/websocket`)
let pc = new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
})
socket.onmessage = e => {
console.log(e.data)
let msg = JSON.parse(e.data)
if (!msg) {
return console.log('failed to parse msg')
}
if (msg.candidate) {
pc.addIceCandidate(msg)
} else if (msg.type) {
pc.setRemoteDescription(msg)
if (msg.type === "offer") {
pc.createAnswer().then(answer => {
pc.setLocalDescription(answer)
socket.send(JSON.stringify(answer))
})
}
}
}
pc.onicecandidate = e => {
if (e.candidate && e.candidate.candidate !== "") {
socket.send(JSON.stringify(e.candidate))
}
}
pc.ontrack = function (event) {
var el = document.createElement(event.track.kind)
el.srcObject = event.streams[0]
el.autoplay = true
el.controls = true
document.getElementById('remoteVideos').appendChild(el)
}
pc.addTransceiver('video', { 'direction': 'recvonly' })
</script>
</html>
Metadata
Metadata
Assignees
Labels
No labels