main.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. //go:build !js
  4. // +build !js
  5. // swap-tracks demonstrates how to swap multiple incoming tracks on a single outgoing track.
  6. package main
  7. import (
  8. "context"
  9. "errors"
  10. "fmt"
  11. "io"
  12. "time"
  13. "github.com/pion/rtcp"
  14. "github.com/pion/rtp"
  15. "github.com/pion/webrtc/v3"
  16. "github.com/pion/webrtc/v3/examples/internal/signal"
  17. )
  18. func main() { // nolint:gocognit
  19. // Everything below is the Pion WebRTC API! Thanks for using it ❤️.
  20. // Prepare the configuration
  21. config := webrtc.Configuration{
  22. ICEServers: []webrtc.ICEServer{
  23. {
  24. URLs: []string{"stun:stun.l.google.com:19302"},
  25. },
  26. },
  27. }
  28. // Create a new RTCPeerConnection
  29. peerConnection, err := webrtc.NewPeerConnection(config)
  30. if err != nil {
  31. panic(err)
  32. }
  33. defer func() {
  34. if cErr := peerConnection.Close(); cErr != nil {
  35. fmt.Printf("cannot close peerConnection: %v\n", cErr)
  36. }
  37. }()
  38. // Create Track that we send video back to browser on
  39. outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
  40. if err != nil {
  41. panic(err)
  42. }
  43. // Add this newly created track to the PeerConnection
  44. rtpSender, err := peerConnection.AddTrack(outputTrack)
  45. if err != nil {
  46. panic(err)
  47. }
  48. // Read incoming RTCP packets
  49. // Before these packets are returned they are processed by interceptors. For things
  50. // like NACK this needs to be called.
  51. go func() {
  52. rtcpBuf := make([]byte, 1500)
  53. for {
  54. if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
  55. return
  56. }
  57. }
  58. }()
  59. // Wait for the offer to be pasted
  60. offer := webrtc.SessionDescription{}
  61. signal.Decode(signal.MustReadStdin(), &offer)
  62. // Set the remote SessionDescription
  63. err = peerConnection.SetRemoteDescription(offer)
  64. if err != nil {
  65. panic(err)
  66. }
  67. // Which track is currently being handled
  68. currTrack := 0
  69. // The total number of tracks
  70. trackCount := 0
  71. // The channel of packets with a bit of buffer
  72. packets := make(chan *rtp.Packet, 60)
  73. // Set a handler for when a new remote track starts
  74. peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
  75. fmt.Printf("Track has started, of type %d: %s \n", track.PayloadType(), track.Codec().MimeType)
  76. trackNum := trackCount
  77. trackCount++
  78. // The last timestamp so that we can change the packet to only be the delta
  79. var lastTimestamp uint32
  80. // Whether this track is the one currently sending to the channel (on change
  81. // of this we send a PLI to have the entire picture updated)
  82. var isCurrTrack bool
  83. for {
  84. // Read RTP packets being sent to Pion
  85. rtp, _, readErr := track.ReadRTP()
  86. if readErr != nil {
  87. panic(readErr)
  88. }
  89. // Change the timestamp to only be the delta
  90. oldTimestamp := rtp.Timestamp
  91. if lastTimestamp == 0 {
  92. rtp.Timestamp = 0
  93. } else {
  94. rtp.Timestamp -= lastTimestamp
  95. }
  96. lastTimestamp = oldTimestamp
  97. // Check if this is the current track
  98. if currTrack == trackNum {
  99. // If just switched to this track, send PLI to get picture refresh
  100. if !isCurrTrack {
  101. isCurrTrack = true
  102. if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil {
  103. fmt.Println(writeErr)
  104. }
  105. }
  106. packets <- rtp
  107. } else {
  108. isCurrTrack = false
  109. }
  110. }
  111. })
  112. ctx, done := context.WithCancel(context.Background())
  113. // Set the handler for Peer connection state
  114. // This will notify you when the peer has connected/disconnected
  115. peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
  116. fmt.Printf("Peer Connection State has changed: %s\n", s.String())
  117. if s == webrtc.PeerConnectionStateFailed {
  118. // Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
  119. // Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
  120. // Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
  121. done()
  122. }
  123. })
  124. // Create an answer
  125. answer, err := peerConnection.CreateAnswer(nil)
  126. if err != nil {
  127. panic(err)
  128. }
  129. // Create channel that is blocked until ICE Gathering is complete
  130. gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
  131. // Sets the LocalDescription, and starts our UDP listeners
  132. err = peerConnection.SetLocalDescription(answer)
  133. if err != nil {
  134. panic(err)
  135. }
  136. // Block until ICE Gathering is complete, disabling trickle ICE
  137. // we do this because we only can exchange one signaling message
  138. // in a production application you should exchange ICE Candidates via OnICECandidate
  139. <-gatherComplete
  140. fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
  141. // Asynchronously take all packets in the channel and write them out to our
  142. // track
  143. go func() {
  144. var currTimestamp uint32
  145. for i := uint16(0); ; i++ {
  146. packet := <-packets
  147. // Timestamp on the packet is really a diff, so add it to current
  148. currTimestamp += packet.Timestamp
  149. packet.Timestamp = currTimestamp
  150. // Keep an increasing sequence number
  151. packet.SequenceNumber = i
  152. // Write out the packet, ignoring closed pipe if nobody is listening
  153. if err := outputTrack.WriteRTP(packet); err != nil {
  154. if errors.Is(err, io.ErrClosedPipe) {
  155. // The peerConnection has been closed.
  156. return
  157. }
  158. panic(err)
  159. }
  160. }
  161. }()
  162. // Wait for connection, then rotate the track every 5s
  163. fmt.Printf("Waiting for connection\n")
  164. for {
  165. select {
  166. case <-ctx.Done():
  167. return
  168. default:
  169. }
  170. // We haven't gotten any tracks yet
  171. if trackCount == 0 {
  172. continue
  173. }
  174. fmt.Printf("Waiting 5 seconds then changing...\n")
  175. time.Sleep(5 * time.Second)
  176. if currTrack == trackCount-1 {
  177. currTrack = 0
  178. } else {
  179. currTrack++
  180. }
  181. fmt.Printf("Switched to track #%v\n", currTrack+1)
  182. }
  183. }