Files
rtsp-simple-server/internal/protocols/webrtc/peer_connection_test.go
T
Alessandro Ros d5d1de0bd2 webrtc: strip TWCC extension of incoming RTP packets (#5146) (#5605)
The TWCC extension is used as part of the WebRTC congestion control
algorithm placed between the publisher and the server. If this
extension is routed untouched from the server to readers, it messes
with the congestion control algorithm present between the server and
each reader. Remove it.
2026-03-23 14:14:46 +01:00

889 lines
21 KiB
Go

package webrtc
import (
"net"
"regexp"
"sort"
"strings"
"testing"
"time"
"github.com/bluenviron/mediamtx/internal/test"
"github.com/pion/ice/v4"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v4"
"github.com/stretchr/testify/require"
)
type nilWriter struct{}
func (nilWriter) Write(p []byte) (int, error) {
return len(p), nil
}
var webrtcNilLogger = logging.NewDefaultLeveledLoggerForScope("", 0, &nilWriter{})
func gatherCodecs(tracks []*IncomingTrack) []webrtc.RTPCodecParameters {
codecs := make([]webrtc.RTPCodecParameters, len(tracks))
for i, track := range tracks {
codecs[i] = track.Codec()
}
return codecs
}
func senderHeaderExtensionID(params webrtc.RTPSendParameters, uri string) uint8 {
for _, ext := range params.HeaderExtensions {
if ext.URI == uri {
return uint8(ext.ID)
}
}
return 0
}
func TestPeerConnectionCloseImmediately(t *testing.T) {
pc := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err := pc.Start()
require.NoError(t, err)
defer pc.Close()
}
func TestPeerConnectionCloseImmediately2(t *testing.T) {
pc := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err := pc.Start()
require.NoError(t, err)
defer pc.Close()
_, err = pc.CreatePartialOffer()
require.NoError(t, err)
// wait for ICE candidates to be generated
time.Sleep(500 * time.Millisecond)
}
func TestPeerConnectionCandidates(t *testing.T) {
for _, ca := range []string{
"udp random",
"udp",
"tcp",
"stun",
"udp+stun",
"udp random+stun",
} {
t.Run(ca, func(t *testing.T) {
pc2, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pc2.Close() //nolint:errcheck
track, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
},
"video",
"publisher",
)
require.NoError(t, err)
_, err = pc2.AddTrack(track)
require.NoError(t, err)
offer, err := pc2.CreateOffer(nil)
require.NoError(t, err)
var udpMux ice.UDPMux
if ca == "udp" || ca == "udp+stun" {
var ln net.PacketConn
ln, err = net.ListenPacket("udp", ":3454")
require.NoError(t, err)
defer ln.Close()
udpMux = webrtc.NewICEUDPMux(webrtcNilLogger, ln)
}
var tcpMux *TCPMuxWrapper
if ca == "tcp" {
var ln net.Listener
ln, err = net.Listen("tcp", ":3454")
require.NoError(t, err)
defer ln.Close()
tcpMux = &TCPMuxWrapper{
Mux: webrtc.NewICETCPMux(webrtcNilLogger, ln, 8),
Ln: ln,
}
}
pc := &PeerConnection{
LocalRandomUDP: (ca == "udp random" || ca == "udp random+stun"),
ICEUDPMux: udpMux,
ICETCPMux: tcpMux,
IPsFromInterfaces: true,
IPsFromInterfacesList: []string{"lo"},
Log: test.NilLogger,
}
if ca == "stun" || ca == "udp+stun" || ca == "udp random+stun" {
pc.ICEServers = []webrtc.ICEServer{{
URLs: []string{"stun:stun.l.google.com:19302"},
}}
}
err = pc.Start()
require.NoError(t, err)
defer pc.Close()
answer, err := pc.CreateFullAnswer(&offer)
require.NoError(t, err)
n := len(regexp.MustCompile("(?m)^a=candidate:.+? udp .+? typ host").FindAllString(answer.SDP, -1))
if ca == "udp" || ca == "udp random" || ca == "udp+stun" || ca == "udp random+stun" {
require.Equal(t, 2, n)
} else {
require.Equal(t, 0, n)
}
n = len(regexp.MustCompile("(?m)^a=candidate:.+? tcp .+? typ host tcptype passive").FindAllString(answer.SDP, -1))
if ca == "tcp" {
require.Equal(t, 2, n)
} else {
require.Equal(t, 0, n)
}
n = len(regexp.MustCompile("(?m)^a=candidate:.+? udp .+? typ srflx").FindAllString(answer.SDP, -1))
if ca == "stun" || ca == "udp+stun" || ca == "udp random+stun" {
require.Equal(t, 2, n)
} else {
require.Equal(t, 0, n)
}
})
}
}
func TestPeerConnectionConnectivity(t *testing.T) {
for _, mode := range []string{
"passive udp",
"passive tcp",
"active udp",
"active udp + stun",
} {
for _, ip := range []string{
"from interfaces",
"additional hosts",
} {
// LocalRandomUDP doesn't work with AdditionalHosts
// we don't care since we are not currently using them together
if mode == "active udp" && ip == "additional hosts" {
continue
}
t.Run(mode+"_"+ip, func(t *testing.T) {
var iceServers []webrtc.ICEServer
if mode == "active udp + stun" {
iceServers = []webrtc.ICEServer{{
URLs: []string{"stun:stun.l.google.com:19302"},
}}
}
clientPC := &PeerConnection{
LocalRandomUDP: (mode == "passive udp" || mode == "active udp"),
IPsFromInterfaces: true,
IPsFromInterfacesList: []string{"lo"},
ICEServers: iceServers,
Log: test.NilLogger,
}
err := clientPC.Start()
require.NoError(t, err)
defer clientPC.Close()
var udpMux ice.UDPMux
var tcpMux *TCPMuxWrapper
switch mode {
case "passive udp":
var ln net.PacketConn
ln, err = net.ListenPacket("udp4", ":4458")
require.NoError(t, err)
defer ln.Close()
udpMux = webrtc.NewICEUDPMux(webrtcNilLogger, ln)
case "passive tcp":
var ln net.Listener
ln, err = net.Listen("tcp4", ":4458")
require.NoError(t, err)
defer ln.Close()
tcpMux = &TCPMuxWrapper{
Mux: webrtc.NewICETCPMux(webrtcNilLogger, ln, 8),
Ln: ln,
}
}
serverPC := &PeerConnection{
LocalRandomUDP: (mode == "active udp"),
ICEUDPMux: udpMux,
ICETCPMux: tcpMux,
ICEServers: iceServers,
Publish: true,
OutgoingTracks: []*OutgoingTrack{{
Caps: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeAV1,
ClockRate: 90000,
},
}},
Log: test.NilLogger,
}
if ip == "from interfaces" {
serverPC.IPsFromInterfaces = true
serverPC.IPsFromInterfacesList = []string{"lo"}
} else {
serverPC.AdditionalHosts = []string{"127.0.0.1"}
}
err = serverPC.Start()
require.NoError(t, err)
defer serverPC.Close()
offer, err := clientPC.CreatePartialOffer()
require.NoError(t, err)
answer, err := serverPC.CreateFullAnswer(offer)
require.NoError(t, err)
require.Equal(t, 2, strings.Count(answer.SDP, "a=candidate:"))
err = clientPC.SetAnswer(answer)
require.NoError(t, err)
go func() {
for {
select {
case cd := <-clientPC.NewLocalCandidate():
err2 := serverPC.AddRemoteCandidate(cd)
require.NoError(t, err2)
case <-clientPC.Failed():
return
}
}
}()
err = serverPC.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
})
}
}
}
func TestPeerConnectionRead(t *testing.T) {
pub, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pub.Close() //nolint:errcheck
videoTrack, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
},
"video",
"publisher")
require.NoError(t, err)
videoSender, err := pub.AddTrack(videoTrack)
require.NoError(t, err)
audioTrack, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: 48000,
},
"audio",
"publisher")
require.NoError(t, err)
audioSender, err := pub.AddTrack(audioTrack)
require.NoError(t, err)
reader := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err = reader.Start()
require.NoError(t, err)
defer reader.Close()
offer, err := pub.CreateOffer(nil)
require.NoError(t, err)
err = pub.SetLocalDescription(offer)
require.NoError(t, err)
answer, err := reader.CreateFullAnswer(&offer)
require.NoError(t, err)
err = pub.SetRemoteDescription(*answer)
require.NoError(t, err)
err = reader.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
go func() {
time.Sleep(100 * time.Millisecond)
err2 := videoTrack.WriteRTP(&rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{5, 2},
})
require.NoError(t, err2)
err2 = audioTrack.WriteRTP(&rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{5, 2},
})
require.NoError(t, err2)
}()
err = reader.GatherIncomingTracks(2 * time.Second)
require.NoError(t, err)
codecs := gatherCodecs(reader.IncomingTracks())
sort.Slice(codecs, func(i, j int) bool {
return codecs[i].PayloadType < codecs[j].PayloadType
})
require.Equal(t, []webrtc.RTPCodecParameters{
{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
RTCPFeedback: codecs[0].RTCPFeedback,
},
PayloadType: 96,
},
{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: 48000,
Channels: 2,
SDPFmtpLine: "minptime=10;useinbandfec=1",
RTCPFeedback: codecs[1].RTCPFeedback,
},
PayloadType: 111,
},
}, codecs)
reader.StartReading()
pkts, _, err := videoSender.ReadRTCP()
require.NoError(t, err)
require.Equal(t, []rtcp.Packet{
&rtcp.ReceiverReport{
SSRC: pkts[0].(*rtcp.ReceiverReport).SSRC,
Reports: []rtcp.ReceptionReport{{
SSRC: uint32(videoSender.GetParameters().Encodings[0].SSRC),
LastSequenceNumber: pkts[0].(*rtcp.ReceiverReport).Reports[0].LastSequenceNumber,
LastSenderReport: pkts[0].(*rtcp.ReceiverReport).Reports[0].LastSenderReport,
Delay: pkts[0].(*rtcp.ReceiverReport).Reports[0].Delay,
}},
ProfileExtensions: []byte{},
},
}, pkts)
pkts, _, err = audioSender.ReadRTCP()
require.NoError(t, err)
require.Equal(t, []rtcp.Packet{
&rtcp.ReceiverReport{
SSRC: pkts[0].(*rtcp.ReceiverReport).SSRC,
Reports: []rtcp.ReceptionReport{{
SSRC: uint32(audioSender.GetParameters().Encodings[0].SSRC),
LastSequenceNumber: pkts[0].(*rtcp.ReceiverReport).Reports[0].LastSequenceNumber,
LastSenderReport: pkts[0].(*rtcp.ReceiverReport).Reports[0].LastSenderReport,
Delay: pkts[0].(*rtcp.ReceiverReport).Reports[0].Delay,
}},
ProfileExtensions: []byte{},
},
}, pkts)
}
func TestPeerConnectionReadSimulcast(t *testing.T) {
pub, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pub.Close() //nolint:errcheck
videoTrackL, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
},
"video", "publisher",
webrtc.WithRTPStreamID("l"),
)
require.NoError(t, err)
videoTrackM, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
},
"video", "publisher",
webrtc.WithRTPStreamID("m"),
)
require.NoError(t, err)
videoTrackH, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
},
"video", "publisher",
webrtc.WithRTPStreamID("h"),
)
require.NoError(t, err)
transceiver, err := pub.AddTransceiverFromTrack(videoTrackL, webrtc.RTPTransceiverInit{
Direction: webrtc.RTPTransceiverDirectionSendonly,
SendEncodings: []webrtc.RTPEncodingParameters{
{RTPCodingParameters: webrtc.RTPCodingParameters{RID: "l"}},
{RTPCodingParameters: webrtc.RTPCodingParameters{RID: "m"}},
{RTPCodingParameters: webrtc.RTPCodingParameters{RID: "h"}},
},
})
require.NoError(t, err)
err = transceiver.Sender().AddEncoding(videoTrackM)
require.NoError(t, err)
err = transceiver.Sender().AddEncoding(videoTrackH)
require.NoError(t, err)
reader := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err = reader.Start()
require.NoError(t, err)
defer reader.Close()
offer, err := pub.CreateOffer(nil)
require.NoError(t, err)
err = pub.SetLocalDescription(offer)
require.NoError(t, err)
answer, err := reader.CreateFullAnswer(&offer)
require.NoError(t, err)
err = pub.SetRemoteDescription(*answer)
require.NoError(t, err)
err = reader.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
params := transceiver.Sender().GetParameters()
midExtID := senderHeaderExtensionID(params, sdp.SDESMidURI)
ridExtID := senderHeaderExtensionID(params, sdp.SDESRTPStreamIDURI)
require.NotZero(t, midExtID)
require.NotZero(t, ridExtID)
mid := transceiver.Mid()
go func() {
time.Sleep(200 * time.Millisecond)
layers := []struct {
track *webrtc.TrackLocalStaticRTP
rid string
ssrc uint32
seqNum uint16
}{
{videoTrackL, "l", 100001, 1000},
{videoTrackM, "m", 100002, 2000},
{videoTrackH, "h", 100003, 3000},
}
for i, layer := range layers {
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: layer.seqNum,
Timestamp: 45343,
SSRC: layer.ssrc,
},
Payload: []byte{5, 2},
}
pkt.ExtensionProfile = 0xBEDE
require.NoError(t, pkt.SetExtension(midExtID, []byte(mid)))
require.NoError(t, pkt.SetExtension(ridExtID, []byte(layer.rid)))
err2 := layer.track.WriteRTP(pkt)
if err2 != nil {
return
}
layers[i].seqNum++
}
}()
err = reader.GatherIncomingTracks(5 * time.Second)
require.NoError(t, err)
tracks := reader.IncomingTracks()
codecs := gatherCodecs(tracks)
require.Equal(t, 3, len(codecs))
for _, codec := range codecs {
require.Equal(t, webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
RTCPFeedback: codec.RTCPFeedback,
}, codec.RTPCodecCapability)
}
rids := make([]string, len(tracks))
for i, track := range tracks {
rids[i] = track.track.RID()
}
sort.Strings(rids)
require.Equal(t, []string{"h", "l", "m"}, rids)
}
func TestPeerConnectionStripIncomingTWCC(t *testing.T) {
pub, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pub.Close() //nolint:errcheck
videoTrack, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
ClockRate: 90000,
},
"video", "publisher",
webrtc.WithRTPStreamID("l"),
)
require.NoError(t, err)
videoSender, err := pub.AddTrack(videoTrack)
require.NoError(t, err)
reader := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err = reader.Start()
require.NoError(t, err)
defer reader.Close()
offer, err := pub.CreateOffer(nil)
require.NoError(t, err)
err = pub.SetLocalDescription(offer)
require.NoError(t, err)
answer, err := reader.CreateFullAnswer(&offer)
require.NoError(t, err)
err = pub.SetRemoteDescription(*answer)
require.NoError(t, err)
err = reader.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
params := videoSender.GetParameters()
twccExtID := senderHeaderExtensionID(params, twccExtensionURI)
require.NotZero(t, twccExtID)
go func() {
time.Sleep(200 * time.Millisecond)
pkt := &rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 96,
SequenceNumber: 55421,
Timestamp: 45343,
SSRC: 124123,
},
Payload: []byte{5, 2},
}
pkt.ExtensionProfile = 0xBEDE
require.NoError(t, pkt.SetExtension(twccExtID, []byte{0x12, 0x34}))
err2 := videoTrack.WriteRTP(pkt)
if err2 != nil {
return
}
}()
err = reader.GatherIncomingTracks(5 * time.Second)
require.NoError(t, err)
tracks := reader.IncomingTracks()
require.Len(t, tracks, 1)
done := make(chan struct{})
tracks[0].OnPacketRTP = func(p *rtp.Packet) {
require.False(t, p.Extension)
require.Empty(t, p.Extensions)
close(done)
}
reader.StartReading()
<-done
}
func TestPeerConnectionPublishRead(t *testing.T) {
pc1 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err := pc1.Start()
require.NoError(t, err)
defer pc1.Close()
pc2 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
OutgoingTracks: []*OutgoingTrack{
{
Caps: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264,
ClockRate: 90000,
},
},
{
Caps: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: 48000,
Channels: 2,
},
},
},
Log: test.NilLogger,
}
err = pc2.Start()
require.NoError(t, err)
defer pc2.Close()
offer, err := pc1.CreatePartialOffer()
require.NoError(t, err)
answer, err := pc2.CreateFullAnswer(offer)
require.NoError(t, err)
err = pc1.SetAnswer(answer)
require.NoError(t, err)
err = pc1.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
err = pc2.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
for _, track := range pc2.OutgoingTracks {
err = track.WriteRTP(&rtp.Packet{
Header: rtp.Header{
Version: 2,
Marker: true,
PayloadType: 111,
SequenceNumber: 1123,
Timestamp: 45343,
SSRC: 563424,
},
Payload: []byte{5, 2},
})
require.NoError(t, err)
}
err = pc1.GatherIncomingTracks(2 * time.Second)
require.NoError(t, err)
codecs := gatherCodecs(pc1.IncomingTracks())
sort.Slice(codecs, func(i, j int) bool {
return codecs[i].PayloadType < codecs[j].PayloadType
})
require.Equal(t, []webrtc.RTPCodecParameters{
{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264,
ClockRate: 90000,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
RTCPFeedback: codecs[0].RTCPFeedback,
},
PayloadType: 105,
},
{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus,
ClockRate: 48000,
Channels: 2,
SDPFmtpLine: "minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
RTCPFeedback: codecs[1].RTCPFeedback,
},
PayloadType: 111,
},
}, codecs)
}
// test that an audio codec is present regardless of the fact that an audio track is.
func TestPeerConnectionFallbackCodecs(t *testing.T) {
pc1 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: false,
Log: test.NilLogger,
}
err := pc1.Start()
require.NoError(t, err)
defer pc1.Close()
pc2 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
OutgoingTracks: []*OutgoingTrack{{
Caps: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeAV1,
ClockRate: 90000,
},
}},
Log: test.NilLogger,
}
err = pc2.Start()
require.NoError(t, err)
defer pc2.Close()
offer, err := pc1.CreatePartialOffer()
require.NoError(t, err)
answer, err := pc2.CreateFullAnswer(offer)
require.NoError(t, err)
var s sdp.SessionDescription
err = s.Unmarshal([]byte(answer.SDP))
require.NoError(t, err)
require.Equal(t, []*sdp.MediaDescription{
{
MediaName: sdp.MediaName{
Media: "video",
Port: sdp.RangedPort{Value: 9},
Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
Formats: []string{"97"},
},
ConnectionInformation: s.MediaDescriptions[0].ConnectionInformation,
Attributes: s.MediaDescriptions[0].Attributes,
},
{
MediaName: sdp.MediaName{
Media: "audio",
Port: sdp.RangedPort{Value: 9},
Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
Formats: []string{"0"},
},
ConnectionInformation: s.MediaDescriptions[1].ConnectionInformation,
Attributes: s.MediaDescriptions[1].Attributes,
},
}, s.MediaDescriptions)
}
func TestPeerConnectionPublishDataChannel(t *testing.T) {
pc1, err := webrtc.NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pc1.Close() //nolint:errcheck
_, err = pc1.CreateDataChannel("", nil)
require.NoError(t, err)
dataChanCreated := make(chan struct{})
dataReceived := make(chan struct{})
pc1.OnDataChannel(func(dc *webrtc.DataChannel) {
close(dataChanCreated)
dc.OnMessage(func(msg webrtc.DataChannelMessage) {
require.Equal(t, []byte("test data"), msg.Data)
close(dataReceived)
})
})
offer, err := pc1.CreateOffer(nil)
require.NoError(t, err)
err = pc1.SetLocalDescription(offer)
require.NoError(t, err)
pc2 := &PeerConnection{
LocalRandomUDP: true,
IPsFromInterfaces: true,
Publish: true,
OutgoingDataChannels: []*OutgoingDataChannel{
{
Label: "test-channel",
},
},
Log: test.NilLogger,
}
err = pc2.Start()
require.NoError(t, err)
defer pc2.Close()
answer, err := pc2.CreateFullAnswer(&offer)
require.NoError(t, err)
err = pc1.SetRemoteDescription(*answer)
require.NoError(t, err)
err = pc2.WaitUntilConnected(10 * time.Second)
require.NoError(t, err)
<-dataChanCreated
pc2.OutgoingDataChannels[0].Write([]byte("test data"))
<-dataReceived
}