The Interval PLI (Picture Loss Indication) interceptor automatically requests keyframes from video senders at regular intervals. This is useful for handling video corruption, stream recovery, and new viewer scenarios.
Overview
The Interval PLI interceptor:
Periodic keyframes : Requests keyframes at configurable intervals
Immediate requests : Supports on-demand keyframe requests
Per-stream control : Works independently for each video stream
Automatic management : Handles timing and RTCP generation
PLI (Picture Loss Indication) is an RTCP feedback message defined in RFC 4585 that requests an intra-coded frame from the video encoder.
Basic Usage
import (
" time "
" github.com/pion/interceptor "
" github.com/pion/interceptor/pkg/intervalpli "
)
// Create interval PLI generator
pliFactory , err := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 3 * time . Second ),
)
if err != nil {
panic ( err )
}
// Register with interceptor registry
m := & interceptor . Registry {}
m . Add ( pliFactory )
Configuration Options
GeneratorInterval
Sets how often to request keyframes:
pliFactory , err := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 3 * time . Second ), // Default: 3 seconds
)
Setting the interval to 0 disables periodic keyframe requests, but still allows manual requests via ForcePLI().
GeneratorLog / WithLoggerFactory
Configure logging for debugging:
pliFactory , err := intervalpli . NewReceiverInterceptor (
intervalpli . WithLoggerFactory ( loggerFactory ),
)
Manual Keyframe Requests
You can manually trigger keyframe requests:
// Get the interceptor instance
interceptor , err := intervalpli . NewGeneratorInterceptor (
intervalpli . GeneratorInterval ( 0 ), // Disable periodic
)
if err != nil {
panic ( err )
}
// Request keyframe for specific SSRCs
ssrc := uint32 ( 12345 )
interceptor . ForcePLI ( ssrc )
// Request for multiple streams
interceptor . ForcePLI ( ssrc1 , ssrc2 , ssrc3 )
Complete Example
package main
import (
" log "
" time "
" github.com/pion/interceptor "
" github.com/pion/interceptor/pkg/intervalpli "
" github.com/pion/webrtc/v4 "
)
func main () {
// Create interceptor registry
i := & interceptor . Registry {}
// Configure interval PLI
pliFactory , err := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 3 * time . Second ),
)
if err != nil {
panic ( err )
}
i . Add ( pliFactory )
// Create media engine and API
m := & webrtc . MediaEngine {}
if err := m . RegisterDefaultCodecs (); err != nil {
panic ( err )
}
api := webrtc . NewAPI (
webrtc . WithMediaEngine ( m ),
webrtc . WithInterceptorRegistry ( i ),
)
// Create peer connection
pc , err := api . NewPeerConnection ( webrtc . Configuration {})
if err != nil {
panic ( err )
}
defer pc . Close ()
// Handle incoming tracks
pc . OnTrack ( func ( track * webrtc . TrackRemote , receiver * webrtc . RTPReceiver ) {
log . Printf ( "Track started: %s " , track . Codec (). MimeType )
// PLI will be automatically sent every 3 seconds
// for this video track
})
log . Println ( "Interval PLI configured" )
select {}
}
How It Works
Automatic PLI Generation
Track Detection : When a video track is added, it’s registered
Timer : A ticker fires at the configured interval
PLI Generation : RTCP PLI packets are created for all registered tracks
Transmission : PLI packets are sent to video senders
Keyframe : Video encoder generates and sends an intra-frame
// Simplified PLI generation loop
for {
select {
case <- ticker . C :
for _ , ssrc := range videoStreams {
sendPLI ( ssrc ) // Request keyframe
}
}
}
PLI Packet Structure
type PictureLossIndication struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of media source needing keyframe
MediaSSRC uint32
}
Use Cases
New Viewer Scenario
// When a new viewer joins, immediately request a keyframe
pc . OnTrack ( func ( track * webrtc . TrackRemote , receiver * webrtc . RTPReceiver ) {
// First PLI is sent automatically by interval interceptor
log . Printf ( "New viewer joined, keyframe will be requested" )
})
Handling Video Corruption
import (
" github.com/pion/interceptor/pkg/intervalpli "
" github.com/pion/interceptor/pkg/stats "
)
// Monitor for high packet loss, request keyframe if detected
statsFactory , _ := stats . NewInterceptor ()
pliInterceptor , _ := intervalpli . NewGeneratorInterceptor (
intervalpli . GeneratorInterval ( 0 ), // Manual control
)
statsFactory . OnNewPeerConnection ( func ( id string , getter stats . Getter ) {
go func () {
ticker := time . NewTicker ( 1 * time . Second )
defer ticker . Stop ()
for range ticker . C {
stats := getter . Get ( ssrc )
if stats != nil && stats . InboundRTPStreamStats . FractionLost > 0.10 {
// High packet loss, request keyframe
log . Println ( "High loss detected, requesting keyframe" )
pliInterceptor . ForcePLI ( ssrc )
}
}
}()
})
Stream Recovery
// Request keyframe after network interruption
func handleNetworkReconnection ( pli * intervalpli . GeneratorInterceptor , ssrcs [] uint32 ) {
log . Println ( "Network reconnected, requesting keyframes" )
pli . ForcePLI ( ssrcs ... )
}
Interval Recommendations
By Use Case
// Live streaming (balance quality and bandwidth)
pliFactory , _ := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 3 * time . Second ),
)
// Video conferencing (frequent participants join/leave)
pliFactory , _ := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 2 * time . Second ),
)
// Broadcasting (viewers join frequently)
pliFactory , _ := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 1 * time . Second ),
)
// Low bandwidth (minimize overhead)
pliFactory , _ := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 10 * time . Second ),
)
// On-demand only (manual control)
pliFactory , _ := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 0 ), // Disable periodic
)
Bandwidth
// PLI packet size
pliSize := 12 // bytes (RTCP header + PLI)
// Overhead at different intervals
overhead1s := pliSize * 8 / 1.0 // 96 bps
overhead3s := pliSize * 8 / 3.0 // 32 bps
overhead10s := pliSize * 8 / 10.0 // 9.6 bps
PLI packets are tiny and have negligible bandwidth impact, but keyframes are much larger than delta frames.
Keyframe Overhead
Keyframes are typically 5-10x larger than delta frames:
// Typical sizes
deltaFrame := 10_000 // bytes
keyFrame := 50_000 // bytes (5x larger)
// More frequent PLI = more keyframes = higher bandwidth
Encoder Impact
Keyframe generation is CPU-intensive:
Delta frame : ~5ms encoding time
Keyframe : ~25ms encoding time (5x longer)
Best Practices
Balance Interval : Too frequent wastes bandwidth, too rare delays recovery
Network Conditions : Increase frequency on unreliable networks
Viewer Patterns : More frequent for scenarios with many new viewers
Combine with Monitoring : Use stats to trigger on-demand PLI
Codec Considerations : Some codecs handle keyframes better than others
3 seconds is a good default for most use cases, balancing recovery time with bandwidth efficiency.
Integration Patterns
With Packet Loss Detection
import (
" github.com/pion/interceptor/pkg/intervalpli "
" github.com/pion/interceptor/pkg/nack "
)
// Use NACK for normal losses, PLI for severe corruption
pliFactory , _ := intervalpli . NewReceiverInterceptor (
intervalpli . GeneratorInterval ( 5 * time . Second ),
)
i . Add ( pliFactory )
nackFactory , _ := nack . NewGeneratorInterceptor ()
i . Add ( nackFactory )
// NACK handles individual packet loss
// PLI handles decoder state corruption
With Simulcast
// Request keyframes on layer switches
func switchQualityLayer ( pli * intervalpli . GeneratorInterceptor , newLayerSSRC uint32 ) {
log . Printf ( "Switching to quality layer %d " , newLayerSSRC )
pli . ForcePLI ( newLayerSSRC )
}
Troubleshooting
Keyframes not being generated
Check:
PLI interceptor is registered
Interval is not set to 0
Video track supports PLI feedback
Encoder is receiving and processing PLI
RTCP is not blocked
If keyframes are too frequent:
Increase GeneratorInterval
Check for multiple PLI sources
Verify encoder isn’t generating unsolicited keyframes
Monitor bandwidth usage
For faster startup:
Call ForcePLI immediately on track start
Use shorter interval (1-2 seconds)
Configure encoder for faster keyframe generation
Alternative: FIR (Full Intra Request)
PLI is preferred over FIR in modern WebRTC:
// PLI (preferred)
// - Simpler protocol
// - Less overhead
// - Widely supported
// FIR (legacy)
// - More complex
// - Includes sequence number
// - Being phased out
NACK - Handle individual packet loss
Stats - Monitor loss to trigger PLI
Report - Track keyframe statistics