Repository: pion/interceptor (go)

Issue:
NACK generator interceptor implements `BindRemoteStream` but `UnbindLocalStream`
I assume it should instead implement `UnbindRemoteStream`?

Pull request:
Unbinding should happen on remote stream
#### Description

The interceptor embeds the NoOp interceptor, so this is not a breaking change. UnbindLocalStream will no longer remove any state. Only when UnbindRemoteStream is called will the state be removed.

#### Reference issue
Fixes #154 


Patch:
diff --git a/AUTHORS.txt b/AUTHORS.txt
index 42752d4..aebbe97 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -8,6 +8,7 @@ Adam Kiss <masterada@gmail.com>
 adamroach <adam@nostrum.com>
 Aditya Kumar <k.aditya00@gmail.com>
 aler9 <46489434+aler9@users.noreply.github.com>
+Antoine <antoine@tenten.app>
 Antoine Baché <antoine@tenten.app>
 Atsushi Watanabe <atsushi.w@ieee.org>
 Bobby Peck <rpeck@mux.com>
@@ -15,8 +16,11 @@ boks1971 <raja.gobi@tutanota.com>
 David Zhao <david@davidzhao.com>
 Jonathan Müller <jonathan@fotokite.com>
 Kevin Caffrey <kcaffrey@gmail.com>
+Maksim Nesterov <msnesterov@avito.ru>
 Mathis Engelbart <mathis.engelbart@gmail.com>
 Sean DuBois <sean@siobud.com>
+Steffen Vogel <post@steffenvogel.de>
+ziminghua <565209960@qq.com>
 
 # List of contributors not appearing in Git history
 
diff --git a/README.md b/README.md
index b35cac7..dcc856f 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,8 @@
   <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-interceptor-gray.svg?longCache=true&colorB=brightgreen" alt="Pion Interceptor"></a>
   <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
   <br>
-  <a href="https://pkg.go.dev/github.com/pion/interceptor"><img src="https://godoc.org/github.com/pion/interceptor?status.svg" alt="GoDoc"></a>
+  <img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/interceptor/test.yaml">
+  <a href="https://pkg.go.dev/github.com/pion/interceptor"><img src="https://pkg.go.dev/badge/github.com/pion/interceptor.svg" alt="Go Reference"></a>
   <a href="https://codecov.io/gh/pion/interceptor"><img src="https://codecov.io/gh/pion/interceptor/branch/master/graph/badge.svg" alt="Coverage Status"></a>
   <a href="https://goreportcard.com/report/github.com/pion/interceptor"><img src="https://goreportcard.com/badge/github.com/pion/interceptor" alt="Go Report Card"></a>
   <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
@@ -27,19 +28,20 @@ by anyone. With the following tenets in mind.
 * Encourage modification. Add your own interceptors without forking. Mixing with the ones we provide.
 * Empower learning. This code base should be useful to read and learn even if you aren't using Pion.
 
