github.com/pion/webrtc/v4@v4.0.1/examples/reflect/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 // reflect demonstrates how with one PeerConnection you can send video to Pion and have the packets sent back 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 20 "github.com/pion/interceptor" 21 "github.com/pion/interceptor/pkg/intervalpli" 22 "github.com/pion/webrtc/v4" 23 ) 24 25 // nolint:gocognit 26 func main() { 27 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 28 29 // Create a MediaEngine object to configure the supported codec 30 m := &webrtc.MediaEngine{} 31 32 // Setup the codecs you want to use. 33 // We'll use a VP8 and Opus but you can also define your own 34 if err := m.RegisterCodec(webrtc.RTPCodecParameters{ 35 RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, 36 PayloadType: 96, 37 }, webrtc.RTPCodecTypeVideo); 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 // Use the default set of Interceptors 48 if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil { 49 panic(err) 50 } 51 52 // Register a intervalpli factory 53 // This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender. 54 // This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates 55 // A real world application should process incoming RTCP packets from viewers and forward them to senders 56 intervalPliFactory, err := intervalpli.NewReceiverInterceptor() 57 if err != nil { 58 panic(err) 59 } 60 i.Add(intervalPliFactory) 61 62 // Create the API object with the MediaEngine 63 api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)) 64 65 // Prepare the configuration 66 config := webrtc.Configuration{ 67 ICEServers: []webrtc.ICEServer{ 68 { 69 URLs: []string{"stun:stun.l.google.com:19302"}, 70 }, 71 }, 72 } 73 // Create a new RTCPeerConnection 74 peerConnection, err := api.NewPeerConnection(config) 75 if err != nil { 76 panic(err) 77 } 78 defer func() { 79 if cErr := peerConnection.Close(); cErr != nil { 80 fmt.Printf("cannot close peerConnection: %v\n", cErr) 81 } 82 }() 83 84 // Create Track that we send video back to browser on 85 outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion") 86 if err != nil { 87 panic(err) 88 } 89 90 // Add this newly created track to the PeerConnection 91 rtpSender, err := peerConnection.AddTrack(outputTrack) 92 if err != nil { 93 panic(err) 94 } 95 96 // Read incoming RTCP packets 97 // Before these packets are returned they are processed by interceptors. For things 98 // like NACK this needs to be called. 99 go func() { 100 rtcpBuf := make([]byte, 1500) 101 for { 102 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 103 return 104 } 105 } 106 }() 107 108 // Wait for the offer to be pasted 109 offer := webrtc.SessionDescription{} 110 decode(readUntilNewline(), &offer) 111 112 // Set the remote SessionDescription 113 err = peerConnection.SetRemoteDescription(offer) 114 if err != nil { 115 panic(err) 116 } 117 118 // Set a handler for when a new remote track starts, this handler copies inbound RTP packets, 119 // replaces the SSRC and sends them back 120 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 121 fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().MimeType) 122 for { 123 // Read RTP packets being sent to Pion 124 rtp, _, readErr := track.ReadRTP() 125 if readErr != nil { 126 panic(readErr) 127 } 128 129 if writeErr := outputTrack.WriteRTP(rtp); writeErr != nil { 130 panic(writeErr) 131 } 132 } 133 }) 134 135 // Set the handler for Peer connection state 136 // This will notify you when the peer has connected/disconnected 137 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 138 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 139 140 if s == webrtc.PeerConnectionStateFailed { 141 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 142 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 143 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 144 fmt.Println("Peer Connection has gone to failed exiting") 145 os.Exit(0) 146 } 147 148 if s == webrtc.PeerConnectionStateClosed { 149 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 150 fmt.Println("Peer Connection has gone to closed exiting") 151 os.Exit(0) 152 } 153 }) 154 155 // Create an answer 156 answer, err := peerConnection.CreateAnswer(nil) 157 if err != nil { 158 panic(err) 159 } 160 161 // Create channel that is blocked until ICE Gathering is complete 162 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 163 164 // Sets the LocalDescription, and starts our UDP listeners 165 if err = peerConnection.SetLocalDescription(answer); err != nil { 166 panic(err) 167 } 168 169 // Block until ICE Gathering is complete, disabling trickle ICE 170 // we do this because we only can exchange one signaling message 171 // in a production application you should exchange ICE Candidates via OnICECandidate 172 <-gatherComplete 173 174 // Output the answer in base64 so we can paste it in browser 175 fmt.Println(encode(peerConnection.LocalDescription())) 176 177 // Block forever 178 select {} 179 } 180 181 // Read from stdin until we get a newline 182 func readUntilNewline() (in string) { 183 var err error 184 185 r := bufio.NewReader(os.Stdin) 186 for { 187 in, err = r.ReadString('\n') 188 if err != nil && !errors.Is(err, io.EOF) { 189 panic(err) 190 } 191 192 if in = strings.TrimSpace(in); len(in) > 0 { 193 break 194 } 195 } 196 197 fmt.Println("") 198 return 199 } 200 201 // JSON encode + base64 a SessionDescription 202 func encode(obj *webrtc.SessionDescription) string { 203 b, err := json.Marshal(obj) 204 if err != nil { 205 panic(err) 206 } 207 208 return base64.StdEncoding.EncodeToString(b) 209 } 210 211 // Decode a base64 and unmarshal JSON into a SessionDescription 212 func decode(in string, obj *webrtc.SessionDescription) { 213 b, err := base64.StdEncoding.DecodeString(in) 214 if err != nil { 215 panic(err) 216 } 217 218 if err = json.Unmarshal(b, obj); err != nil { 219 panic(err) 220 } 221 }