github.com/pion/webrtc/v4@v4.0.1/examples/stats/main.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  // stats demonstrates how to use the webrtc-stats implementation provided by Pion WebRTC.
     8  package main
     9  
    10  import (
    11  	"bufio"
    12  	"encoding/base64"
    13  	"encoding/json"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"strings"
    19  	"sync/atomic"
    20  	"time"
    21  
    22  	"github.com/pion/interceptor"
    23  	"github.com/pion/interceptor/pkg/stats"
    24  	"github.com/pion/webrtc/v4"
    25  )
    26  
    27  // How ofter to print WebRTC stats
    28  const statsInterval = time.Second * 5
    29  
    30  // nolint:gocognit
    31  func main() {
    32  	// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
    33  
    34  	// Create a MediaEngine object to configure the supported codec
    35  	m := &webrtc.MediaEngine{}
    36  
    37  	if err := m.RegisterDefaultCodecs(); err != nil {
    38  		panic(err)
    39  	}
    40  
    41  	// Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
    42  	// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
    43  	// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
    44  	// for each PeerConnection.
    45  	i := &interceptor.Registry{}
    46  
    47  	statsInterceptorFactory, err := stats.NewInterceptor()
    48  	if err != nil {
    49  		panic(err)
    50  	}
    51  
    52  	var statsGetter stats.Getter
    53  	statsInterceptorFactory.OnNewPeerConnection(func(_ string, g stats.Getter) {
    54  		statsGetter = g
    55  	})
    56  	i.Add(statsInterceptorFactory)
    57  
    58  	// Use the default set of Interceptors
    59  	if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
    60  		panic(err)
    61  	}
    62  
    63  	// Create the API object with the MediaEngine
    64  	api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
    65  
    66  	// Prepare the configuration
    67  	config := webrtc.Configuration{
    68  		ICEServers: []webrtc.ICEServer{
    69  			{
    70  				URLs: []string{"stun:stun.l.google.com:19302"},
    71  			},
    72  		},
    73  	}
    74  
    75  	// Create a new RTCPeerConnection
    76  	peerConnection, err := api.NewPeerConnection(config)
    77  	if err != nil {
    78  		panic(err)
    79  	}
    80  
    81  	// Allow us to receive 1 audio track, and 1 video track
    82  	if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
    83  		panic(err)
    84  	} else if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil {
    85  		panic(err)
    86  	}
    87  
    88  	// Set a handler for when a new remote track starts. We read the incoming packets, but then
    89  	// immediately discard them
    90  	peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive
    91  		fmt.Printf("New incoming track with codec: %s\n", track.Codec().MimeType)
    92  
    93  		go func() {
    94  			// Print the stats for this individual track
    95  			for {
    96  				stats := statsGetter.Get(uint32(track.SSRC()))
    97  
    98  				fmt.Printf("Stats for: %s\n", track.Codec().MimeType)
    99  				fmt.Println(stats.InboundRTPStreamStats)
   100  
   101  				time.Sleep(statsInterval)
   102  			}
   103  		}()
   104  
   105  		rtpBuff := make([]byte, 1500)
   106  		for {
   107  			_, _, readErr := track.Read(rtpBuff)
   108  			if readErr != nil {
   109  				panic(readErr)
   110  			}
   111  		}
   112  	})
   113  
   114  	var iceConnectionState atomic.Value
   115  	iceConnectionState.Store(webrtc.ICEConnectionStateNew)
   116  
   117  	// Set the handler for ICE connection state
   118  	// This will notify you when the peer has connected/disconnected
   119  	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
   120  		fmt.Printf("Connection State has changed %s \n", connectionState.String())
   121  		iceConnectionState.Store(connectionState)
   122  	})
   123  
   124  	// Wait for the offer to be pasted
   125  	offer := webrtc.SessionDescription{}
   126  	decode(readUntilNewline(), &offer)
   127  
   128  	// Set the remote SessionDescription
   129  	err = peerConnection.SetRemoteDescription(offer)
   130  	if err != nil {
   131  		panic(err)
   132  	}
   133  
   134  	// Create answer
   135  	answer, err := peerConnection.CreateAnswer(nil)
   136  	if err != nil {
   137  		panic(err)
   138  	}
   139  
   140  	// Create channel that is blocked until ICE Gathering is complete
   141  	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
   142  
   143  	// Sets the LocalDescription, and starts our UDP listeners
   144  	err = peerConnection.SetLocalDescription(answer)
   145  	if err != nil {
   146  		panic(err)
   147  	}
   148  
   149  	// Block until ICE Gathering is complete, disabling trickle ICE
   150  	// we do this because we only can exchange one signaling message
   151  	// in a production application you should exchange ICE Candidates via OnICECandidate
   152  	<-gatherComplete
   153  
   154  	// Output the answer in base64 so we can paste it in browser
   155  	fmt.Println(encode(peerConnection.LocalDescription()))
   156  
   157  	for {
   158  		time.Sleep(statsInterval)
   159  
   160  		// Stats are only printed after completed to make Copy/Pasting easier
   161  		if iceConnectionState.Load() == webrtc.ICEConnectionStateChecking {
   162  			continue
   163  		}
   164  
   165  		// Only print the remote IPs seen
   166  		for _, s := range peerConnection.GetStats() {
   167  			switch stat := s.(type) {
   168  			case webrtc.ICECandidateStats:
   169  				if stat.Type == webrtc.StatsTypeRemoteCandidate {
   170  					fmt.Printf("%s IP(%s) Port(%d)\n", stat.Type, stat.IP, stat.Port)
   171  				}
   172  			default:
   173  			}
   174  		}
   175  	}
   176  }
   177  
   178  // Read from stdin until we get a newline
   179  func readUntilNewline() (in string) {
   180  	var err error
   181  
   182  	r := bufio.NewReader(os.Stdin)
   183  	for {
   184  		in, err = r.ReadString('\n')
   185  		if err != nil && !errors.Is(err, io.EOF) {
   186  			panic(err)
   187  		}
   188  
   189  		if in = strings.TrimSpace(in); len(in) > 0 {
   190  			break
   191  		}
   192  	}
   193  
   194  	fmt.Println("")
   195  	return
   196  }
   197  
   198  // JSON encode + base64 a SessionDescription
   199  func encode(obj *webrtc.SessionDescription) string {
   200  	b, err := json.Marshal(obj)
   201  	if err != nil {
   202  		panic(err)
   203  	}
   204  
   205  	return base64.StdEncoding.EncodeToString(b)
   206  }
   207  
   208  // Decode a base64 and unmarshal JSON into a SessionDescription
   209  func decode(in string, obj *webrtc.SessionDescription) {
   210  	b, err := base64.StdEncoding.DecodeString(in)
   211  	if err != nil {
   212  		panic(err)
   213  	}
   214  
   215  	if err = json.Unmarshal(b, obj); err != nil {
   216  		panic(err)
   217  	}
   218  }