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  }