github.com/pion/webrtc/v4@v4.0.1/examples/broadcast/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  // broadcast demonstrates how to broadcast a video to many peers, while only requiring the broadcaster to upload once.
     8  package main
     9  
    10  import (
    11  	"encoding/base64"
    12  	"encoding/json"
    13  	"errors"
    14  	"flag"
    15  	"fmt"
    16  	"io"
    17  	"net/http"
    18  	"strconv"
    19  
    20  	"github.com/pion/interceptor"
    21  	"github.com/pion/interceptor/pkg/intervalpli"
    22  	"github.com/pion/webrtc/v4"
    23  )
    24  
    25  func main() { // nolint:gocognit
    26  	port := flag.Int("port", 8080, "http server port")
    27  	flag.Parse()
    28  
    29  	sdpChan := httpSDPServer(*port)
    30  
    31  	// Everything below is the Pion WebRTC API, thanks for using it ❤️.
    32  	offer := webrtc.SessionDescription{}
    33  	decode(<-sdpChan, &offer)
    34  	fmt.Println("")
    35  
    36  	peerConnectionConfig := webrtc.Configuration{
    37  		ICEServers: []webrtc.ICEServer{
    38  			{
    39  				URLs: []string{"stun:stun.l.google.com:19302"},
    40  			},
    41  		},
    42  	}
    43  
    44  	m := &webrtc.MediaEngine{}
    45  	if err := m.RegisterDefaultCodecs(); err != nil {
    46  		panic(err)
    47  	}
    48  
    49  	// Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
    50  	// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
    51  	// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
    52  	// for each PeerConnection.
    53  	i := &interceptor.Registry{}
    54  
    55  	// Use the default set of Interceptors
    56  	if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil {
    57  		panic(err)
    58  	}
    59  
    60  	// Register a intervalpli factory
    61  	// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
    62  	// This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates
    63  	// A real world application should process incoming RTCP packets from viewers and forward them to senders
    64  	intervalPliFactory, err := intervalpli.NewReceiverInterceptor()
    65  	if err != nil {
    66  		panic(err)
    67  	}
    68  	i.Add(intervalPliFactory)
    69  
    70  	// Create a new RTCPeerConnection
    71  	peerConnection, err := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)).NewPeerConnection(peerConnectionConfig)
    72  	if err != nil {
    73  		panic(err)
    74  	}
    75  	defer func() {
    76  		if cErr := peerConnection.Close(); cErr != nil {
    77  			fmt.Printf("cannot close peerConnection: %v\n", cErr)
    78  		}
    79  	}()
    80  
    81  	// Allow us to receive 1 video track
    82  	if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil {
    83  		panic(err)
    84  	}
    85  
    86  	localTrackChan := make(chan *webrtc.TrackLocalStaticRTP)
    87  	// Set a handler for when a new remote track starts, this just distributes all our packets
    88  	// to connected peers
    89  	peerConnection.OnTrack(func(remoteTrack *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive
    90  		// Create a local track, all our SFU clients will be fed via this track
    91  		localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(remoteTrack.Codec().RTPCodecCapability, "video", "pion")
    92  		if newTrackErr != nil {
    93  			panic(newTrackErr)
    94  		}
    95  		localTrackChan <- localTrack
    96  
    97  		rtpBuf := make([]byte, 1400)
    98  		for {
    99  			i, _, readErr := remoteTrack.Read(rtpBuf)
   100  			if readErr != nil {
   101  				panic(readErr)
   102  			}
   103  
   104  			// ErrClosedPipe means we don't have any subscribers, this is ok if no peers have connected yet
   105  			if _, err = localTrack.Write(rtpBuf[:i]); err != nil && !errors.Is(err, io.ErrClosedPipe) {
   106  				panic(err)
   107  			}
   108  		}
   109  	})
   110  
   111  	// Set the remote SessionDescription
   112  	err = peerConnection.SetRemoteDescription(offer)
   113  	if err != nil {
   114  		panic(err)
   115  	}
   116  
   117  	// Create answer
   118  	answer, err := peerConnection.CreateAnswer(nil)
   119  	if err != nil {
   120  		panic(err)
   121  	}
   122  
   123  	// Create channel that is blocked until ICE Gathering is complete
   124  	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
   125  
   126  	// Sets the LocalDescription, and starts our UDP listeners
   127  	err = peerConnection.SetLocalDescription(answer)
   128  	if err != nil {
   129  		panic(err)
   130  	}
   131  
   132  	// Block until ICE Gathering is complete, disabling trickle ICE
   133  	// we do this because we only can exchange one signaling message
   134  	// in a production application you should exchange ICE Candidates via OnICECandidate
   135  	<-gatherComplete
   136  
   137  	// Get the LocalDescription and take it to base64 so we can paste in browser
   138  	fmt.Println(encode(peerConnection.LocalDescription()))
   139  
   140  	localTrack := <-localTrackChan
   141  	for {
   142  		fmt.Println("")
   143  		fmt.Println("Curl an base64 SDP to start sendonly peer connection")
   144  
   145  		recvOnlyOffer := webrtc.SessionDescription{}
   146  		decode(<-sdpChan, &recvOnlyOffer)
   147  
   148  		// Create a new PeerConnection
   149  		peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfig)
   150  		if err != nil {
   151  			panic(err)
   152  		}
   153  
   154  		rtpSender, err := peerConnection.AddTrack(localTrack)
   155  		if err != nil {
   156  			panic(err)
   157  		}
   158  
   159  		// Read incoming RTCP packets
   160  		// Before these packets are returned they are processed by interceptors. For things
   161  		// like NACK this needs to be called.
   162  		go func() {
   163  			rtcpBuf := make([]byte, 1500)
   164  			for {
   165  				if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
   166  					return
   167  				}
   168  			}
   169  		}()
   170  
   171  		// Set the remote SessionDescription
   172  		err = peerConnection.SetRemoteDescription(recvOnlyOffer)
   173  		if err != nil {
   174  			panic(err)
   175  		}
   176  
   177  		// Create answer
   178  		answer, err := peerConnection.CreateAnswer(nil)
   179  		if err != nil {
   180  			panic(err)
   181  		}
   182  
   183  		// Create channel that is blocked until ICE Gathering is complete
   184  		gatherComplete = webrtc.GatheringCompletePromise(peerConnection)
   185  
   186  		// Sets the LocalDescription, and starts our UDP listeners
   187  		err = peerConnection.SetLocalDescription(answer)
   188  		if err != nil {
   189  			panic(err)
   190  		}
   191  
   192  		// Block until ICE Gathering is complete, disabling trickle ICE
   193  		// we do this because we only can exchange one signaling message
   194  		// in a production application you should exchange ICE Candidates via OnICECandidate
   195  		<-gatherComplete
   196  
   197  		// Get the LocalDescription and take it to base64 so we can paste in browser
   198  		fmt.Println(encode(peerConnection.LocalDescription()))
   199  	}
   200  }
   201  
   202  // JSON encode + base64 a SessionDescription
   203  func encode(obj *webrtc.SessionDescription) string {
   204  	b, err := json.Marshal(obj)
   205  	if err != nil {
   206  		panic(err)
   207  	}
   208  
   209  	return base64.StdEncoding.EncodeToString(b)
   210  }
   211  
   212  // Decode a base64 and unmarshal JSON into a SessionDescription
   213  func decode(in string, obj *webrtc.SessionDescription) {
   214  	b, err := base64.StdEncoding.DecodeString(in)
   215  	if err != nil {
   216  		panic(err)
   217  	}
   218  
   219  	if err = json.Unmarshal(b, obj); err != nil {
   220  		panic(err)
   221  	}
   222  }
   223  
   224  // httpSDPServer starts a HTTP Server that consumes SDPs
   225  func httpSDPServer(port int) chan string {
   226  	sdpChan := make(chan string)
   227  	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   228  		body, _ := io.ReadAll(r.Body)
   229  		fmt.Fprintf(w, "done") //nolint: errcheck
   230  		sdpChan <- string(body)
   231  	})
   232  
   233  	go func() {
   234  		// nolint: gosec
   235  		panic(http.ListenAndServe(":"+strconv.Itoa(port), nil))
   236  	}()
   237  
   238  	return sdpChan
   239  }