github.com/pion/webrtc/v4@v4.0.1/examples/simulcast/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 // simulcast demonstrates of how to handle incoming track with multiple simulcast rtp streams and show all them 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 "time" 20 21 "github.com/pion/rtcp" 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 // Prepare the configuration 30 config := webrtc.Configuration{ 31 ICEServers: []webrtc.ICEServer{ 32 { 33 URLs: []string{"stun:stun.l.google.com:19302"}, 34 }, 35 }, 36 } 37 38 // Create a new RTCPeerConnection 39 peerConnection, err := webrtc.NewPeerConnection(config) 40 if err != nil { 41 panic(err) 42 } 43 defer func() { 44 if cErr := peerConnection.Close(); cErr != nil { 45 fmt.Printf("cannot close peerConnection: %v\n", cErr) 46 } 47 }() 48 49 outputTracks := map[string]*webrtc.TrackLocalStaticRTP{} 50 51 // Create Track that we send video back to browser on 52 outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_q", "pion_q") 53 if err != nil { 54 panic(err) 55 } 56 outputTracks["q"] = outputTrack 57 58 outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_h", "pion_h") 59 if err != nil { 60 panic(err) 61 } 62 outputTracks["h"] = outputTrack 63 64 outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_f", "pion_f") 65 if err != nil { 66 panic(err) 67 } 68 outputTracks["f"] = outputTrack 69 70 if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly}); err != nil { 71 panic(err) 72 } 73 74 // Add this newly created track to the PeerConnection to send back video 75 if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["q"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil { 76 panic(err) 77 } 78 if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["h"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil { 79 panic(err) 80 } 81 if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["f"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil { 82 panic(err) 83 } 84 85 // Read incoming RTCP packets 86 // Before these packets are returned they are processed by interceptors. For things 87 // like NACK this needs to be called. 88 processRTCP := func(rtpSender *webrtc.RTPSender) { 89 rtcpBuf := make([]byte, 1500) 90 for { 91 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 92 return 93 } 94 } 95 } 96 for _, rtpSender := range peerConnection.GetSenders() { 97 go processRTCP(rtpSender) 98 } 99 100 // Wait for the offer to be pasted 101 offer := webrtc.SessionDescription{} 102 decode(readUntilNewline(), &offer) 103 104 if err = peerConnection.SetRemoteDescription(offer); err != nil { 105 panic(err) 106 } 107 108 // Set a handler for when a new remote track starts 109 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 110 fmt.Println("Track has started") 111 112 // Start reading from all the streams and sending them to the related output track 113 rid := track.RID() 114 if track.Kind() == webrtc.RTPCodecTypeVideo { 115 go func() { 116 ticker := time.NewTicker(3 * time.Second) 117 defer ticker.Stop() 118 for range ticker.C { 119 fmt.Printf("Sending pli for stream with rid: %q, ssrc: %d\n", track.RID(), track.SSRC()) 120 if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil { 121 fmt.Println(writeErr) 122 } 123 } 124 }() 125 } 126 for { 127 // Read RTP packets being sent to Pion 128 packet, _, readErr := track.ReadRTP() 129 if readErr != nil { 130 panic(readErr) 131 } 132 133 if writeErr := outputTracks[rid].WriteRTP(packet); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) { 134 panic(writeErr) 135 } 136 } 137 }) 138 139 // Set the handler for Peer connection state 140 // This will notify you when the peer has connected/disconnected 141 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 142 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 143 144 if s == webrtc.PeerConnectionStateFailed { 145 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 146 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 147 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 148 fmt.Println("Peer Connection has gone to failed exiting") 149 os.Exit(0) 150 } 151 152 if s == webrtc.PeerConnectionStateClosed { 153 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 154 fmt.Println("Peer Connection has gone to closed exiting") 155 os.Exit(0) 156 } 157 }) 158 159 // Create an answer 160 answer, err := peerConnection.CreateAnswer(nil) 161 if err != nil { 162 panic(err) 163 } 164 165 // Create channel that is blocked until ICE Gathering is complete 166 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 167 168 // Sets the LocalDescription, and starts our UDP listeners 169 err = peerConnection.SetLocalDescription(answer) 170 if err != nil { 171 panic(err) 172 } 173 174 // Block until ICE Gathering is complete, disabling trickle ICE 175 // we do this because we only can exchange one signaling message 176 // in a production application you should exchange ICE Candidates via OnICECandidate 177 <-gatherComplete 178 179 // Output the answer in base64 so we can paste it in browser 180 fmt.Println(encode(peerConnection.LocalDescription())) 181 182 // Block forever 183 select {} 184 } 185 186 // Read from stdin until we get a newline 187 func readUntilNewline() (in string) { 188 var err error 189 190 r := bufio.NewReader(os.Stdin) 191 for { 192 in, err = r.ReadString('\n') 193 if err != nil && !errors.Is(err, io.EOF) { 194 panic(err) 195 } 196 197 if in = strings.TrimSpace(in); len(in) > 0 { 198 break 199 } 200 } 201 202 fmt.Println("") 203 return 204 } 205 206 // JSON encode + base64 a SessionDescription 207 func encode(obj *webrtc.SessionDescription) string { 208 b, err := json.Marshal(obj) 209 if err != nil { 210 panic(err) 211 } 212 213 return base64.StdEncoding.EncodeToString(b) 214 } 215 216 // Decode a base64 and unmarshal JSON into a SessionDescription 217 func decode(in string, obj *webrtc.SessionDescription) { 218 b, err := base64.StdEncoding.DecodeString(in) 219 if err != nil { 220 panic(err) 221 } 222 223 if err = json.Unmarshal(b, obj); err != nil { 224 panic(err) 225 } 226 }