@@ -435,3 +435,138 @@ 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+ assert .NoError (t , err )
480+ }
481+ api := NewAPI (
482+ WithMediaEngine (& m ),
483+ WithInterceptorRegistry (& ir ),
484+ )
485+
486+ pc1 , err := NewPeerConnection (Configuration {})
487+ assert .NoError (t , err )
488+
489+ track1 , err := NewTrackLocalStaticRTP (
490+ RTPCodecCapability {MimeType : MimeTypeVP8 },
491+ "video" , "pion" ,
492+ )
493+ assert .NoError (t , err )
494+ sender , err := pc1 .AddTrack (track1 )
495+ assert .NoError (t , err )
496+
497+ pc2 , err := api .NewPeerConnection (Configuration {})
498+ assert .NoError (t , err )
499+
500+ offer , err := pc1 .CreateOffer (nil )
501+ assert .NoError (t , err )
502+ err = pc1 .SetLocalDescription (offer )
503+ assert .NoError (t , err )
504+ <- GatheringCompletePromise (pc1 )
505+
506+ err = pc2 .SetRemoteDescription (* pc1 .LocalDescription ())
507+ assert .NoError (t , err )
508+ answer , err := pc2 .CreateAnswer (nil )
509+ assert .NoError (t , err )
510+ err = pc2 .SetLocalDescription (answer )
511+ assert .NoError (t , err )
512+ <- GatheringCompletePromise (pc2 )
513+
514+ err = pc1 .SetRemoteDescription (* pc2 .LocalDescription ())
515+ assert .NoError (t , err )
516+
517+ done := make (chan struct {})
518+ pc2 .OnTrack (func (track2 * TrackRemote , _ * RTPReceiver ) {
519+ defer close (done )
520+ p , _ , err2 := track2 .ReadRTP ()
521+ assert .NoError (t , err2 )
522+ time .Sleep (20 * time .Millisecond )
523+ err2 = pc2 .WriteRTCP ([]rtcp.Packet {
524+ & rtcp.TransportLayerNack {
525+ MediaSSRC : uint32 (track2 .SSRC ()),
526+ Nacks : rtcp .NackPairsFromSequenceNumbers (
527+ []uint16 {p .SequenceNumber },
528+ ),
529+ },
530+ })
531+ assert .NoError (t , err2 )
532+ p2 , _ , err2 := track2 .ReadRTP ()
533+ assert .NoError (t , err2 )
534+ assert .Equal (t , p .SequenceNumber , p2 .SequenceNumber )
535+ assert .Equal (t , p .Timestamp , p2 .Timestamp )
536+ assert .Equal (t , p .Payload , p2 .Payload )
537+ })
538+
539+ rtcpDone := make (chan struct {})
540+ go func () {
541+ defer close (rtcpDone )
542+ buf := make ([]byte , 1500 )
543+ for {
544+ _ , _ , err2 := sender .Read (buf )
545+ // nolint
546+ if err2 == io .EOF {
547+ break
548+ }
549+ assert .NoError (t , err2 )
550+ }
551+ }()
552+
553+ go func () {
554+ time .Sleep (20 * time .Millisecond )
555+ var p rtp.Packet
556+ p .Version = 2
557+ p .Marker = true
558+ p .PayloadType = 96
559+ p .SequenceNumber = 0
560+ p .Timestamp = 0
561+ p .Payload = []byte {42 }
562+ err2 := track1 .WriteRTP (& p )
563+ assert .NoError (t , err2 )
564+ }()
565+
566+ <- done
567+ err = pc1 .Close ()
568+ assert .NoError (t , err )
569+ err = pc2 .Close ()
570+ assert .NoError (t , err )
571+ <- rtcpDone
572+ }
0 commit comments