@@ -435,3 +435,137 @@ func testInterceptorNack(t *testing.T, requestNack bool) {
435435 }
436436 }
437437}
438+
439+ // TestInterceptorNackReply is an end-to-end test for the NACK responder.
440+ // It tests that we do receive a resent packet to a NACK, both with and
441+ // without negotiating an RTX track.
442+ func TestInterceptorNackReply (t * testing.T ) {
443+ to := test .TimeOut (time .Second * 20 )
444+ defer to .Stop ()
445+
446+ t .Run ("RTX" , func (t * testing.T ) { testInterceptorNackReply (t , true ) })
447+ t .Run ("NoRTX" , func (t * testing.T ) { testInterceptorNackReply (t , false ) })
448+ }
449+
450+ func testInterceptorNackReply (t * testing.T , negotiateRTX bool ) {
451+ ir := interceptor.Registry {}
452+ m := MediaEngine {}
453+ feedback := []RTCPFeedback {{"nack" , "" }}
454+ err := m .RegisterCodec (
455+ RTPCodecParameters {
456+ RTPCodecCapability : RTPCodecCapability {
457+ "video/VP8" , 90000 , 0 ,
458+ "" ,
459+ feedback ,
460+ },
461+ PayloadType : 96 ,
462+ },
463+ RTPCodecTypeVideo ,
464+ )
465+ assert .NoError (t , err )
466+
467+ if negotiateRTX {
468+ err = m .RegisterCodec (
469+ RTPCodecParameters {
470+ RTPCodecCapability : RTPCodecCapability {
471+ MimeTypeRTX , 90000 , 0 ,
472+ "apt=96" ,
473+ feedback ,
474+ },
475+ PayloadType : 97 ,
476+ },
477+ RTPCodecTypeVideo ,
478+ )
479+ }
480+ api := NewAPI (
481+ WithMediaEngine (& m ),
482+ WithInterceptorRegistry (& ir ),
483+ )
484+
485+ pc1 , err := NewPeerConnection (Configuration {})
486+ assert .NoError (t , err )
487+
488+ track1 , err := NewTrackLocalStaticRTP (
489+ RTPCodecCapability {MimeType : MimeTypeVP8 },
490+ "video" , "pion" ,
491+ )
492+ assert .NoError (t , err )
493+ sender , err := pc1 .AddTrack (track1 )
494+ assert .NoError (t , err )
495+
496+ pc2 , err := api .NewPeerConnection (Configuration {})
497+ assert .NoError (t , err )
498+
499+ offer , err := pc1 .CreateOffer (nil )
500+ assert .NoError (t , err )
501+ err = pc1 .SetLocalDescription (offer )
502+ assert .NoError (t , err )
503+ <- GatheringCompletePromise (pc1 )
504+
505+ err = pc2 .SetRemoteDescription (* pc1 .LocalDescription ())
506+ assert .NoError (t , err )
507+ answer , err := pc2 .CreateAnswer (nil )
508+ assert .NoError (t , err )
509+ err = pc2 .SetLocalDescription (answer )
510+ assert .NoError (t , err )
511+ <- GatheringCompletePromise (pc2 )
512+
513+ err = pc1 .SetRemoteDescription (* pc2 .LocalDescription ())
514+ assert .NoError (t , err )
515+
516+ done := make (chan struct {})
517+ pc2 .OnTrack (func (track2 * TrackRemote , _ * RTPReceiver ) {
518+ defer close (done )
519+ p , _ , err2 := track2 .ReadRTP ()
520+ assert .NoError (t , err2 )
521+ time .Sleep (20 * time .Millisecond )
522+ err2 = pc2 .WriteRTCP ([]rtcp.Packet {
523+ & rtcp.TransportLayerNack {
524+ MediaSSRC : uint32 (track2 .SSRC ()),
525+ Nacks : rtcp .NackPairsFromSequenceNumbers (
526+ []uint16 {p .SequenceNumber },
527+ ),
528+ },
529+ })
530+ assert .NoError (t , err2 )
531+ p2 , _ , err2 := track2 .ReadRTP ()
532+ assert .NoError (t , err2 )
533+ assert .Equal (t , p .SequenceNumber , p2 .SequenceNumber )
534+ assert .Equal (t , p .Timestamp , p2 .Timestamp )
535+ assert .Equal (t , p .Payload , p2 .Payload )
536+ })
537+
538+ rtcpDone := make (chan struct {})
539+ go func () {
540+ defer close (rtcpDone )
541+ buf := make ([]byte , 1500 )
542+ for {
543+ _ , _ , err2 := sender .Read (buf )
544+ // nolint
545+ if err2 == io .EOF {
546+ break
547+ }
548+ assert .NoError (t , err2 )
549+ }
550+ }()
551+
552+ go func () {
553+ time .Sleep (20 * time .Millisecond )
554+ var p rtp.Packet
555+ p .Version = 2
556+ p .Marker = true
557+ p .PayloadType = 96
558+ p .SequenceNumber = 0
559+ p .Timestamp = 0
560+ p .Payload = []byte {42 }
561+ err2 := track1 .WriteRTP (& p )
562+ assert .NoError (t , err2 )
563+ }()
564+
565+ <- done
566+ err = pc1 .Close ()
567+ assert .NoError (t , err )
568+ err = pc2 .Close ()
569+ assert .NoError (t , err )
570+ <- rtcpDone
571+ }
0 commit comments