github.com/pion/webrtc/v4@v4.0.1/examples/swap-tracks/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 // swap-tracks demonstrates how to swap multiple incoming tracks on a single outgoing track. 8 package main 9 10 import ( 11 "bufio" 12 "context" 13 "encoding/base64" 14 "encoding/json" 15 "errors" 16 "fmt" 17 "io" 18 "os" 19 "strings" 20 "time" 21 22 "github.com/pion/rtcp" 23 "github.com/pion/rtp" 24 "github.com/pion/webrtc/v4" 25 ) 26 27 func main() { // nolint:gocognit 28 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 29 30 // Prepare the configuration 31 config := webrtc.Configuration{ 32 ICEServers: []webrtc.ICEServer{ 33 { 34 URLs: []string{"stun:stun.l.google.com:19302"}, 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 // Create Track that we send video back to browser on 50 outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion") 51 if err != nil { 52 panic(err) 53 } 54 55 // Add this newly created track to the PeerConnection 56 rtpSender, err := peerConnection.AddTrack(outputTrack) 57 if err != nil { 58 panic(err) 59 } 60 61 // Read incoming RTCP packets 62 // Before these packets are returned they are processed by interceptors. For things 63 // like NACK this needs to be called. 64 go func() { 65 rtcpBuf := make([]byte, 1500) 66 for { 67 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 68 return 69 } 70 } 71 }() 72 73 // Wait for the offer to be pasted 74 offer := webrtc.SessionDescription{} 75 decode(readUntilNewline(), &offer) 76 77 // Set the remote SessionDescription 78 err = peerConnection.SetRemoteDescription(offer) 79 if err != nil { 80 panic(err) 81 } 82 83 // Which track is currently being handled 84 currTrack := 0 85 // The total number of tracks 86 trackCount := 0 87 // The channel of packets with a bit of buffer 88 packets := make(chan *rtp.Packet, 60) 89 90 // Set a handler for when a new remote track starts 91 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 92 fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().MimeType) 93 trackNum := trackCount 94 trackCount++ 95 // The last timestamp so that we can change the packet to only be the delta 96 var lastTimestamp uint32 97 98 // Whether this track is the one currently sending to the channel (on change 99 // of this we send a PLI to have the entire picture updated) 100 var isCurrTrack bool 101 for { 102 // Read RTP packets being sent to Pion 103 rtp, _, readErr := track.ReadRTP() 104 if readErr != nil { 105 panic(readErr) 106 } 107 108 // Change the timestamp to only be the delta 109 oldTimestamp := rtp.Timestamp 110 if lastTimestamp == 0 { 111 rtp.Timestamp = 0 112 } else { 113 rtp.Timestamp -= lastTimestamp 114 } 115 lastTimestamp = oldTimestamp 116 117 // Check if this is the current track 118 if currTrack == trackNum { 119 // If just switched to this track, send PLI to get picture refresh 120 if !isCurrTrack { 121 isCurrTrack = true 122 if track.Kind() == webrtc.RTPCodecTypeVideo { 123 if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil { 124 fmt.Println(writeErr) 125 } 126 } 127 } 128 packets <- rtp 129 } else { 130 isCurrTrack = false 131 } 132 } 133 }) 134 135 ctx, done := context.WithCancel(context.Background()) 136 137 // Set the handler for Peer connection state 138 // This will notify you when the peer has connected/disconnected 139 peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) { 140 fmt.Printf("Peer Connection State has changed: %s\n", s.String()) 141 142 if s == webrtc.PeerConnectionStateFailed { 143 // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart. 144 // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout. 145 // Note that the PeerConnection may come back from PeerConnectionStateDisconnected. 146 done() 147 } 148 149 if s == webrtc.PeerConnectionStateClosed { 150 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 151 done() 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 err = peerConnection.SetLocalDescription(answer) 166 if err != nil { 167 panic(err) 168 } 169 170 // Block until ICE Gathering is complete, disabling trickle ICE 171 // we do this because we only can exchange one signaling message 172 // in a production application you should exchange ICE Candidates via OnICECandidate 173 <-gatherComplete 174 175 fmt.Println(encode(peerConnection.LocalDescription())) 176 177 // Asynchronously take all packets in the channel and write them out to our 178 // track 179 go func() { 180 var currTimestamp uint32 181 for i := uint16(0); ; i++ { 182 packet := <-packets 183 // Timestamp on the packet is really a diff, so add it to current 184 currTimestamp += packet.Timestamp 185 packet.Timestamp = currTimestamp 186 // Keep an increasing sequence number 187 packet.SequenceNumber = i 188 // Write out the packet, ignoring closed pipe if nobody is listening 189 if err := outputTrack.WriteRTP(packet); err != nil { 190 if errors.Is(err, io.ErrClosedPipe) { 191 // The peerConnection has been closed. 192 return 193 } 194 195 panic(err) 196 } 197 } 198 }() 199 200 // Wait for connection, then rotate the track every 5s 201 fmt.Printf("Waiting for connection\n") 202 for { 203 select { 204 case <-ctx.Done(): 205 return 206 default: 207 } 208 209 // We haven't gotten any tracks yet 210 if trackCount == 0 { 211 continue 212 } 213 214 fmt.Printf("Waiting 5 seconds then changing...\n") 215 time.Sleep(5 * time.Second) 216 if currTrack == trackCount-1 { 217 currTrack = 0 218 } else { 219 currTrack++ 220 } 221 fmt.Printf("Switched to track #%v\n", currTrack+1) 222 } 223 } 224 225 // Read from stdin until we get a newline 226 func readUntilNewline() (in string) { 227 var err error 228 229 r := bufio.NewReader(os.Stdin) 230 for { 231 in, err = r.ReadString('\n') 232 if err != nil && !errors.Is(err, io.EOF) { 233 panic(err) 234 } 235 236 if in = strings.TrimSpace(in); len(in) > 0 { 237 break 238 } 239 } 240 241 fmt.Println("") 242 return 243 } 244 245 // JSON encode + base64 a SessionDescription 246 func encode(obj *webrtc.SessionDescription) string { 247 b, err := json.Marshal(obj) 248 if err != nil { 249 panic(err) 250 } 251 252 return base64.StdEncoding.EncodeToString(b) 253 } 254 255 // Decode a base64 and unmarshal JSON into a SessionDescription 256 func decode(in string, obj *webrtc.SessionDescription) { 257 b, err := base64.StdEncoding.DecodeString(in) 258 if err != nil { 259 panic(err) 260 } 261 262 if err = json.Unmarshal(b, obj); err != nil { 263 panic(err) 264 } 265 }