mirror of
https://github.com/pion/webrtc.git
synced 2025-10-16 20:20:53 +08:00

Before we computed DataChannel IDs before signaling, this is incorrect because IDs must take into account if we are running an DTLS Client or Server. This updates the DataChannel ID generation code to take this into account before generating a streamId. Resolves #908
903 lines
21 KiB
Go
903 lines
21 KiB
Go
// +build !js
|
|
|
|
package webrtc
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"reflect"
|
|
"regexp"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/rtcp"
|
|
"github.com/pion/sdp/v2"
|
|
"github.com/pion/transport/test"
|
|
"github.com/pion/webrtc/v2/pkg/media"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func offerMediaHasDirection(offer SessionDescription, kind RTPCodecType, direction RTPTransceiverDirection) bool {
|
|
for _, media := range offer.parsed.MediaDescriptions {
|
|
if media.MediaName.Media == kind.String() {
|
|
_, exists := media.Attribute(direction.String())
|
|
return exists
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/*
|
|
Integration test for bi-directional peers
|
|
|
|
This asserts we can send RTP and RTCP both ways, and blocks until
|
|
each side gets something (and asserts payload contents)
|
|
*/
|
|
// nolint: gocyclo
|
|
func TestPeerConnection_Media_Sample(t *testing.T) {
|
|
const (
|
|
expectedTrackID = "video"
|
|
expectedTrackLabel = "pion"
|
|
)
|
|
|
|
api := NewAPI()
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
awaitRTPRecv := make(chan bool)
|
|
awaitRTPRecvClosed := make(chan bool)
|
|
awaitRTPSend := make(chan bool)
|
|
|
|
awaitRTCPSenderRecv := make(chan bool)
|
|
awaitRTCPSenderSend := make(chan error)
|
|
|
|
awaitRTCPReceiverRecv := make(chan error)
|
|
awaitRTCPReceiverSend := make(chan error)
|
|
|
|
trackMetadataValid := make(chan error)
|
|
|
|
pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
|
|
if track.ID() != expectedTrackID {
|
|
trackMetadataValid <- fmt.Errorf("Incoming Track ID is invalid expected(%s) actual(%s)", expectedTrackID, track.ID())
|
|
return
|
|
}
|
|
|
|
if track.Label() != expectedTrackLabel {
|
|
trackMetadataValid <- fmt.Errorf("Incoming Track Label is invalid expected(%s) actual(%s)", expectedTrackLabel, track.Label())
|
|
return
|
|
}
|
|
close(trackMetadataValid)
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 100)
|
|
if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: track.SSRC(), MediaSSRC: track.SSRC()}}); routineErr != nil {
|
|
awaitRTCPReceiverSend <- routineErr
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-awaitRTCPSenderRecv:
|
|
close(awaitRTCPReceiverSend)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
_, routineErr := receiver.Read(make([]byte, 1400))
|
|
if routineErr != nil {
|
|
awaitRTCPReceiverRecv <- routineErr
|
|
} else {
|
|
close(awaitRTCPReceiverRecv)
|
|
}
|
|
}()
|
|
|
|
haveClosedAwaitRTPRecv := false
|
|
for {
|
|
p, routineErr := track.ReadRTP()
|
|
if routineErr != nil {
|
|
close(awaitRTPRecvClosed)
|
|
return
|
|
} else if bytes.Equal(p.Payload, []byte{0x10, 0x00}) && !haveClosedAwaitRTPRecv {
|
|
haveClosedAwaitRTPRecv = true
|
|
close(awaitRTPRecv)
|
|
}
|
|
}
|
|
})
|
|
|
|
vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), expectedTrackID, expectedTrackLabel)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rtpReceiver, err := pcOffer.AddTrack(vp8Track)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 100)
|
|
if routineErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); routineErr != nil {
|
|
fmt.Println(routineErr)
|
|
}
|
|
|
|
select {
|
|
case <-awaitRTPRecv:
|
|
close(awaitRTPSend)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 100)
|
|
if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: vp8Track.SSRC(), MediaSSRC: vp8Track.SSRC()}}); routineErr != nil {
|
|
awaitRTCPSenderSend <- routineErr
|
|
}
|
|
|
|
select {
|
|
case <-awaitRTCPReceiverRecv:
|
|
close(awaitRTCPSenderSend)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
if _, routineErr := rtpReceiver.Read(make([]byte, 1400)); routineErr == nil {
|
|
close(awaitRTCPSenderRecv)
|
|
}
|
|
}()
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err, ok := <-trackMetadataValid
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
<-awaitRTPRecv
|
|
<-awaitRTPSend
|
|
|
|
<-awaitRTCPSenderRecv
|
|
err, ok = <-awaitRTCPSenderSend
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
<-awaitRTCPReceiverRecv
|
|
err, ok = <-awaitRTCPReceiverSend
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
<-awaitRTPRecvClosed
|
|
}
|
|
|
|
/*
|
|
PeerConnection should be able to be torn down at anytime
|
|
This test adds an input track and asserts
|
|
|
|
* OnTrack doesn't fire since no video packets will arrive
|
|
* No goroutine leaks
|
|
* No deadlocks on shutdown
|
|
*/
|
|
func TestPeerConnection_Media_Shutdown(t *testing.T) {
|
|
iceComplete := make(chan bool)
|
|
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcOffer.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
opusTrack, err := pcOffer.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err = pcOffer.AddTrack(opusTrack); err != nil {
|
|
t.Fatal(err)
|
|
} else if _, err = pcAnswer.AddTrack(vp8Track); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var onTrackFiredLock sync.RWMutex
|
|
onTrackFired := false
|
|
|
|
pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
|
|
onTrackFiredLock.Lock()
|
|
defer onTrackFiredLock.Unlock()
|
|
onTrackFired = true
|
|
})
|
|
|
|
pcAnswer.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
|
|
if iceState == ICEConnectionStateConnected {
|
|
go func() {
|
|
close(iceComplete)
|
|
}()
|
|
}
|
|
})
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
<-iceComplete
|
|
|
|
// Each PeerConnection should have one sender, one receiver and two transceivers
|
|
for _, pc := range []*PeerConnection{pcOffer, pcAnswer} {
|
|
senders := pc.GetSenders()
|
|
if len(senders) != 1 {
|
|
t.Errorf("Each PeerConnection should have one RTPSender, we have %d", len(senders))
|
|
}
|
|
|
|
receivers := pc.GetReceivers()
|
|
if len(receivers) != 2 {
|
|
t.Errorf("Each PeerConnection should have two RTPReceivers, we have %d", len(receivers))
|
|
}
|
|
|
|
transceivers := pc.GetTransceivers()
|
|
if len(transceivers) != 2 {
|
|
t.Errorf("Each PeerConnection should have two RTPTransceivers, we have %d", len(transceivers))
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
|
|
onTrackFiredLock.Lock()
|
|
if onTrackFired {
|
|
t.Fatalf("PeerConnection OnTrack fired even though we got no packets")
|
|
}
|
|
onTrackFiredLock.Unlock()
|
|
}
|
|
|
|
/*
|
|
Integration test for behavior around media and disconnected peers
|
|
|
|
* Sending RTP and RTCP to a disconnected Peer shouldn't return an error
|
|
*/
|
|
func TestPeerConnection_Media_Disconnected(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
s := SettingEngine{}
|
|
s.SetConnectionTimeout(time.Duration(1)*time.Second, time.Duration(250)*time.Millisecond)
|
|
|
|
api := NewAPI(WithSettingEngine(s))
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Sender, err := pcOffer.AddTrack(vp8Track)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
haveDisconnected := make(chan error)
|
|
pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
|
|
if iceState == ICEConnectionStateDisconnected {
|
|
close(haveDisconnected)
|
|
} else if iceState == ICEConnectionStateConnected {
|
|
// Assert that DTLS is done by pull remote certificate, don't tear down the PC early
|
|
for {
|
|
if len(vp8Sender.Transport().GetRemoteCertificate()) != 0 {
|
|
pcAnswer.sctpTransport.lock.RLock()
|
|
haveAssociation := pcAnswer.sctpTransport.association != nil
|
|
pcAnswer.sctpTransport.lock.RUnlock()
|
|
|
|
if haveAssociation {
|
|
break
|
|
}
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
if pcCloseErr := pcAnswer.Close(); pcCloseErr != nil {
|
|
haveDisconnected <- pcCloseErr
|
|
}
|
|
}
|
|
})
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err, ok := <-haveDisconnected
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i <= 5; i++ {
|
|
if rtpErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); rtpErr != nil {
|
|
t.Fatal(rtpErr)
|
|
} else if rtcpErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 0}}); rtcpErr != nil {
|
|
t.Fatal(rtcpErr)
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
}
|
|
|
|
/*
|
|
Integration test for behavior around media and closing
|
|
|
|
* Writing and Reading from tracks should return io.EOF when the PeerConnection is closed
|
|
*/
|
|
func TestPeerConnection_Media_Closed(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err = pcOffer.AddTrack(vp8Writer); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
answerChan := make(chan *Track)
|
|
pcAnswer.OnTrack(func(t *Track, r *RTPReceiver) {
|
|
answerChan <- t
|
|
})
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Reader := func() *Track {
|
|
for {
|
|
if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(time.Millisecond * 25)
|
|
|
|
select {
|
|
case t := <-answerChan:
|
|
return t
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}()
|
|
|
|
closeChan := make(chan error)
|
|
go func() {
|
|
time.Sleep(time.Second)
|
|
closeChan <- pcAnswer.Close()
|
|
}()
|
|
if _, err = vp8Reader.Read(make([]byte, 1)); err != io.EOF {
|
|
t.Fatal("Reading from closed Track did not return io.EOF")
|
|
} else if err = <-closeChan; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
|
|
if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); err != io.ErrClosedPipe {
|
|
t.Fatal("Write to Track with no RTPSenders did not return io.ErrClosedPipe")
|
|
} else if err = pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: 0, MediaSSRC: 0}}); err != io.ErrClosedPipe {
|
|
t.Fatal("WriteRTCP to closed PeerConnection did not return io.ErrClosedPipe")
|
|
}
|
|
}
|
|
|
|
// If a SessionDescription has a single media section and no SSRC
|
|
// assume that it is meant to handle all RTP packets
|
|
func TestUndeclaredSSRC(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
assert.NoError(t, err)
|
|
|
|
_, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo)
|
|
assert.NoError(t, err)
|
|
|
|
vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
assert.NoError(t, err)
|
|
|
|
_, err = pcOffer.AddTrack(vp8Writer)
|
|
assert.NoError(t, err)
|
|
|
|
onTrackFired := make(chan *Track)
|
|
pcAnswer.OnTrack(func(t *Track, r *RTPReceiver) {
|
|
close(onTrackFired)
|
|
})
|
|
|
|
offer, err := pcOffer.CreateOffer(nil)
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, pcOffer.SetLocalDescription(offer))
|
|
|
|
// Filter SSRC lines, and remove SCTP
|
|
offer.SDP = regexp.MustCompile("a=ssrc.*").ReplaceAllString(offer.SDP, "")
|
|
offer.SDP = regexp.MustCompile("m=application(?s).*").ReplaceAllString(offer.SDP, "")
|
|
offer.SDP = regexp.MustCompile("\n\n").ReplaceAllString(offer.SDP, "")
|
|
|
|
assert.NoError(t, pcAnswer.SetRemoteDescription(offer))
|
|
|
|
answer, err := pcAnswer.CreateAnswer(nil)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, pcAnswer.SetLocalDescription(answer))
|
|
assert.NoError(t, pcOffer.SetRemoteDescription(answer))
|
|
|
|
go func() {
|
|
for {
|
|
assert.NoError(t, vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
|
|
time.Sleep(time.Millisecond * 25)
|
|
|
|
select {
|
|
case <-onTrackFired:
|
|
return
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}()
|
|
|
|
<-onTrackFired
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
}
|
|
|
|
func TestOfferRejectionMissingCodec(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pc, err := api.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
noCodecAPI := NewAPI()
|
|
noCodecPC, err := noCodecAPI.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
track, err := pc.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pc.AddTrack(track); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := signalPair(pc, noCodecPC); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var sdes sdp.SessionDescription
|
|
if err := sdes.Unmarshal([]byte(pc.RemoteDescription().SDP)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var videoDesc sdp.MediaDescription
|
|
for _, m := range sdes.MediaDescriptions {
|
|
if m.MediaName.Media == "video" {
|
|
videoDesc = *m
|
|
}
|
|
}
|
|
|
|
if got, want := videoDesc.MediaName.Formats, []string{"0"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("rejecting unknown codec: sdp m=%s, want trailing 0", *videoDesc.MediaName.String())
|
|
}
|
|
|
|
assert.NoError(t, noCodecPC.Close())
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromTrackSendOnly(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
track, err := pc.NewTrack(
|
|
DefaultPayloadTypeOpus,
|
|
0xDEADBEEF,
|
|
"track-id",
|
|
"track-label",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromTrack(track, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendonly,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver != nil {
|
|
t.Errorf("Transceiver shouldn't have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender == nil {
|
|
t.Errorf("Transceiver should have a sender")
|
|
}
|
|
|
|
if len(pc.GetTransceivers()) != 1 {
|
|
t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers()))
|
|
}
|
|
|
|
if len(pc.GetSenders()) != 1 {
|
|
t.Errorf("PeerConnection should have one sender but has %d", len(pc.GetSenders()))
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendonly)
|
|
}
|
|
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromTrackSendRecv(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
track, err := pc.NewTrack(
|
|
DefaultPayloadTypeOpus,
|
|
0xDEADBEEF,
|
|
"track-id",
|
|
"track-label",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromTrack(track, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendrecv,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver == nil {
|
|
t.Errorf("Transceiver should have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender == nil {
|
|
t.Errorf("Transceiver should have a sender")
|
|
}
|
|
|
|
if len(pc.GetTransceivers()) != 1 {
|
|
t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers()))
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendrecv)
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
// nolint: dupl
|
|
func TestAddTransceiver(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendrecv,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver == nil {
|
|
t.Errorf("Transceiver should have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender == nil {
|
|
t.Errorf("Transceiver should have a sender")
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeVideo, RTPTransceiverDirectionSendrecv) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendrecv)
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
// nolint: dupl
|
|
func TestAddTransceiverFromKind(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionRecvonly,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver == nil {
|
|
t.Errorf("Transceiver should have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender != nil {
|
|
t.Errorf("Transceiver shouldn't have a sender")
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionRecvonly)
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromKindFailsSendOnly(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendonly,
|
|
})
|
|
|
|
if transceiver != nil {
|
|
t.Error("AddTransceiverFromKind shouldn't succeed with Direction RTPTransceiverDirectionSendonly")
|
|
}
|
|
|
|
assert.NotNil(t, err)
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
track, err := pc.NewTrack(
|
|
DefaultPayloadTypeH264,
|
|
0xDEADBEEF,
|
|
"track-id",
|
|
"track-label",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromTrack(track, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionRecvonly,
|
|
})
|
|
|
|
if transceiver != nil {
|
|
t.Error("AddTransceiverFromTrack shouldn't succeed with Direction RTPTransceiverDirectionRecvonly")
|
|
}
|
|
|
|
assert.NotNil(t, err)
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestOmitMediaFromBundleIfUnsupported(t *testing.T) {
|
|
const sdpOfferWithAudioAndVideo = `v=0
|
|
o=- 6476616870435111971 2 IN IP4 127.0.0.1
|
|
s=-
|
|
t=0 0
|
|
a=group:BUNDLE 0 1
|
|
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
|
c=IN IP4 0.0.0.0
|
|
a=rtcp:9 IN IP4 0.0.0.0
|
|
a=ice-ufrag:sRIG
|
|
a=ice-pwd:yZb5ZMsBlPoK577sGhjvEUtT
|
|
a=ice-options:trickle
|
|
a=fingerprint:sha-256 27:EF:25:BF:57:45:BC:1C:0D:36:42:FF:5E:93:71:D2:41:58:EA:46:FD:A8:2A:F3:13:94:6E:E6:43:23:CB:D7
|
|
a=setup:actpass
|
|
a=mid:0
|
|
a=sendrecv
|
|
a=rtpmap:111 opus/48000/2
|
|
a=fmtp:111 minptime=10;useinbandfec=1
|
|
m=video 9 UDP/TLS/RTP/SAVPF 96
|
|
c=IN IP4 0.0.0.0
|
|
a=rtcp:9 IN IP4 0.0.0.0
|
|
a=ice-ufrag:sRIG
|
|
a=ice-pwd:yZb5ZMsBlPoK577sGhjvEUtT
|
|
a=ice-options:trickle
|
|
a=fingerprint:sha-256 27:EF:25:BF:57:45:BC:1C:0D:36:42:FF:5E:93:71:D2:41:58:EA:46:FD:A8:2A:F3:13:94:6E:E6:43:23:CB:D7
|
|
a=setup:actpass
|
|
a=mid:1
|
|
a=sendrecv
|
|
a=rtpmap:96 H264/90000
|
|
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
|
|
`
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
mediaEngine := MediaEngine{}
|
|
mediaEngine.RegisterCodec(
|
|
NewRTPH264Codec(DefaultPayloadTypeH264, 90000),
|
|
)
|
|
|
|
api := NewAPI(
|
|
WithMediaEngine(mediaEngine),
|
|
)
|
|
|
|
pc, err := api.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if err = pc.SetRemoteDescription(SessionDescription{
|
|
Type: SDPTypeOffer,
|
|
SDP: sdpOfferWithAudioAndVideo,
|
|
}); nil != err {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
answer, err := pc.CreateAnswer(nil)
|
|
if nil != err {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
success := false
|
|
for _, attr := range answer.parsed.Attributes {
|
|
if "group" == attr.Key {
|
|
if "BUNDLE 1" == attr.Value {
|
|
success = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !success {
|
|
t.Fail()
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|