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