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