github.com/pion/webrtc/v4@v4.0.1/examples/broadcast/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 // broadcast demonstrates how to broadcast a video to many peers, while only requiring the broadcaster to upload once. 8 package main 9 10 import ( 11 "encoding/base64" 12 "encoding/json" 13 "errors" 14 "flag" 15 "fmt" 16 "io" 17 "net/http" 18 "strconv" 19 20 "github.com/pion/interceptor" 21 "github.com/pion/interceptor/pkg/intervalpli" 22 "github.com/pion/webrtc/v4" 23 ) 24 25 func main() { // nolint:gocognit 26 port := flag.Int("port", 8080, "http server port") 27 flag.Parse() 28 29 sdpChan := httpSDPServer(*port) 30 31 // Everything below is the Pion WebRTC API, thanks for using it ❤️. 32 offer := webrtc.SessionDescription{} 33 decode(<-sdpChan, &offer) 34 fmt.Println("") 35 36 peerConnectionConfig := webrtc.Configuration{ 37 ICEServers: []webrtc.ICEServer{ 38 { 39 URLs: []string{"stun:stun.l.google.com:19302"}, 40 }, 41 }, 42 } 43 44 m := &webrtc.MediaEngine{} 45 if err := m.RegisterDefaultCodecs(); err != nil { 46 panic(err) 47 } 48 49 // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 50 // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 51 // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 52 // for each PeerConnection. 53 i := &interceptor.Registry{} 54 55 // Use the default set of Interceptors 56 if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil { 57 panic(err) 58 } 59 60 // Register a intervalpli factory 61 // This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender. 62 // This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates 63 // A real world application should process incoming RTCP packets from viewers and forward them to senders 64 intervalPliFactory, err := intervalpli.NewReceiverInterceptor() 65 if err != nil { 66 panic(err) 67 } 68 i.Add(intervalPliFactory) 69 70 // Create a new RTCPeerConnection 71 peerConnection, err := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)).NewPeerConnection(peerConnectionConfig) 72 if err != nil { 73 panic(err) 74 } 75 defer func() { 76 if cErr := peerConnection.Close(); cErr != nil { 77 fmt.Printf("cannot close peerConnection: %v\n", cErr) 78 } 79 }() 80 81 // Allow us to receive 1 video track 82 if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil { 83 panic(err) 84 } 85 86 localTrackChan := make(chan *webrtc.TrackLocalStaticRTP) 87 // Set a handler for when a new remote track starts, this just distributes all our packets 88 // to connected peers 89 peerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 90 // Create a local track, all our SFU clients will be fed via this track 91 localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "video", "pion") 92 if newTrackErr != nil { 93 panic(newTrackErr) 94 } 95 localTrackChan <- localTrack 96 97 rtpBuf := make([]byte, 1400) 98 for { 99 i, _, readErr := remoteTrack.Read(rtpBuf) 100 if readErr != nil { 101 panic(readErr) 102 } 103 104 // ErrClosedPipe means we don't have any subscribers, this is ok if no peers have connected yet 105 if _, err = localTrack.Write(rtpBuf[:i]); err != nil && !errors.Is(err, io.ErrClosedPipe) { 106 panic(err) 107 } 108 } 109 }) 110 111 // Set the remote SessionDescription 112 err = peerConnection.SetRemoteDescription(offer) 113 if err != nil { 114 panic(err) 115 } 116 117 // Create answer 118 answer, err := peerConnection.CreateAnswer(nil) 119 if err != nil { 120 panic(err) 121 } 122 123 // Create channel that is blocked until ICE Gathering is complete 124 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 125 126 // Sets the LocalDescription, and starts our UDP listeners 127 err = peerConnection.SetLocalDescription(answer) 128 if err != nil { 129 panic(err) 130 } 131 132 // Block until ICE Gathering is complete, disabling trickle ICE 133 // we do this because we only can exchange one signaling message 134 // in a production application you should exchange ICE Candidates via OnICECandidate 135 <-gatherComplete 136 137 // Get the LocalDescription and take it to base64 so we can paste in browser 138 fmt.Println(encode(peerConnection.LocalDescription())) 139 140 localTrack := <-localTrackChan 141 for { 142 fmt.Println("") 143 fmt.Println("Curl an base64 SDP to start sendonly peer connection") 144 145 recvOnlyOffer := webrtc.SessionDescription{} 146 decode(<-sdpChan, &recvOnlyOffer) 147 148 // Create a new PeerConnection 149 peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfig) 150 if err != nil { 151 panic(err) 152 } 153 154 rtpSender, err := peerConnection.AddTrack(localTrack) 155 if err != nil { 156 panic(err) 157 } 158 159 // Read incoming RTCP packets 160 // Before these packets are returned they are processed by interceptors. For things 161 // like NACK this needs to be called. 162 go func() { 163 rtcpBuf := make([]byte, 1500) 164 for { 165 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 166 return 167 } 168 } 169 }() 170 171 // Set the remote SessionDescription 172 err = peerConnection.SetRemoteDescription(recvOnlyOffer) 173 if err != nil { 174 panic(err) 175 } 176 177 // Create answer 178 answer, err := peerConnection.CreateAnswer(nil) 179 if err != nil { 180 panic(err) 181 } 182 183 // Create channel that is blocked until ICE Gathering is complete 184 gatherComplete = webrtc.GatheringCompletePromise(peerConnection) 185 186 // Sets the LocalDescription, and starts our UDP listeners 187 err = peerConnection.SetLocalDescription(answer) 188 if err != nil { 189 panic(err) 190 } 191 192 // Block until ICE Gathering is complete, disabling trickle ICE 193 // we do this because we only can exchange one signaling message 194 // in a production application you should exchange ICE Candidates via OnICECandidate 195 <-gatherComplete 196 197 // Get the LocalDescription and take it to base64 so we can paste in browser 198 fmt.Println(encode(peerConnection.LocalDescription())) 199 } 200 } 201 202 // JSON encode + base64 a SessionDescription 203 func encode(obj *webrtc.SessionDescription) string { 204 b, err := json.Marshal(obj) 205 if err != nil { 206 panic(err) 207 } 208 209 return base64.StdEncoding.EncodeToString(b) 210 } 211 212 // Decode a base64 and unmarshal JSON into a SessionDescription 213 func decode(in string, obj *webrtc.SessionDescription) { 214 b, err := base64.StdEncoding.DecodeString(in) 215 if err != nil { 216 panic(err) 217 } 218 219 if err = json.Unmarshal(b, obj); err != nil { 220 panic(err) 221 } 222 } 223 224 // httpSDPServer starts a HTTP Server that consumes SDPs 225 func httpSDPServer(port int) chan string { 226 sdpChan := make(chan string) 227 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 228 body, _ := io.ReadAll(r.Body) 229 fmt.Fprintf(w, "done") //nolint: errcheck 230 sdpChan <- string(body) 231 }) 232 233 go func() { 234 // nolint: gosec 235 panic(http.ListenAndServe(":"+strconv.Itoa(port), nil)) 236 }() 237 238 return sdpChan 239 }