github.com/pion/webrtc/v3@v3.2.24/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 "errors" 12 "fmt" 13 "io" 14 "os" 15 "time" 16 17 "github.com/pion/interceptor" 18 "github.com/pion/rtcp" 19 "github.com/pion/webrtc/v3" 20 "github.com/pion/webrtc/v3/examples/internal/signal" 21 ) 22 23 // nolint:gocognit 24 func main() { 25 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 26 27 // Prepare the configuration 28 config := webrtc.Configuration{ 29 ICEServers: []webrtc.ICEServer{ 30 { 31 URLs: []string{"stun:stun.l.google.com:19302"}, 32 }, 33 }, 34 } 35 36 // Enable Extension Headers needed for Simulcast 37 m := &webrtc.MediaEngine{} 38 if err := m.RegisterDefaultCodecs(); err != nil { 39 panic(err) 40 } 41 for _, extension := range []string{ 42 "urn:ietf:params:rtp-hdrext:sdes:mid", 43 "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", 44 "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id", 45 } { 46 if err := m.RegisterHeaderExtension(webrtc.RTPHeaderExtensionCapability{URI: extension}, webrtc.RTPCodecTypeVideo); err != nil { 47 panic(err) 48 } 49 } 50 51 // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 52 // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 53 // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 54 // for each PeerConnection. 55 i := &interceptor.Registry{} 56 57 // Use the default set of Interceptors 58 if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil { 59 panic(err) 60 } 61 62 // Create a new RTCPeerConnection 63 peerConnection, err := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)).NewPeerConnection(config) 64 if err != nil { 65 panic(err) 66 } 67 defer func() { 68 if cErr := peerConnection.Close(); cErr != nil { 69 fmt.Printf("cannot close peerConnection: %v\n", cErr) 70 } 71 }() 72 73 outputTracks := map[string]*webrtc.TrackLocalStaticRTP{} 74 75 // Create Track that we send video back to browser on 76 outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_q", "pion_q") 77 if err != nil { 78 panic(err) 79 } 80 outputTracks["q"] = outputTrack 81 82 outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_h", "pion_h") 83 if err != nil { 84 panic(err) 85 } 86 outputTracks["h"] = outputTrack 87 88 outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_f", "pion_f") 89 if err != nil { 90 panic(err) 91 } 92 outputTracks["f"] = outputTrack 93 94 // Add this newly created track to the PeerConnection 95 if _, err = peerConnection.AddTrack(outputTracks["q"]); err != nil { 96 panic(err) 97 } 98 if _, err = peerConnection.AddTrack(outputTracks["h"]); err != nil { 99 panic(err) 100 } 101 if _, err = peerConnection.AddTrack(outputTracks["f"]); err != nil { 102 panic(err) 103 } 104 105 // Read incoming RTCP packets 106 // Before these packets are returned they are processed by interceptors. For things 107 // like NACK this needs to be called. 108 processRTCP := func(rtpSender *webrtc.RTPSender) { 109 rtcpBuf := make([]byte, 1500) 110 for { 111 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 112 return 113 } 114 } 115 } 116 for _, rtpSender := range peerConnection.GetSenders() { 117 go processRTCP(rtpSender) 118 } 119 120 // Wait for the offer to be pasted 121 offer := webrtc.SessionDescription{} 122 signal.Decode(signal.MustReadStdin(), &offer) 123 124 if err = peerConnection.SetRemoteDescription(offer); err != nil { 125 panic(err) 126 } 127 128 // Set a handler for when a new remote track starts 129 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { 130 fmt.Println("Track has started") 131 132 // Start reading from all the streams and sending them to the related output track 133 rid := track.RID() 134 go func() { 135 ticker := time.NewTicker(3 * time.Second) 136 for range ticker.C { 137 fmt.Printf("Sending pli for stream with rid: %q, ssrc: %d\n", track.RID(), track.SSRC()) 138 if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil { 139 fmt.Println(writeErr) 140 } 141 } 142 }() 143 for { 144 // Read RTP packets being sent to Pion 145 packet, _, readErr := track.ReadRTP() 146 if readErr != nil { 147 panic(readErr) 148 } 149 150 if writeErr := outputTracks[rid].WriteRTP(packet); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) { 151 panic(writeErr) 152 } 153 } 154 }) 155 156 // Set the handler for Peer connection state 157 // This will notify you when the peer has connected/disconnected 158 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 159 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 160 161 if s == webrtc.PeerConnectionStateFailed { 162 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 163 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 164 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 165 fmt.Println("Peer Connection has gone to failed exiting") 166 os.Exit(0) 167 } 168 }) 169 170 // Create an 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 // Output the answer in base64 so we can paste it in browser 191 fmt.Println(signal.Encode(*peerConnection.LocalDescription())) 192 193 // Block forever 194 select {} 195 }