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 }