-#### Current Interceptors
+### Current Interceptors
 * [NACK Generator/Responder](https://github.com/pion/interceptor/tree/master/pkg/nack)
 * [Sender and Receiver Reports](https://github.com/pion/interceptor/tree/master/pkg/report)
 * [Transport Wide Congestion Control Feedback](https://github.com/pion/interceptor/tree/master/pkg/twcc)
 * [Packet Dump](https://github.com/pion/interceptor/tree/master/pkg/packetdump)
 * [Google Congestion Control](https://github.com/pion/interceptor/tree/master/pkg/gcc)
+* [Stats](https://github.com/pion/interceptor/tree/master/pkg/stats) A [webrtc-stats](https://www.w3.org/TR/webrtc-stats/) compliant statistics generation
+* [Interval PLI](https://github.com/pion/interceptor/tree/master/pkg/intervalpli) Generate PLI on a interval. Useful when no decoder is available.
 
-#### Planned Interceptors
+### Planned Interceptors
 * Bandwidth Estimation
   - [NADA](https://tools.ietf.org/html/rfc8698)
 * JitterBuffer, re-order packets and wait for arrival
 * [FlexFec](https://tools.ietf.org/html/draft-ietf-payload-flexible-fec-scheme-20)
-* [webrtc-stats](https://www.w3.org/TR/webrtc-stats/) compliant statistics generation
 * [RTCP Feedback for Congestion Control](https://datatracker.ietf.org/doc/html/rfc8888) the standardized alternative to TWCC.
 
 ### Interceptor Public API
@@ -64,15 +66,19 @@ sequentially as the packet moves through them.
 The [examples](https://github.com/pion/interceptor/blob/master/examples) directory provides some basic examples. If you need more please file an issue!
 You should also look in [pion/webrtc](https://github.com/pion/webrtc) for real world examples.
 
+### Roadmap
+The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
+
 ### Community
-Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion).
+Pion has an active community on the [Slack](https://pion.ly/slack).
 
-We are always looking to support **your projects**. Please reach out if you have something to build!
+Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
 
+We are always looking to support **your projects**. Please reach out if you have something to build!
 If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
 
 ### Contributing
-Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
+Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt)
 
 ### License
 MIT License - see [LICENSE](LICENSE) for full text
diff --git a/examples/nack/main.go b/examples/nack/main.go
index 316809c..9cdb674 100644
--- a/examples/nack/main.go
+++ b/examples/nack/main.go
@@ -1,3 +1,4 @@
+// Package demonstrates how to use the NACK interceptor
 package main
 
 import (
diff --git a/go.mod b/go.mod
index 59cda13..f4d83c5 100644
--- a/go.mod
+++ b/go.mod
@@ -6,5 +6,6 @@ require (
 	github.com/pion/logging v0.2.2
 	github.com/pion/rtcp v1.2.10
 	github.com/pion/rtp v1.7.13
+	github.com/pion/transport/v2 v2.2.0
 	github.com/stretchr/testify v1.8.2
 )
diff --git a/go.sum b/go.sum
index 4d5c42b..5124e5b 100644
--- a/go.sum
+++ b/go.sum
@@ -9,6 +9,8 @@ github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
 github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
 github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
 github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
+github.com/pion/transport/v2 v2.2.0 h1:u5lFqFHkXLMXMzai8tixZDfVjb8eOjH35yCunhPeb1c=
+github.com/pion/transport/v2 v2.2.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -18,6 +20,42 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/cc/feedback_adapter.go b/internal/cc/feedback_adapter.go
index fe94ff0..30ff1ac 100644
--- a/internal/cc/feedback_adapter.go
+++ b/internal/cc/feedback_adapter.go
@@ -136,7 +136,7 @@ func (f *FeedbackAdapter) unpackStatusVectorChunk(start uint16, refTime time.Tim
 
 // OnTransportCCFeedback converts incoming TWCC RTCP packet feedback to
 // Acknowledgments.
-func (f *FeedbackAdapter) OnTransportCCFeedback(ts time.Time, feedback *rtcp.TransportLayerCC) ([]Acknowledgment, error) {
+func (f *FeedbackAdapter) OnTransportCCFeedback(_ time.Time, feedback *rtcp.TransportLayerCC) ([]Acknowledgment, error) {
 	f.lock.Lock()
 	defer f.lock.Unlock()
 
@@ -175,7 +175,7 @@ func (f *FeedbackAdapter) OnTransportCCFeedback(ts time.Time, feedback *rtcp.Tra
 
 // OnRFC8888Feedback converts incoming Congestion Control Feedback RTCP packet
 // to Acknowledgments.
-func (f *FeedbackAdapter) OnRFC8888Feedback(ts time.Time, feedback *rtcp.CCFeedbackReport) []Acknowledgment {
+func (f *FeedbackAdapter) OnRFC8888Feedback(_ time.Time, feedback *rtcp.CCFeedbackReport) []Acknowledgment {
 	f.lock.Lock()
 	defer f.lock.Unlock()
 
diff --git a/pkg/gcc/adaptive_threshold.go b/pkg/gcc/adaptive_threshold.go
index c7a6012..e72ce38 100644
--- a/pkg/gcc/adaptive_threshold.go
+++ b/pkg/gcc/adaptive_threshold.go
@@ -57,7 +57,7 @@ func newAdaptiveThreshold(opts ...adaptiveThresholdOption) *adaptiveThreshold {
 	return at
 }
 
-func (a *adaptiveThreshold) compare(estimate, dt time.Duration) (usage, time.Duration, time.Duration) {
+func (a *adaptiveThreshold) compare(estimate, _ time.Duration) (usage, time.Duration, time.Duration) {
 	a.numDeltas++
 	if a.numDeltas < 2 {
 		return usageNormal, estimate, a.max
diff --git a/pkg/gcc/send_side_bwe.go b/pkg/gcc/send_side_bwe.go
index c1a28e5..70c6b31 100644
--- a/pkg/gcc/send_side_bwe.go
+++ b/pkg/gcc/send_side_bwe.go
@@ -153,7 +153,7 @@ func (e *SendSideBWE) AddStream(info *interceptor.StreamInfo, writer interceptor
 }
 
 // WriteRTCP adds some RTCP feedback to the bandwidth estimator
-func (e *SendSideBWE) WriteRTCP(pkts []rtcp.Packet, attributes interceptor.Attributes) error {
+func (e *SendSideBWE) WriteRTCP(pkts []rtcp.Packet, _ interceptor.Attributes) error {
 	now := time.Now()
 	e.closeLock.RLock()
 	defer e.closeLock.RUnlock()
diff --git a/pkg/intervalpli/generator_interceptor.go b/pkg/intervalpli/generator_interceptor.go
new file mode 100644
index 0000000..7caa844
--- /dev/null
+++ b/pkg/intervalpli/generator_interceptor.go
@@ -0,0 +1,175 @@
+package intervalpli
+
+import (
+	"sync"
+	"time"
+
+	"github.com/pion/interceptor"
+	"github.com/pion/logging"
+	"github.com/pion/rtcp"
+)
+
+// GeneratorInterceptor interceptor sends PLI packets.
+// Implements PLI in a naive way: sends a PLI for each new track that support PLI, periodically.
+type GeneratorInterceptor struct {
+	interceptor.NoOp
+
+	interval           time.Duration
+	streams            sync.Map
+	immediatePLINeeded chan []uint32
+
+	log logging.LeveledLogger
+	m   sync.Mutex
+	wg  sync.WaitGroup
+
+	close chan struct{}
+}
+
+// NewGeneratorInterceptor returns a new GeneratorInterceptor interceptor.
+func NewGeneratorInterceptor(opts ...GeneratorOption) (*GeneratorInterceptor, error) {
+	r := &GeneratorInterceptor{
+		interval:           3 * time.Second,
+		log:                logging.NewDefaultLoggerFactory().NewLogger("pli_generator"),
+		immediatePLINeeded: make(chan []uint32, 1),
+		close:              make(chan struct{}),
+	}
+
+	for _, opt := range opts {
+		if err := opt(r); err != nil {
+			return nil, err
+		}
+	}
+
+	return r, nil
+}
+
+func (r *GeneratorInterceptor) isClosed() bool {
+	select {
+	case <-r.close:
+		return true
+	default:
+		return false
+	}
+}
+
+// Close closes the interceptor.
+func (r *GeneratorInterceptor) Close() error {
+	defer r.wg.Wait()
+	r.m.Lock()
+	defer r.m.Unlock()
+
+	if !r.isClosed() {
+		close(r.close)
+	}
+
+	return nil
+}
+
+// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
+// will be called once per packet batch.
+func (r *GeneratorInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
+	r.m.Lock()
+	defer r.m.Unlock()
+
+	if r.isClosed() {
+		return writer
+	}
+
+	r.wg.Add(1)
+
+	go r.loop(writer)
+
+	return writer
+}
+
+func (r *GeneratorInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
+	defer r.wg.Done()
+
+	ticker, tickerChan := r.createLoopTicker()
+
+	defer func() {
+		if ticker != nil {
+			ticker.Stop()
+		}
+	}()
+
+	for {
+		select {
+		case ssrcs := <-r.immediatePLINeeded:
+			r.writePLIs(rtcpWriter, ssrcs)
+
+		case <-tickerChan:
+			ssrcs := make([]uint32, 0)
+
+			r.streams.Range(func(k, value interface{}) bool {
+				key, ok := k.(uint32)
+				if !ok {
+					return false
+				}
+
+				ssrcs = append(ssrcs, key)
+				return true
+			})
+
+			r.writePLIs(rtcpWriter, ssrcs)
+
+		case <-r.close:
+			return
+		}
+	}
+}
+
+func (r *GeneratorInterceptor) createLoopTicker() (*time.Ticker, <-chan time.Time) {
+	if r.interval > 0 {
+		ticker := time.NewTicker(r.interval)
+		return ticker, ticker.C
+	}
+
+	return nil, make(chan time.Time)
+}
+
+func (r *GeneratorInterceptor) writePLIs(rtcpWriter interceptor.RTCPWriter, ssrcs []uint32) {
+	if len(ssrcs) == 0 {
+		return
+	}
+
+	pkts := []rtcp.Packet{}
+
+	for _, ssrc := range ssrcs {
+		pkts = append(pkts, &rtcp.PictureLossIndication{MediaSSRC: ssrc})
+	}
+
+	if _, err := rtcpWriter.Write(pkts, interceptor.Attributes{}); err != nil {
+		r.log.Warnf("failed sending: %+v", err)
+	}
+}
+
+// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
+// will be called once per rtp packet.
+func (r *GeneratorInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
+	if !streamSupportPli(info) {
+		return reader
+	}
+
+	r.streams.Store(info.SSRC, nil)
+	// New streams need to receive a PLI as soon as possible.
+	r.ForcePLI(info.SSRC)
+
+	return reader
+}
+
+// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
+func (r *GeneratorInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
+	r.streams.Delete(info.SSRC)
+}
+
+// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
+// change in the future. The returned method will be called once per packet batch.
+func (r *GeneratorInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
+	return reader
+}
+
+// ForcePLI sends a PLI request to the tracks matching the provided SSRCs.
+func (r *GeneratorInterceptor) ForcePLI(ssrc ...uint32) {
+	r.immediatePLINeeded <- ssrc
+}
diff --git a/pkg/intervalpli/generator_option.go b/pkg/intervalpli/generator_option.go
new file mode 100644
index 0000000..d717ad2
--- /dev/null
+++ b/pkg/intervalpli/generator_option.go
@@ -0,0 +1,26 @@
+package intervalpli
+
+import (
+	"time"
+
+	"github.com/pion/logging"
+)
+
+// GeneratorOption can be used to configure GeneratorInterceptor.
+type GeneratorOption func(r *GeneratorInterceptor) error
+
+// GeneratorLog sets a logger for the interceptor.
+func GeneratorLog(log logging.LeveledLogger) GeneratorOption {
+	return func(r *GeneratorInterceptor) error {
+		r.log = log
+		return nil
+	}
+}
+
+// GeneratorInterval sets send interval for the interceptor.
+func GeneratorInterval(interval time.Duration) GeneratorOption {
+	return func(r *GeneratorInterceptor) error {
+		r.interval = interval
+		return nil
+	}
+}
diff --git a/pkg/intervalpli/pli.go b/pkg/intervalpli/pli.go
new file mode 100644
index 0000000..10ea6a3
--- /dev/null
+++ b/pkg/intervalpli/pli.go
@@ -0,0 +1,14 @@
+// Package intervalpli is an interceptor that requests PLI on a static interval. Useful when bridging protocols that don't have receiver feedback
+package intervalpli
+
+import "github.com/pion/interceptor"
+
+func streamSupportPli(info *interceptor.StreamInfo) bool {
+	for _, fb := range info.RTCPFeedback {
+		if fb.Type == "nack" && fb.Parameter == "pli" {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/pkg/nack/generator_interceptor.go b/pkg/nack/generator_interceptor.go
index f55f4d7..30025ed 100644
--- a/pkg/nack/generator_interceptor.go
+++ b/pkg/nack/generator_interceptor.go
@@ -16,7 +16,7 @@ type GeneratorInterceptorFactory struct {
 }
 
 // NewInterceptor constructs a new ReceiverInterceptor
-func (g *GeneratorInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (g *GeneratorInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	i := &GeneratorInterceptor{
 		size:        512,
 		skipLastN:   0,
@@ -108,8 +108,8 @@ func (n *GeneratorInterceptor) BindRemoteStream(info *interceptor.StreamInfo, re
 	})
 }
 
-// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
-func (n *GeneratorInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
+// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
+func (n *GeneratorInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) {
 	n.receiveLogsMu.Lock()
 	delete(n.receiveLogs, info.SSRC)
 	n.receiveLogsMu.Unlock()
diff --git a/pkg/nack/receive_log.go b/pkg/nack/receive_log.go
index 8107f59..14dc00b 100644
--- a/pkg/nack/receive_log.go
+++ b/pkg/nack/receive_log.go
@@ -127,8 +127,11 @@ func (s *receiveLog) getReceived(seq uint16) bool {
 
 func (s *receiveLog) fixLastConsecutive() {
 	i := s.lastConsecutive + 1
+
+	// nolint:revive
 	for ; i != s.end+1 && s.getReceived(i); i++ {
 		// find all consecutive packets
 	}
+
 	s.lastConsecutive = i - 1
 }
diff --git a/pkg/nack/responder_interceptor.go b/pkg/nack/responder_interceptor.go
index 7f3123f..e992090 100644
--- a/pkg/nack/responder_interceptor.go
+++ b/pkg/nack/responder_interceptor.go
@@ -19,7 +19,7 @@ type packetFactory interface {
 }
 
 // NewInterceptor constructs a new ResponderInterceptor
-func (r *ResponderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (r *ResponderInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	i := &ResponderInterceptor{
 		size:    1024,
 		log:     logging.NewDefaultLoggerFactory().NewLogger("nack_responder"),
diff --git a/pkg/nack/retainable_packet.go b/pkg/nack/retainable_packet.go
index fa0afea..07a3e8b 100644
--- a/pkg/nack/retainable_packet.go
+++ b/pkg/nack/retainable_packet.go
@@ -80,7 +80,7 @@ func (f *noOpPacketFactory) NewPacket(header *rtp.Header, payload []byte) (*reta
 	}, nil
 }
 
-func (f *noOpPacketFactory) releasePacket(header *rtp.Header, payload *[]byte) {
+func (f *noOpPacketFactory) releasePacket(*rtp.Header, *[]byte) {
 	// no-op
 }
 
diff --git a/pkg/packetdump/receiver_interceptor.go b/pkg/packetdump/receiver_interceptor.go
index bc7b6f2..670f80e 100644
--- a/pkg/packetdump/receiver_interceptor.go
+++ b/pkg/packetdump/receiver_interceptor.go
@@ -17,7 +17,7 @@ func NewReceiverInterceptor(opts ...PacketDumperOption) (*ReceiverInterceptorFac
 }
 
 // NewInterceptor returns a new ReceiverInterceptor interceptor.
-func (r *ReceiverInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (r *ReceiverInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	dumper, err := NewPacketDumper(r.opts...)
 	if err != nil {
 		return nil, err
@@ -38,7 +38,7 @@ type ReceiverInterceptor struct {
 
 // BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
 // will be called once per rtp packet.
-func (r *ReceiverInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
+func (r *ReceiverInterceptor) BindRemoteStream(_ *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
 	return interceptor.RTPReaderFunc(func(bytes []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
 		i, attr, err := reader.Read(bytes, attributes)
 		if err != nil {
diff --git a/pkg/packetdump/sender_interceptor.go b/pkg/packetdump/sender_interceptor.go
index ae65466..a15c30e 100644
--- a/pkg/packetdump/sender_interceptor.go
+++ b/pkg/packetdump/sender_interceptor.go
@@ -19,7 +19,7 @@ func NewSenderInterceptor(opts ...PacketDumperOption) (*SenderInterceptorFactory
 }
 
 // NewInterceptor returns a new SenderInterceptor interceptor
-func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (s *SenderInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	dumper, err := NewPacketDumper(s.opts...)
 	if err != nil {
 		return nil, err
@@ -47,7 +47,7 @@ func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interc
 
 // BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
 // will be called once per rtp packet.
-func (s *SenderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
+func (s *SenderInterceptor) BindLocalStream(_ *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
 	return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
 		s.logRTPPacket(header, payload, attributes)
 		return writer.Write(header, payload, attributes)
diff --git a/pkg/report/receiver_interceptor.go b/pkg/report/receiver_interceptor.go
index eb7aff9..e4df8b4 100644
--- a/pkg/report/receiver_interceptor.go
+++ b/pkg/report/receiver_interceptor.go
@@ -15,7 +15,7 @@ type ReceiverInterceptorFactory struct {
 }
 
 // NewInterceptor constructs a new ReceiverInterceptor
-func (r *ReceiverInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (r *ReceiverInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	i := &ReceiverInterceptor{
 		interval: 1 * time.Second,
 		now:      time.Now,
diff --git a/pkg/report/sender_interceptor.go b/pkg/report/sender_interceptor.go
index d191392..bbcafde 100644
--- a/pkg/report/sender_interceptor.go
+++ b/pkg/report/sender_interceptor.go
@@ -16,7 +16,7 @@ type SenderInterceptorFactory struct {
 }
 
 // NewInterceptor constructs a new SenderInterceptor
-func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (s *SenderInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	i := &SenderInterceptor{
 		interval: 1 * time.Second,
 		now:      time.Now,
diff --git a/pkg/rfc8888/interceptor.go b/pkg/rfc8888/interceptor.go
index 1529d5c..9adddb4 100644
--- a/pkg/rfc8888/interceptor.go
+++ b/pkg/rfc8888/interceptor.go
@@ -20,7 +20,7 @@ type SenderInterceptorFactory struct {
 }
 
 // NewInterceptor constructs a new SenderInterceptor
-func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (s *SenderInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	i := &SenderInterceptor{
 		NoOp:          interceptor.NoOp{},
 		log:           logging.NewDefaultLoggerFactory().NewLogger("rfc8888_interceptor"),
@@ -90,7 +90,7 @@ func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interc
 
 // BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
 // will be called once per rtp packet.
-func (s *SenderInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
+func (s *SenderInterceptor) BindRemoteStream(_ *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
 	return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
 		i, attr, err := reader.Read(b, a)
 		if err != nil {
diff --git a/pkg/stats/stats_recorder.go b/pkg/stats/stats_recorder.go
index e6206fb..11bcc9e 100644
--- a/pkg/stats/stats_recorder.go
+++ b/pkg/stats/stats_recorder.go
@@ -223,7 +223,7 @@ func (r *recorder) recordIncomingXR(latestStats internalStats, pkt *rtcp.Extende
 					for i := min(r.maxLastReceiverReferenceTimes, len(latestStats.lastReceiverReferenceTimes)) - 1; i >= 0; i-- {
 						lastRR := latestStats.lastReceiverReferenceTimes[i]
 						if (lastRR&0x0000FFFFFFFF0000)>>16 == uint64(xrReport.LastRR) {
-							dlrr := time.Duration(xrReport.DLRR/65536.0) * time.Second
+							dlrr := time.Duration(float64(xrReport.DLRR) / 65536.0 * float64(time.Second))
 							latestStats.RemoteOutboundRTPStreamStats.RoundTripTime = (ts.Add(-dlrr)).Sub(ntp.ToTime(lastRR))
 							latestStats.RemoteOutboundRTPStreamStats.TotalRoundTripTime += latestStats.RemoteOutboundRTPStreamStats.RoundTripTime
 							latestStats.RemoteOutboundRTPStreamStats.RoundTripTimeMeasurements++
diff --git a/pkg/twcc/header_extension_interceptor.go b/pkg/twcc/header_extension_interceptor.go
index fa3fb81..b1d4d95 100644
--- a/pkg/twcc/header_extension_interceptor.go
+++ b/pkg/twcc/header_extension_interceptor.go
@@ -11,7 +11,7 @@ import (
 type HeaderExtensionInterceptorFactory struct{}
 
 // NewInterceptor constructs a new HeaderExtensionInterceptor
-func (h *HeaderExtensionInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (h *HeaderExtensionInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	return &HeaderExtensionInterceptor{}, nil
 }
 
diff --git a/pkg/twcc/sender_interceptor.go b/pkg/twcc/sender_interceptor.go
index 89df1ca..1f0d57f 100644
--- a/pkg/twcc/sender_interceptor.go
+++ b/pkg/twcc/sender_interceptor.go
@@ -1,6 +1,7 @@
 package twcc
 
 import (
+	"errors"
 	"math/rand"
 	"sync"
 	"time"
@@ -15,8 +16,10 @@ type SenderInterceptorFactory struct {
 	opts []Option
 }
 
+var errClosed = errors.New("interceptor is closed")
+
 // NewInterceptor constructs a new SenderInterceptor
-func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
+func (s *SenderInterceptorFactory) NewInterceptor(string) (interceptor.Interceptor, error) {
 	i := &SenderInterceptor{
 		log:        logging.NewDefaultLoggerFactory().NewLogger("twcc_sender_interceptor"),
 		packetChan: make(chan packet),
@@ -129,12 +132,17 @@ func (s *SenderInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reade
 				return 0, nil, err
 			}
 
-			s.packetChan <- packet{
+			p := packet{
 				hdr:            header,
 				sequenceNumber: tccExt.TransportSequence,
 				arrivalTime:    time.Since(s.startTime).Microseconds(),
 				ssrc:           info.SSRC,
 			}
+			select {
+			case <-s.close:
+				return 0, nil, errClosed
+			case s.packetChan <- p:
+			}
 		}
 
 		return i, attr, nil


Test patch:
diff --git a/pkg/gcc/send_side_bwe_test.go b/pkg/gcc/send_side_bwe_test.go
index 34ddfa0..e66ddb1 100644
--- a/pkg/gcc/send_side_bwe_test.go
+++ b/pkg/gcc/send_side_bwe_test.go
@@ -19,7 +19,7 @@ type mockTWCCResponder struct {
 	rtpChan chan []byte
 }
 
-func (m *mockTWCCResponder) Read(out []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
+func (m *mockTWCCResponder) Read(out []byte, _ interceptor.Attributes) (int, interceptor.Attributes, error) {
 	pkt := <-m.rtpChan
 	copy(out, pkt)
 	return len(pkt), nil, nil
@@ -35,7 +35,7 @@ type mockGCCWriteStream struct {
 	twccResponder *mockTWCCResponder
 }
 
-func (m *mockGCCWriteStream) Write(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
+func (m *mockGCCWriteStream) Write(header *rtp.Header, payload []byte, _ interceptor.Attributes) (int, error) {
 	pkt, err := (&rtp.Packet{Header: *header, Payload: payload}).Marshal()
 	if err != nil {
 		panic(err)
@@ -125,12 +125,10 @@ func BenchmarkSendSideBWE_WriteRTCP(b *testing.B) {
 				for j := 0; j < seqs; j++ {
 					seq++
 
-					if rand.Intn(5) == 0 { //nolint:gosec,staticcheck
-						// skip this packet
+					if rand.Intn(5) != 0 { //nolint:gosec,staticcheck
+						arrivalTime += int64(rtcp.TypeTCCDeltaScaleFactor * (rand.Intn(128) + 1)) //nolint:gosec
+						r.Record(5000, seq, arrivalTime)
 					}
-
-					arrivalTime += int64(rtcp.TypeTCCDeltaScaleFactor * (rand.Intn(128) + 1)) //nolint:gosec
-					r.Record(5000, seq, arrivalTime)
 				}
 
 				rtcpPackets := r.BuildFeedbackPacket()
diff --git a/pkg/intervalpli/generator_interceptor_test.go b/pkg/intervalpli/generator_interceptor_test.go
new file mode 100644
index 0000000..96a28f5
--- /dev/null
+++ b/pkg/intervalpli/generator_interceptor_test.go
@@ -0,0 +1,84 @@
+package intervalpli
+
+import (
+	"testing"
+	"time"
+
+	"github.com/pion/interceptor"
+	"github.com/pion/interceptor/internal/test"
+	"github.com/pion/logging"
+	"github.com/pion/rtcp"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPLIGeneratorInterceptor_Unsupported(t *testing.T) {
+	i, err := NewGeneratorInterceptor(
+		GeneratorInterval(time.Millisecond*10),
+		GeneratorLog(logging.NewDefaultLoggerFactory().NewLogger("test")),
+	)
+	assert.Nil(t, err)
+
+	streamSSRC := uint32(123456)
+	stream := test.NewMockStream(&interceptor.StreamInfo{
+		SSRC:     streamSSRC,
+		MimeType: "video/h264",
+	}, i)
+	defer func() {
+		assert.NoError(t, stream.Close())
+	}()
+
+	timeout := time.NewTimer(100 * time.Millisecond)
+	defer timeout.Stop()
+	select {
+	case <-timeout.C:
+		return
+	case <-stream.WrittenRTCP():
+		assert.FailNow(t, "should not receive any PIL")
+	}
+}
+
+func TestPLIGeneratorInterceptor(t *testing.T) {
+	i, err := NewGeneratorInterceptor(
+		GeneratorInterval(time.Second*1),
+		GeneratorLog(logging.NewDefaultLoggerFactory().NewLogger("test")),
+	)
+	assert.Nil(t, err)
+
+	streamSSRC := uint32(123456)
+	stream := test.NewMockStream(&interceptor.StreamInfo{
+		SSRC:      streamSSRC,
+		ClockRate: 90000,
+		MimeType:  "video/h264",
+		RTCPFeedback: []interceptor.RTCPFeedback{
+			{Type: "nack", Parameter: "pli"},
+		},
+	}, i)
+	defer func() {
+		assert.NoError(t, stream.Close())
+	}()
+
+	pkts := <-stream.WrittenRTCP()
+	assert.Equal(t, len(pkts), 1)
+	sr, ok := pkts[0].(*rtcp.PictureLossIndication)
+	assert.True(t, ok)
+	assert.Equal(t, &rtcp.PictureLossIndication{MediaSSRC: streamSSRC}, sr)
+
+	// Should not have another packet immediately...
+	func() {
+		timeout := time.NewTimer(100 * time.Millisecond)
+		defer timeout.Stop()
+		select {
+		case <-timeout.C:
+			return
+		case <-stream.WrittenRTCP():
+			assert.FailNow(t, "should not receive any PIL")
+		}
+	}()
+
+	// ... but should receive one 1sec later.
+	pkts = <-stream.WrittenRTCP()
+	assert.Equal(t, len(pkts), 1)
+	sr, ok = pkts[0].(*rtcp.PictureLossIndication)
+	assert.True(t, ok)
+	assert.Equal(t, &rtcp.PictureLossIndication{MediaSSRC: streamSSRC}, sr)
+}
diff --git a/pkg/nack/responder_interceptor_test.go b/pkg/nack/responder_interceptor_test.go
index fa1d881..ecc5bb5 100644
--- a/pkg/nack/responder_interceptor_test.go
+++ b/pkg/nack/responder_interceptor_test.go
@@ -113,7 +113,7 @@ func TestResponderInterceptor_DisableCopy(t *testing.T) {
 
 // this test is only useful when being run with the race detector, it won't fail otherwise:
 //
-//     go test -race ./pkg/nack/
+// go test -race ./pkg/nack/
 func TestResponderInterceptor_Race(t *testing.T) {
 	f, err := NewResponderInterceptor(
 		ResponderSize(32768),
diff --git a/pkg/nack/send_buffer_test.go b/pkg/nack/send_buffer_test.go
index 751f6c0..7b00009 100644
--- a/pkg/nack/send_buffer_test.go
+++ b/pkg/nack/send_buffer_test.go
@@ -98,7 +98,7 @@ func TestSendBuffer_Overridden(t *testing.T) {
 
 // this test is only useful when being run with the race detector, it won't fail otherwise:
 //
-//     go test -race ./pkg/nack/
+// go test -race ./pkg/nack/
 func TestSendBuffer_Race(t *testing.T) {
 	pm := newPacketManager()
 	for _, start := range []uint16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 511, 512, 513, 32767, 32768, 32769, 65527, 65528, 65529, 65530, 65531, 65532, 65533, 65534, 65535} {
diff --git a/pkg/report/receiver_interceptor_test.go b/pkg/report/receiver_interceptor_test.go
index 412122c..aa87a81 100644
--- a/pkg/report/receiver_interceptor_test.go
+++ b/pkg/report/receiver_interceptor_test.go
@@ -182,7 +182,7 @@ func TestReceiverInterceptor(t *testing.T) {
 		assert.Equal(t, 1, len(rr.Reports))
 		assert.Equal(t, rtcp.ReceptionReport{
 			SSRC:               uint32(123456),
-			LastSequenceNumber: 1<<16 | 0x0000,
+			LastSequenceNumber: 1 << 16,
 			LastSenderReport:   0,
 			FractionLost:       0,
 			TotalLost:          0,
diff --git a/pkg/stats/stats_recorder_test.go b/pkg/stats/stats_recorder_test.go
index d0a7938..bc2e9b4 100644
--- a/pkg/stats/stats_recorder_test.go
+++ b/pkg/stats/stats_recorder_test.go
@@ -289,3 +289,27 @@ func TestStatsRecorder(t *testing.T) {
 		})
 	}
 }
+
+func TestStatsRecorder_DLRR_Precision(t *testing.T) {
+	r := newRecorder(0, 90_000)
+
+	report := &rtcp.ExtendedReport{
+		Reports: []rtcp.ReportBlock{
+			&rtcp.DLRRReportBlock{
+				Reports: []rtcp.DLRRReport{
+					{
+						SSRC:   5000,
+						LastRR: 762,
+						DLRR:   30000,
+					},
+				},
+			},
+		},
+	}
+
+	s := r.recordIncomingXR(internalStats{
+		lastReceiverReferenceTimes: []uint64{50000000},
+	}, report, time.Time{})
+
+	assert.Equal(t, int64(s.RemoteOutboundRTPStreamStats.RoundTripTime), int64(-9223372036854775808))
+}
diff --git a/pkg/twcc/sender_interceptor_test.go b/pkg/twcc/sender_interceptor_test.go
index 9a9c7f5..36860bb 100644
--- a/pkg/twcc/sender_interceptor_test.go
+++ b/pkg/twcc/sender_interceptor_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/pion/interceptor/internal/test"
 	"github.com/pion/rtcp"
 	"github.com/pion/rtp"
+	transportTest "github.com/pion/transport/v2/test"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -265,3 +266,37 @@ func TestSenderInterceptor(t *testing.T) {
 		}, cc.PacketChunks)
 	})
 }
+
+func TestSenderInterceptor_Leak(t *testing.T) {
+	lim := transportTest.TimeOut(time.Second * 10)
+	defer lim.Stop()
+
+	report := transportTest.CheckRoutines(t)
+	defer report()
+
+	f, err := NewSenderInterceptor(SendInterval(200 * time.Millisecond))
+	assert.NoError(t, err)
+
+	i, err := f.NewInterceptor("")
+	assert.NoError(t, err)
+
+	stream := test.NewMockStream(&interceptor.StreamInfo{RTPHeaderExtensions: []interceptor.RTPHeaderExtension{
+		{
+			URI: transportCCURI,
+			ID:  1,
+		},
+	}}, i)
+	defer func() {
+		assert.NoError(t, stream.Close())
+	}()
+
+	assert.NoError(t, i.Close())
+	for _, i := range []int{0, 1, 2, 3, 4, 5} {
+		hdr := rtp.Header{}
+		tcc, err := (&rtp.TransportCCExtension{TransportSequence: uint16(i)}).Marshal()
+		assert.NoError(t, err)
+
+		assert.NoError(t, hdr.SetExtension(1, tcc))
+		stream.ReceiveRTP(&rtp.Packet{Header: hdr})
+	}
+}