- 
                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