github.com/pion/webrtc/v3@v3.2.24/peerconnection_renegotiation_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 package webrtc 8 9 import ( 10 "bufio" 11 "context" 12 "errors" 13 "io" 14 "strconv" 15 "strings" 16 "sync" 17 "sync/atomic" 18 "testing" 19 "time" 20 21 "github.com/pion/rtp" 22 "github.com/pion/transport/v2/test" 23 "github.com/pion/webrtc/v3/internal/util" 24 "github.com/pion/webrtc/v3/pkg/media" 25 "github.com/pion/webrtc/v3/pkg/rtcerr" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 ) 29 30 func sendVideoUntilDone(done <-chan struct{}, t *testing.T, tracks []*TrackLocalStaticSample) { 31 for { 32 select { 33 case <-time.After(20 * time.Millisecond): 34 for _, track := range tracks { 35 assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second})) 36 } 37 case <-done: 38 return 39 } 40 } 41 } 42 43 func sdpMidHasSsrc(offer SessionDescription, mid string, ssrc SSRC) bool { 44 for _, media := range offer.parsed.MediaDescriptions { 45 cmid, ok := media.Attribute("mid") 46 if !ok { 47 continue 48 } 49 if cmid != mid { 50 continue 51 } 52 cssrc, ok := media.Attribute("ssrc") 53 if !ok { 54 continue 55 } 56 parts := strings.Split(cssrc, " ") 57 58 ssrcInt64, err := strconv.ParseUint(parts[0], 10, 32) 59 if err != nil { 60 continue 61 } 62 63 if uint32(ssrcInt64) == uint32(ssrc) { 64 return true 65 } 66 } 67 return false 68 } 69 70 func TestPeerConnection_Renegotiation_AddRecvonlyTransceiver(t *testing.T) { 71 type testCase struct { 72 name string 73 answererSends bool 74 } 75 76 testCases := []testCase{ 77 // Assert the following behaviors: 78 // - Offerer can add a recvonly transceiver 79 // - During negotiation, answerer peer adds an inactive (or sendonly) transceiver 80 // - Offerer can add a track 81 // - Answerer can receive the RTP packets. 82 {"add recvonly, then receive from answerer", false}, 83 // Assert the following behaviors: 84 // - Offerer can add a recvonly transceiver 85 // - During negotiation, answerer peer adds an inactive (or sendonly) transceiver 86 // - Answerer can add a track to the existing sendonly transceiver 87 // - Offerer can receive the RTP packets. 88 {"add recvonly, then send to answerer", true}, 89 } 90 91 for _, tc := range testCases { 92 tc := tc 93 t.Run(tc.name, func(t *testing.T) { 94 lim := test.TimeOut(time.Second * 30) 95 defer lim.Stop() 96 97 report := test.CheckRoutines(t) 98 defer report() 99 100 pcOffer, pcAnswer, err := newPair() 101 if err != nil { 102 t.Fatal(err) 103 } 104 105 _, err = pcOffer.AddTransceiverFromKind( 106 RTPCodecTypeVideo, 107 RTPTransceiverInit{ 108 Direction: RTPTransceiverDirectionRecvonly, 109 }, 110 ) 111 assert.NoError(t, err) 112 113 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 114 115 localTrack, err := NewTrackLocalStaticSample( 116 RTPCodecCapability{MimeType: "video/VP8"}, "track-one", "stream-one", 117 ) 118 require.NoError(t, err) 119 120 if tc.answererSends { 121 _, err = pcAnswer.AddTrack(localTrack) 122 } else { 123 _, err = pcOffer.AddTrack(localTrack) 124 } 125 126 require.NoError(t, err) 127 128 onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background()) 129 130 if tc.answererSends { 131 pcOffer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 132 onTrackFiredFunc() 133 }) 134 assert.NoError(t, signalPair(pcAnswer, pcOffer)) 135 } else { 136 pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 137 onTrackFiredFunc() 138 }) 139 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 140 } 141 142 sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{localTrack}) 143 144 closePairNow(t, pcOffer, pcAnswer) 145 }) 146 } 147 } 148 149 /* 150 * Assert the following behaviors 151 * - We are able to call AddTrack after signaling 152 * - OnTrack is NOT called on the other side until after SetRemoteDescription 153 * - We are able to re-negotiate and AddTrack is properly called 154 */ 155 func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) { 156 lim := test.TimeOut(time.Second * 30) 157 defer lim.Stop() 158 159 report := test.CheckRoutines(t) 160 defer report() 161 162 pcOffer, pcAnswer, err := newPair() 163 if err != nil { 164 t.Fatal(err) 165 } 166 167 haveRenegotiated := &atomicBool{} 168 onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background()) 169 pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 170 if !haveRenegotiated.get() { 171 t.Fatal("OnTrack was called before renegotiation") 172 } 173 onTrackFiredFunc() 174 }) 175 176 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 177 178 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 179 assert.NoError(t, err) 180 181 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 182 assert.NoError(t, err) 183 184 sender, err := pcOffer.AddTrack(vp8Track) 185 assert.NoError(t, err) 186 187 // Send 10 packets, OnTrack MUST not be fired 188 for i := 0; i <= 10; i++ { 189 assert.NoError(t, vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second})) 190 time.Sleep(20 * time.Millisecond) 191 } 192 193 haveRenegotiated.set(true) 194 assert.False(t, sender.isNegotiated()) 195 offer, err := pcOffer.CreateOffer(nil) 196 assert.True(t, sender.isNegotiated()) 197 assert.NoError(t, err) 198 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 199 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 200 answer, err := pcAnswer.CreateAnswer(nil) 201 assert.NoError(t, err) 202 203 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 204 205 pcOffer.ops.Done() 206 assert.Equal(t, 0, len(vp8Track.rtpTrack.bindings)) 207 208 assert.NoError(t, pcOffer.SetRemoteDescription(answer)) 209 210 pcOffer.ops.Done() 211 assert.Equal(t, 1, len(vp8Track.rtpTrack.bindings)) 212 213 sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track}) 214 215 closePairNow(t, pcOffer, pcAnswer) 216 } 217 218 // Assert that adding tracks across multiple renegotiations performs as expected 219 func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) { 220 addTrackWithLabel := func(trackID string, pcOffer, pcAnswer *PeerConnection) *TrackLocalStaticSample { 221 _, err := pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 222 assert.NoError(t, err) 223 224 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, trackID, trackID) 225 assert.NoError(t, err) 226 227 _, err = pcOffer.AddTrack(track) 228 assert.NoError(t, err) 229 230 return track 231 } 232 233 trackIDs := []string{util.MathRandAlpha(16), util.MathRandAlpha(16), util.MathRandAlpha(16)} 234 outboundTracks := []*TrackLocalStaticSample{} 235 onTrackCount := map[string]int{} 236 onTrackChan := make(chan struct{}, 1) 237 238 lim := test.TimeOut(time.Second * 30) 239 defer lim.Stop() 240 241 report := test.CheckRoutines(t) 242 defer report() 243 244 pcOffer, pcAnswer, err := newPair() 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 250 onTrackCount[track.ID()]++ 251 onTrackChan <- struct{}{} 252 }) 253 254 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 255 256 for i := range trackIDs { 257 outboundTracks = append(outboundTracks, addTrackWithLabel(trackIDs[i], pcOffer, pcAnswer)) 258 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 259 sendVideoUntilDone(onTrackChan, t, outboundTracks) 260 } 261 262 closePairNow(t, pcOffer, pcAnswer) 263 264 assert.Equal(t, onTrackCount[trackIDs[0]], 1) 265 assert.Equal(t, onTrackCount[trackIDs[1]], 1) 266 assert.Equal(t, onTrackCount[trackIDs[2]], 1) 267 } 268 269 // Assert that renegotiation triggers OnTrack() with correct ID and label from 270 // remote side, even when a transceiver was added before the actual track data 271 // was received. This happens when we add a transceiver on the server, create 272 // an offer on the server and the browser's answer contains the same SSRC, but 273 // a track hasn't been added on the browser side yet. The browser can add a 274 // track later and renegotiate, and track ID and label will be set by the time 275 // first packets are received. 276 func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) { 277 lim := test.TimeOut(time.Second * 30) 278 defer lim.Stop() 279 280 report := test.CheckRoutines(t) 281 defer report() 282 283 pcOffer, pcAnswer, err := newPair() 284 if err != nil { 285 t.Fatal(err) 286 } 287 288 haveRenegotiated := &atomicBool{} 289 onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background()) 290 var atomicRemoteTrack atomic.Value 291 pcOffer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 292 if !haveRenegotiated.get() { 293 t.Fatal("OnTrack was called before renegotiation") 294 } 295 onTrackFiredFunc() 296 atomicRemoteTrack.Store(track) 297 }) 298 299 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 300 assert.NoError(t, err) 301 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo1", "bar1") 302 assert.NoError(t, err) 303 _, err = pcAnswer.AddTrack(vp8Track) 304 assert.NoError(t, err) 305 306 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 307 308 vp8Track.rtpTrack.id = "foo2" 309 vp8Track.rtpTrack.streamID = "bar2" 310 311 haveRenegotiated.set(true) 312 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 313 314 sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track}) 315 316 closePairNow(t, pcOffer, pcAnswer) 317 318 remoteTrack, ok := atomicRemoteTrack.Load().(*TrackRemote) 319 require.True(t, ok) 320 require.NotNil(t, remoteTrack) 321 assert.Equal(t, "foo2", remoteTrack.ID()) 322 assert.Equal(t, "bar2", remoteTrack.StreamID()) 323 } 324 325 // TestPeerConnection_Transceiver_Mid tests that we'll provide the same 326 // transceiver for a media id on successive offer/answer 327 func TestPeerConnection_Transceiver_Mid(t *testing.T) { 328 lim := test.TimeOut(time.Second * 30) 329 defer lim.Stop() 330 331 report := test.CheckRoutines(t) 332 defer report() 333 334 pcOffer, err := NewPeerConnection(Configuration{}) 335 assert.NoError(t, err) 336 337 pcAnswer, err := NewPeerConnection(Configuration{}) 338 assert.NoError(t, err) 339 340 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1") 341 require.NoError(t, err) 342 343 sender1, err := pcOffer.AddTrack(track1) 344 require.NoError(t, err) 345 346 track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 347 require.NoError(t, err) 348 349 sender2, err := pcOffer.AddTrack(track2) 350 require.NoError(t, err) 351 352 // this will create the initial offer using generateUnmatchedSDP 353 offer, err := pcOffer.CreateOffer(nil) 354 assert.NoError(t, err) 355 356 offerGatheringComplete := GatheringCompletePromise(pcOffer) 357 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 358 <-offerGatheringComplete 359 360 assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription())) 361 362 answer, err := pcAnswer.CreateAnswer(nil) 363 assert.NoError(t, err) 364 365 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 366 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 367 <-answerGatheringComplete 368 369 // apply answer so we'll test generateMatchedSDP 370 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 371 372 pcOffer.ops.Done() 373 pcAnswer.ops.Done() 374 375 // Must have 3 media descriptions (2 video channels) 376 assert.Equal(t, len(offer.parsed.MediaDescriptions), 2) 377 378 assert.True(t, sdpMidHasSsrc(offer, "0", sender1.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "0", sender1.trackEncodings[0].ssrc, offer.SDP) 379 380 // Remove first track, must keep same number of media 381 // descriptions and same track ssrc for mid 1 as previous 382 assert.NoError(t, pcOffer.RemoveTrack(sender1)) 383 384 offer, err = pcOffer.CreateOffer(nil) 385 assert.NoError(t, err) 386 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 387 388 assert.Equal(t, len(offer.parsed.MediaDescriptions), 2) 389 390 assert.True(t, sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "1", sender2.trackEncodings[0].ssrc, offer.SDP) 391 392 _, err = pcAnswer.CreateAnswer(nil) 393 assert.Equal(t, err, &rtcerr.InvalidStateError{Err: ErrIncorrectSignalingState}) 394 395 pcOffer.ops.Done() 396 pcAnswer.ops.Done() 397 398 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 399 answer, err = pcAnswer.CreateAnswer(nil) 400 assert.NoError(t, err) 401 assert.NoError(t, pcOffer.SetRemoteDescription(answer)) 402 403 track3, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion3") 404 require.NoError(t, err) 405 406 sender3, err := pcOffer.AddTrack(track3) 407 require.NoError(t, err) 408 409 offer, err = pcOffer.CreateOffer(nil) 410 assert.NoError(t, err) 411 412 // We reuse the existing non-sending transceiver 413 assert.Equal(t, len(offer.parsed.MediaDescriptions), 2) 414 415 assert.True(t, sdpMidHasSsrc(offer, "0", sender3.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "0", sender3.trackEncodings[0].ssrc, offer.SDP) 416 assert.True(t, sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "1", sender2.trackEncodings[0].ssrc, offer.SDP) 417 418 closePairNow(t, pcOffer, pcAnswer) 419 } 420 421 func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) { 422 lim := test.TimeOut(time.Second * 30) 423 defer lim.Stop() 424 425 report := test.CheckRoutines(t) 426 defer report() 427 428 pcOffer, err := NewPeerConnection(Configuration{}) 429 assert.NoError(t, err) 430 431 pcAnswer, err := NewPeerConnection(Configuration{}) 432 assert.NoError(t, err) 433 434 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video1", "pion1") 435 require.NoError(t, err) 436 437 track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video2", "pion2") 438 require.NoError(t, err) 439 440 sender1, err := pcOffer.AddTrack(track1) 441 require.NoError(t, err) 442 443 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 444 require.NoError(t, err) 445 446 tracksCh := make(chan *TrackRemote) 447 tracksClosed := make(chan struct{}) 448 pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 449 tracksCh <- track 450 for { 451 if _, _, readErr := track.ReadRTP(); errors.Is(readErr, io.EOF) { 452 tracksClosed <- struct{}{} 453 return 454 } 455 } 456 }) 457 458 err = signalPair(pcOffer, pcAnswer) 459 require.NoError(t, err) 460 461 transceivers := pcOffer.GetTransceivers() 462 require.Equal(t, 1, len(transceivers)) 463 require.Equal(t, "0", transceivers[0].Mid()) 464 465 transceivers = pcAnswer.GetTransceivers() 466 require.Equal(t, 1, len(transceivers)) 467 require.Equal(t, "0", transceivers[0].Mid()) 468 469 ctx, cancel := context.WithCancel(context.Background()) 470 go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{track1}) 471 472 remoteTrack1 := <-tracksCh 473 cancel() 474 475 assert.Equal(t, "video1", remoteTrack1.ID()) 476 assert.Equal(t, "pion1", remoteTrack1.StreamID()) 477 478 require.NoError(t, pcOffer.RemoveTrack(sender1)) 479 480 require.NoError(t, signalPair(pcOffer, pcAnswer)) 481 <-tracksClosed 482 483 sender2, err := pcOffer.AddTrack(track2) 484 require.NoError(t, err) 485 require.NoError(t, signalPair(pcOffer, pcAnswer)) 486 transceivers = pcOffer.GetTransceivers() 487 require.Equal(t, 1, len(transceivers)) 488 require.Equal(t, "0", transceivers[0].Mid()) 489 490 transceivers = pcAnswer.GetTransceivers() 491 require.Equal(t, 1, len(transceivers)) 492 require.Equal(t, "0", transceivers[0].Mid()) 493 494 ctx, cancel = context.WithCancel(context.Background()) 495 go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{track2}) 496 497 remoteTrack2 := <-tracksCh 498 cancel() 499 500 require.NoError(t, pcOffer.RemoveTrack(sender2)) 501 502 err = signalPair(pcOffer, pcAnswer) 503 require.NoError(t, err) 504 <-tracksClosed 505 506 assert.Equal(t, "video2", remoteTrack2.ID()) 507 assert.Equal(t, "pion2", remoteTrack2.StreamID()) 508 509 closePairNow(t, pcOffer, pcAnswer) 510 } 511 512 func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) { 513 lim := test.TimeOut(time.Second * 30) 514 defer lim.Stop() 515 516 report := test.CheckRoutines(t) 517 defer report() 518 519 pcOffer, pcAnswer, err := newPair() 520 if err != nil { 521 t.Fatal(err) 522 } 523 524 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 525 assert.NoError(t, err) 526 527 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 528 assert.NoError(t, err) 529 530 sender, err := pcOffer.AddTrack(vp8Track) 531 assert.NoError(t, err) 532 533 onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background()) 534 trackClosed, trackClosedFunc := context.WithCancel(context.Background()) 535 536 pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 537 onTrackFiredFunc() 538 539 for { 540 if _, _, err := track.ReadRTP(); errors.Is(err, io.EOF) { 541 trackClosedFunc() 542 return 543 } 544 } 545 }) 546 547 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 548 sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track}) 549 550 assert.NoError(t, pcOffer.RemoveTrack(sender)) 551 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 552 553 <-trackClosed.Done() 554 closePairNow(t, pcOffer, pcAnswer) 555 } 556 557 func TestPeerConnection_RoleSwitch(t *testing.T) { 558 lim := test.TimeOut(time.Second * 30) 559 defer lim.Stop() 560 561 report := test.CheckRoutines(t) 562 defer report() 563 564 pcFirstOfferer, pcSecondOfferer, err := newPair() 565 if err != nil { 566 t.Fatal(err) 567 } 568 569 onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background()) 570 pcFirstOfferer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 571 onTrackFiredFunc() 572 }) 573 574 assert.NoError(t, signalPair(pcFirstOfferer, pcSecondOfferer)) 575 576 // Add a new Track to the second offerer 577 // This asserts that it will match the ordering of the last RemoteDescription, but then also add new Transceivers to the end 578 _, err = pcFirstOfferer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 579 assert.NoError(t, err) 580 581 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 582 assert.NoError(t, err) 583 584 _, err = pcSecondOfferer.AddTrack(vp8Track) 585 assert.NoError(t, err) 586 587 assert.NoError(t, signalPair(pcSecondOfferer, pcFirstOfferer)) 588 sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track}) 589 590 closePairNow(t, pcFirstOfferer, pcSecondOfferer) 591 } 592 593 // Assert that renegotiation doesn't attempt to gather ICE twice 594 // Before we would attempt to gather multiple times and would put 595 // the PeerConnection into a broken state 596 func TestPeerConnection_Renegotiation_Trickle(t *testing.T) { 597 lim := test.TimeOut(time.Second * 30) 598 defer lim.Stop() 599 600 report := test.CheckRoutines(t) 601 defer report() 602 603 settingEngine := SettingEngine{} 604 605 api := NewAPI(WithSettingEngine(settingEngine)) 606 assert.NoError(t, api.mediaEngine.RegisterDefaultCodecs()) 607 608 // Invalid STUN server on purpose, will stop ICE Gathering from completing in time 609 pcOffer, pcAnswer, err := api.newPair(Configuration{ 610 ICEServers: []ICEServer{ 611 { 612 URLs: []string{"stun:127.0.0.1:5000"}, 613 }, 614 }, 615 }) 616 if err != nil { 617 t.Fatal(err) 618 } 619 620 _, err = pcOffer.CreateDataChannel("test-channel", nil) 621 assert.NoError(t, err) 622 623 var wg sync.WaitGroup 624 wg.Add(2) 625 pcOffer.OnICECandidate(func(c *ICECandidate) { 626 if c != nil { 627 assert.NoError(t, pcAnswer.AddICECandidate(c.ToJSON())) 628 } else { 629 wg.Done() 630 } 631 }) 632 pcAnswer.OnICECandidate(func(c *ICECandidate) { 633 if c != nil { 634 assert.NoError(t, pcOffer.AddICECandidate(c.ToJSON())) 635 } else { 636 wg.Done() 637 } 638 }) 639 640 negotiate := func() { 641 offer, err := pcOffer.CreateOffer(nil) 642 assert.NoError(t, err) 643 644 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 645 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 646 647 answer, err := pcAnswer.CreateAnswer(nil) 648 assert.NoError(t, err) 649 650 assert.NoError(t, pcOffer.SetRemoteDescription(answer)) 651 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 652 } 653 negotiate() 654 negotiate() 655 656 pcOffer.ops.Done() 657 pcAnswer.ops.Done() 658 wg.Wait() 659 660 closePairNow(t, pcOffer, pcAnswer) 661 } 662 663 func TestPeerConnection_Renegotiation_SetLocalDescription(t *testing.T) { 664 lim := test.TimeOut(time.Second * 30) 665 defer lim.Stop() 666 667 report := test.CheckRoutines(t) 668 defer report() 669 670 pcOffer, pcAnswer, err := newPair() 671 if err != nil { 672 t.Fatal(err) 673 } 674 675 onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background()) 676 pcOffer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 677 onTrackFiredFunc() 678 }) 679 680 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 681 682 pcOffer.ops.Done() 683 pcAnswer.ops.Done() 684 685 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 686 assert.NoError(t, err) 687 688 localTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 689 assert.NoError(t, err) 690 691 sender, err := pcAnswer.AddTrack(localTrack) 692 assert.NoError(t, err) 693 694 offer, err := pcOffer.CreateOffer(nil) 695 assert.NoError(t, err) 696 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 697 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 698 assert.False(t, sender.isNegotiated()) 699 answer, err := pcAnswer.CreateAnswer(nil) 700 assert.NoError(t, err) 701 assert.True(t, sender.isNegotiated()) 702 703 pcAnswer.ops.Done() 704 assert.Equal(t, 0, len(localTrack.rtpTrack.bindings)) 705 706 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 707 708 pcAnswer.ops.Done() 709 assert.Equal(t, 1, len(localTrack.rtpTrack.bindings)) 710 711 assert.NoError(t, pcOffer.SetRemoteDescription(answer)) 712 713 sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{localTrack}) 714 715 closePairNow(t, pcOffer, pcAnswer) 716 } 717 718 // Issue #346, don't start the SCTP Subsystem if the RemoteDescription doesn't contain one 719 // Before we would always start it, and re-negotiations would fail because SCTP was in flight 720 func TestPeerConnection_Renegotiation_NoApplication(t *testing.T) { 721 signalPairExcludeDataChannel := func(pcOffer, pcAnswer *PeerConnection) { 722 offer, err := pcOffer.CreateOffer(nil) 723 assert.NoError(t, err) 724 offerGatheringComplete := GatheringCompletePromise(pcOffer) 725 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 726 <-offerGatheringComplete 727 728 assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription())) 729 730 answer, err := pcAnswer.CreateAnswer(nil) 731 assert.NoError(t, err) 732 733 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 734 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 735 <-answerGatheringComplete 736 737 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 738 } 739 740 lim := test.TimeOut(time.Second * 30) 741 defer lim.Stop() 742 743 report := test.CheckRoutines(t) 744 defer report() 745 746 pcOffer, pcAnswer, err := newPair() 747 if err != nil { 748 t.Fatal(err) 749 } 750 751 pcOfferConnected, pcOfferConnectedCancel := context.WithCancel(context.Background()) 752 pcOffer.OnICEConnectionStateChange(func(i ICEConnectionState) { 753 if i == ICEConnectionStateConnected { 754 pcOfferConnectedCancel() 755 } 756 }) 757 758 pcAnswerConnected, pcAnswerConnectedCancel := context.WithCancel(context.Background()) 759 pcAnswer.OnICEConnectionStateChange(func(i ICEConnectionState) { 760 if i == ICEConnectionStateConnected { 761 pcAnswerConnectedCancel() 762 } 763 }) 764 765 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv}) 766 assert.NoError(t, err) 767 768 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv}) 769 assert.NoError(t, err) 770 771 signalPairExcludeDataChannel(pcOffer, pcAnswer) 772 pcOffer.ops.Done() 773 pcAnswer.ops.Done() 774 775 signalPairExcludeDataChannel(pcOffer, pcAnswer) 776 pcOffer.ops.Done() 777 pcAnswer.ops.Done() 778 779 <-pcAnswerConnected.Done() 780 <-pcOfferConnected.Done() 781 782 assert.Equal(t, pcOffer.SCTP().State(), SCTPTransportStateConnecting) 783 assert.Equal(t, pcAnswer.SCTP().State(), SCTPTransportStateConnecting) 784 785 closePairNow(t, pcOffer, pcAnswer) 786 } 787 788 func TestAddDataChannelDuringRenegotiation(t *testing.T) { 789 lim := test.TimeOut(time.Second * 10) 790 defer lim.Stop() 791 792 report := test.CheckRoutines(t) 793 defer report() 794 795 pcOffer, err := NewPeerConnection(Configuration{}) 796 assert.NoError(t, err) 797 798 pcAnswer, err := NewPeerConnection(Configuration{}) 799 assert.NoError(t, err) 800 801 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 802 assert.NoError(t, err) 803 804 _, err = pcOffer.AddTrack(track) 805 assert.NoError(t, err) 806 807 offer, err := pcOffer.CreateOffer(nil) 808 assert.NoError(t, err) 809 810 offerGatheringComplete := GatheringCompletePromise(pcOffer) 811 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 812 <-offerGatheringComplete 813 814 assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription())) 815 816 answer, err := pcAnswer.CreateAnswer(nil) 817 assert.NoError(t, err) 818 819 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 820 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 821 <-answerGatheringComplete 822 823 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 824 825 _, err = pcOffer.CreateDataChannel("data-channel", nil) 826 assert.NoError(t, err) 827 828 // Assert that DataChannel is in offer now 829 offer, err = pcOffer.CreateOffer(nil) 830 assert.NoError(t, err) 831 832 applicationMediaSectionCount := 0 833 for _, d := range offer.parsed.MediaDescriptions { 834 if d.MediaName.Media == mediaSectionApplication { 835 applicationMediaSectionCount++ 836 } 837 } 838 assert.Equal(t, applicationMediaSectionCount, 1) 839 840 onDataChannelFired, onDataChannelFiredFunc := context.WithCancel(context.Background()) 841 pcAnswer.OnDataChannel(func(*DataChannel) { 842 onDataChannelFiredFunc() 843 }) 844 845 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 846 847 <-onDataChannelFired.Done() 848 closePairNow(t, pcOffer, pcAnswer) 849 } 850 851 // Assert that CreateDataChannel fires OnNegotiationNeeded 852 func TestNegotiationCreateDataChannel(t *testing.T) { 853 lim := test.TimeOut(time.Second * 30) 854 defer lim.Stop() 855 856 report := test.CheckRoutines(t) 857 defer report() 858 859 pc, err := NewPeerConnection(Configuration{}) 860 assert.NoError(t, err) 861 862 var wg sync.WaitGroup 863 wg.Add(1) 864 865 pc.OnNegotiationNeeded(func() { 866 defer func() { 867 wg.Done() 868 }() 869 }) 870 871 // Create DataChannel, wait until OnNegotiationNeeded is fired 872 if _, err = pc.CreateDataChannel("testChannel", nil); err != nil { 873 t.Error(err.Error()) 874 } 875 876 // Wait until OnNegotiationNeeded is fired 877 wg.Wait() 878 assert.NoError(t, pc.Close()) 879 } 880 881 func TestNegotiationNeededRemoveTrack(t *testing.T) { 882 var wg sync.WaitGroup 883 wg.Add(1) 884 885 report := test.CheckRoutines(t) 886 defer report() 887 888 pcOffer, err := NewPeerConnection(Configuration{}) 889 assert.NoError(t, err) 890 pcAnswer, err := NewPeerConnection(Configuration{}) 891 assert.NoError(t, err) 892 893 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 894 assert.NoError(t, err) 895 896 pcOffer.OnNegotiationNeeded(func() { 897 wg.Add(1) 898 offer, createOfferErr := pcOffer.CreateOffer(nil) 899 assert.NoError(t, createOfferErr) 900 901 offerGatheringComplete := GatheringCompletePromise(pcOffer) 902 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 903 904 <-offerGatheringComplete 905 assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription())) 906 907 answer, createAnswerErr := pcAnswer.CreateAnswer(nil) 908 assert.NoError(t, createAnswerErr) 909 910 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 911 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 912 913 <-answerGatheringComplete 914 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 915 wg.Done() 916 wg.Done() 917 }) 918 919 sender, err := pcOffer.AddTrack(track) 920 assert.NoError(t, err) 921 922 assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second})) 923 924 wg.Wait() 925 926 wg.Add(1) 927 assert.NoError(t, pcOffer.RemoveTrack(sender)) 928 929 wg.Wait() 930 931 closePairNow(t, pcOffer, pcAnswer) 932 } 933 934 func TestNegotiationNeededStressOneSided(t *testing.T) { 935 lim := test.TimeOut(time.Second * 30) 936 defer lim.Stop() 937 938 report := test.CheckRoutines(t) 939 defer report() 940 941 pcA, pcB, err := newPair() 942 assert.NoError(t, err) 943 944 const expectedTrackCount = 500 945 ctx, done := context.WithCancel(context.Background()) 946 pcA.OnNegotiationNeeded(func() { 947 count := len(pcA.GetTransceivers()) 948 assert.NoError(t, signalPair(pcA, pcB)) 949 if count == expectedTrackCount { 950 done() 951 } 952 }) 953 954 for i := 0; i < expectedTrackCount; i++ { 955 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 956 assert.NoError(t, err) 957 958 _, err = pcA.AddTrack(track) 959 assert.NoError(t, err) 960 } 961 <-ctx.Done() 962 assert.Equal(t, expectedTrackCount, len(pcB.GetTransceivers())) 963 closePairNow(t, pcA, pcB) 964 } 965 966 // TestPeerConnection_Renegotiation_DisableTrack asserts that if a remote track is set inactive 967 // that locally it goes inactive as well 968 func TestPeerConnection_Renegotiation_DisableTrack(t *testing.T) { 969 lim := test.TimeOut(time.Second * 30) 970 defer lim.Stop() 971 972 report := test.CheckRoutines(t) 973 defer report() 974 975 pcOffer, pcAnswer, err := newPair() 976 assert.NoError(t, err) 977 978 // Create two transceivers 979 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo) 980 assert.NoError(t, err) 981 982 transceiver, err := pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo) 983 assert.NoError(t, err) 984 985 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 986 987 // Assert we have three active transceivers 988 offer, err := pcOffer.CreateOffer(nil) 989 assert.NoError(t, err) 990 assert.Equal(t, strings.Count(offer.SDP, "a=sendrecv"), 3) 991 992 // Assert we have two active transceivers, one inactive 993 assert.NoError(t, transceiver.Stop()) 994 offer, err = pcOffer.CreateOffer(nil) 995 assert.NoError(t, err) 996 assert.Equal(t, strings.Count(offer.SDP, "a=sendrecv"), 2) 997 assert.Equal(t, strings.Count(offer.SDP, "a=inactive"), 1) 998 999 // Assert that the offer disabled one of our transceivers 1000 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 1001 answer, err := pcAnswer.CreateAnswer(nil) 1002 assert.NoError(t, err) 1003 assert.Equal(t, strings.Count(answer.SDP, "a=sendrecv"), 1) // DataChannel 1004 assert.Equal(t, strings.Count(answer.SDP, "a=recvonly"), 1) 1005 assert.Equal(t, strings.Count(answer.SDP, "a=inactive"), 1) 1006 1007 closePairNow(t, pcOffer, pcAnswer) 1008 } 1009 1010 func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) { 1011 lim := test.TimeOut(time.Second * 30) 1012 defer lim.Stop() 1013 1014 report := test.CheckRoutines(t) 1015 defer report() 1016 1017 m := &MediaEngine{} 1018 if err := m.RegisterDefaultCodecs(); err != nil { 1019 panic(err) 1020 } 1021 registerSimulcastHeaderExtensions(m, RTPCodecTypeVideo) 1022 1023 originalRids := []string{"a", "b", "c"} 1024 signalWithRids := func(sessionDescription string, rids []string) string { 1025 sessionDescription = strings.SplitAfter(sessionDescription, "a=end-of-candidates\r\n")[0] 1026 sessionDescription = filterSsrc(sessionDescription) 1027 for _, rid := range rids { 1028 sessionDescription += "a=" + sdpAttributeRid + ":" + rid + " send\r\n" 1029 } 1030 return sessionDescription + "a=simulcast:send " + strings.Join(rids, ";") + "\r\n" 1031 } 1032 1033 var trackMapLock sync.RWMutex 1034 trackMap := map[string]*TrackRemote{} 1035 1036 onTrackHandler := func(track *TrackRemote, _ *RTPReceiver) { 1037 trackMapLock.Lock() 1038 defer trackMapLock.Unlock() 1039 trackMap[track.RID()] = track 1040 } 1041 1042 sendUntilAllTracksFired := func(vp8Writer *TrackLocalStaticRTP, rids []string) { 1043 allTracksFired := func() bool { 1044 trackMapLock.Lock() 1045 defer trackMapLock.Unlock() 1046 1047 return len(trackMap) == len(rids) 1048 } 1049 1050 for sequenceNumber := uint16(0); !allTracksFired(); sequenceNumber++ { 1051 time.Sleep(20 * time.Millisecond) 1052 1053 for ssrc, rid := range rids { 1054 header := &rtp.Header{ 1055 Version: 2, 1056 SSRC: uint32(ssrc), 1057 SequenceNumber: sequenceNumber, 1058 PayloadType: 96, 1059 } 1060 assert.NoError(t, header.SetExtension(1, []byte("0"))) 1061 assert.NoError(t, header.SetExtension(2, []byte(rid))) 1062 1063 _, err := vp8Writer.bindings[0].writeStream.WriteRTP(header, []byte{0x00}) 1064 assert.NoError(t, err) 1065 } 1066 } 1067 } 1068 1069 assertTracksClosed := func(t *testing.T) { 1070 trackMapLock.Lock() 1071 defer trackMapLock.Unlock() 1072 1073 for _, track := range trackMap { 1074 _, _, err := track.ReadRTP() // Ignore first Read, this is our peeked data 1075 assert.Nil(t, err) 1076 1077 _, _, err = track.ReadRTP() 1078 assert.Equal(t, err, io.EOF) 1079 } 1080 } 1081 1082 t.Run("Disable Transceiver", func(t *testing.T) { 1083 trackMap = map[string]*TrackRemote{} 1084 pcOffer, pcAnswer, err := NewAPI(WithMediaEngine(m)).newPair(Configuration{}) 1085 assert.NoError(t, err) 1086 1087 vp8Writer, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 1088 assert.NoError(t, err) 1089 1090 rtpTransceiver, err := pcOffer.AddTransceiverFromTrack( 1091 vp8Writer, 1092 RTPTransceiverInit{ 1093 Direction: RTPTransceiverDirectionSendonly, 1094 }, 1095 ) 1096 assert.NoError(t, err) 1097 1098 assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string { 1099 return signalWithRids(sessionDescription, originalRids) 1100 })) 1101 1102 pcAnswer.OnTrack(onTrackHandler) 1103 sendUntilAllTracksFired(vp8Writer, originalRids) 1104 1105 assert.NoError(t, pcOffer.RemoveTrack(rtpTransceiver.Sender())) 1106 assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string { 1107 sessionDescription = strings.SplitAfter(sessionDescription, "a=end-of-candidates\r\n")[0] 1108 return sessionDescription 1109 })) 1110 1111 assertTracksClosed(t) 1112 closePairNow(t, pcOffer, pcAnswer) 1113 }) 1114 1115 t.Run("Change RID", func(t *testing.T) { 1116 trackMap = map[string]*TrackRemote{} 1117 pcOffer, pcAnswer, err := NewAPI(WithMediaEngine(m)).newPair(Configuration{}) 1118 assert.NoError(t, err) 1119 1120 vp8Writer, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 1121 assert.NoError(t, err) 1122 1123 _, err = pcOffer.AddTransceiverFromTrack( 1124 vp8Writer, 1125 RTPTransceiverInit{ 1126 Direction: RTPTransceiverDirectionSendonly, 1127 }, 1128 ) 1129 assert.NoError(t, err) 1130 1131 assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string { 1132 return signalWithRids(sessionDescription, originalRids) 1133 })) 1134 1135 pcAnswer.OnTrack(onTrackHandler) 1136 sendUntilAllTracksFired(vp8Writer, originalRids) 1137 1138 newRids := []string{"d", "e", "f"} 1139 assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string { 1140 scanner := bufio.NewScanner(strings.NewReader(sessionDescription)) 1141 sessionDescription = "" 1142 for scanner.Scan() { 1143 l := scanner.Text() 1144 if strings.HasPrefix(l, "a=rid") || strings.HasPrefix(l, "a=simulcast") { 1145 continue 1146 } 1147 1148 sessionDescription += l + "\n" 1149 } 1150 return signalWithRids(sessionDescription, newRids) 1151 })) 1152 1153 assertTracksClosed(t) 1154 closePairNow(t, pcOffer, pcAnswer) 1155 }) 1156 } 1157 1158 func TestPeerConnection_Regegotiation_ReuseTransceiver(t *testing.T) { 1159 lim := test.TimeOut(time.Second * 30) 1160 defer lim.Stop() 1161 1162 report := test.CheckRoutines(t) 1163 defer report() 1164 1165 pcOffer, pcAnswer, err := newPair() 1166 if err != nil { 1167 t.Fatal(err) 1168 } 1169 1170 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 1171 assert.NoError(t, err) 1172 sender, err := pcOffer.AddTrack(vp8Track) 1173 assert.NoError(t, err) 1174 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1175 1176 assert.Equal(t, len(pcOffer.GetTransceivers()), 1) 1177 assert.Equal(t, pcOffer.GetTransceivers()[0].getCurrentDirection(), RTPTransceiverDirectionSendonly) 1178 assert.NoError(t, pcOffer.RemoveTrack(sender)) 1179 assert.Equal(t, pcOffer.GetTransceivers()[0].getCurrentDirection(), RTPTransceiverDirectionSendonly) 1180 1181 // should not reuse tranceiver 1182 vp8Track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 1183 assert.NoError(t, err) 1184 sender2, err := pcOffer.AddTrack(vp8Track2) 1185 assert.NoError(t, err) 1186 assert.Equal(t, len(pcOffer.GetTransceivers()), 2) 1187 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1188 assert.True(t, sender2.rtpTransceiver == pcOffer.GetTransceivers()[1]) 1189 1190 // should reuse first transceiver 1191 sender, err = pcOffer.AddTrack(vp8Track) 1192 assert.NoError(t, err) 1193 assert.Equal(t, len(pcOffer.GetTransceivers()), 2) 1194 assert.True(t, sender.rtpTransceiver == pcOffer.GetTransceivers()[0]) 1195 1196 closePairNow(t, pcOffer, pcAnswer) 1197 } 1198 1199 func TestPeerConnection_Renegotiation_MidConflict(t *testing.T) { 1200 lim := test.TimeOut(time.Second * 30) 1201 defer lim.Stop() 1202 1203 report := test.CheckRoutines(t) 1204 defer report() 1205 1206 offerPC, err := NewPeerConnection(Configuration{}) 1207 assert.NoError(t, err) 1208 answerPC, err := NewPeerConnection(Configuration{}) 1209 assert.NoError(t, err) 1210 _, err = offerPC.CreateDataChannel("test", nil) 1211 assert.NoError(t, err) 1212 1213 _, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionSendonly}) 1214 assert.NoError(t, err) 1215 _, err = offerPC.AddTransceiverFromKind(RTPCodecTypeAudio, RtpTransceiverInit{Direction: RTPTransceiverDirectionSendonly}) 1216 assert.NoError(t, err) 1217 1218 offer, err := offerPC.CreateOffer(nil) 1219 assert.NoError(t, err) 1220 assert.NoError(t, offerPC.SetLocalDescription(offer)) 1221 assert.NoError(t, answerPC.SetRemoteDescription(offer), offer.SDP) 1222 answer, err := answerPC.CreateAnswer(nil) 1223 assert.NoError(t, err) 1224 assert.NoError(t, answerPC.SetLocalDescription(answer)) 1225 assert.NoError(t, offerPC.SetRemoteDescription(answer)) 1226 assert.Equal(t, SignalingStateStable, offerPC.SignalingState()) 1227 1228 tr, err := offerPC.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionSendonly}) 1229 assert.NoError(t, err) 1230 assert.NoError(t, tr.SetMid("3")) 1231 _, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionSendrecv}) 1232 assert.NoError(t, err) 1233 _, err = offerPC.CreateOffer(nil) 1234 assert.NoError(t, err) 1235 1236 assert.NoError(t, offerPC.Close()) 1237 assert.NoError(t, answerPC.Close()) 1238 } 1239 1240 func TestPeerConnection_Regegotiation_AnswerAddsTrack(t *testing.T) { 1241 lim := test.TimeOut(time.Second * 30) 1242 defer lim.Stop() 1243 1244 report := test.CheckRoutines(t) 1245 defer report() 1246 1247 pcOffer, pcAnswer, err := newPair() 1248 if err != nil { 1249 t.Fatal(err) 1250 } 1251 1252 tracksCh := make(chan *TrackRemote) 1253 pcOffer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 1254 tracksCh <- track 1255 for { 1256 if _, _, readErr := track.ReadRTP(); errors.Is(readErr, io.EOF) { 1257 return 1258 } 1259 } 1260 }) 1261 1262 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 1263 assert.NoError(t, err) 1264 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1265 1266 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{ 1267 Direction: RTPTransceiverDirectionRecvonly, 1268 }) 1269 assert.NoError(t, err) 1270 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1271 1272 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{ 1273 Direction: RTPTransceiverDirectionSendonly, 1274 }) 1275 assert.NoError(t, err) 1276 1277 assert.NoError(t, err) 1278 _, err = pcAnswer.AddTrack(vp8Track) 1279 assert.NoError(t, err) 1280 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1281 1282 ctx, cancel := context.WithCancel(context.Background()) 1283 1284 go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{vp8Track}) 1285 1286 <-tracksCh 1287 cancel() 1288 1289 closePairNow(t, pcOffer, pcAnswer) 1290 } 1291 1292 func TestNegotiationNeededWithRecvonlyTrack(t *testing.T) { 1293 lim := test.TimeOut(time.Second * 30) 1294 defer lim.Stop() 1295 1296 report := test.CheckRoutines(t) 1297 defer report() 1298 1299 pcOffer, pcAnswer, err := newPair() 1300 if err != nil { 1301 t.Fatal(err) 1302 } 1303 1304 var wg sync.WaitGroup 1305 wg.Add(1) 1306 pcAnswer.OnNegotiationNeeded(wg.Done) 1307 1308 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 1309 if err != nil { 1310 t.Fatal(err) 1311 } 1312 1313 if err := signalPair(pcOffer, pcAnswer); err != nil { 1314 t.Fatal(err) 1315 } 1316 1317 onDataChannel, onDataChannelCancel := context.WithCancel(context.Background()) 1318 pcAnswer.OnDataChannel(func(d *DataChannel) { 1319 onDataChannelCancel() 1320 }) 1321 <-onDataChannel.Done() 1322 wg.Wait() 1323 1324 closePairNow(t, pcOffer, pcAnswer) 1325 }