github.com/pion/webrtc/v4@v4.0.1/examples/insertable-streams/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  // insertable-streams demonstrates how to use insertable streams with Pion
     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/webrtc/v4"
    23  	"github.com/pion/webrtc/v4/pkg/media"
    24  	"github.com/pion/webrtc/v4/pkg/media/ivfreader"
    25  )
    26  
    27  const cipherKey = 0xAA
    28  
    29  // nolint:gocognit
    30  func main() {
    31  	peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
    32  		ICEServers: []webrtc.ICEServer{
    33  			{
    34  				URLs: []string{"stun:stun.l.google.com:19302"},
    35  			},
    36  		},
    37  	})
    38  	if err != nil {
    39  		panic(err)
    40  	}
    41  	defer func() {
    42  		if cErr := peerConnection.Close(); cErr != nil {
    43  			fmt.Printf("cannot close peerConnection: %v\n", cErr)
    44  		}
    45  	}()
    46  
    47  	// Create a video track
    48  	videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  	rtpSender, err := peerConnection.AddTrack(videoTrack)
    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  	iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background())
    70  	go func() {
    71  		// Open a IVF file and start reading using our IVFReader
    72  		file, ivfErr := os.Open("output.ivf")
    73  		if ivfErr != nil {
    74  			panic(ivfErr)
    75  		}
    76  
    77  		ivf, header, ivfErr := ivfreader.NewWith(file)
    78  		if ivfErr != nil {
    79  			panic(ivfErr)
    80  		}
    81  
    82  		// Wait for connection established
    83  		<-iceConnectedCtx.Done()
    84  
    85  		// Send our video file frame at a time. Pace our sending so we send it at the same speed it should be played back as.
    86  		// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
    87  		//
    88  		// It is important to use a time.Ticker instead of time.Sleep because
    89  		// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
    90  		// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
    91  		ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
    92  		defer ticker.Stop()
    93  		for range ticker.C {
    94  			frame, _, ivfErr := ivf.ParseNextFrame()
    95  			if errors.Is(ivfErr, io.EOF) {
    96  				fmt.Printf("All frames parsed and sent")
    97  				os.Exit(0)
    98  			}
    99  
   100  			if ivfErr != nil {
   101  				panic(ivfErr)
   102  			}
   103  
   104  			// Encrypt video using XOR Cipher
   105  			for i := range frame {
   106  				frame[i] ^= cipherKey
   107  			}
   108  
   109  			if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); ivfErr != nil {
   110  				panic(ivfErr)
   111  			}
   112  		}
   113  	}()
   114  
   115  	// Set the handler for ICE connection state
   116  	// This will notify you when the peer has connected/disconnected
   117  	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
   118  		fmt.Printf("Connection State has changed %s \n", connectionState.String())
   119  		if connectionState == webrtc.ICEConnectionStateConnected {
   120  			iceConnectedCtxCancel()
   121  		}
   122  	})
   123  
   124  	// Set the handler for Peer connection state
   125  	// This will notify you when the peer has connected/disconnected
   126  	peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
   127  		fmt.Printf("Peer Connection State has changed: %s\n", s.String())
   128  
   129  		if s == webrtc.PeerConnectionStateFailed {
   130  			// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
   131  			// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
   132  			// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
   133  			fmt.Println("Peer Connection has gone to failed exiting")
   134  			os.Exit(0)
   135  		}
   136  
   137  		if s == webrtc.PeerConnectionStateClosed {
   138  			// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
   139  			fmt.Println("Peer Connection has gone to closed exiting")
   140  			os.Exit(0)
   141  		}
   142  	})
   143  
   144  	// Wait for the offer to be pasted
   145  	offer := webrtc.SessionDescription{}
   146  	decode(readUntilNewline(), &offer)
   147  
   148  	// Set the remote SessionDescription
   149  	if err = peerConnection.SetRemoteDescription(offer); err != nil {
   150  		panic(err)
   151  	}
   152  
   153  	// Create answer
   154  	answer, err := peerConnection.CreateAnswer(nil)
   155  	if err != nil {
   156  		panic(err)
   157  	}
   158  
   159  	// Create channel that is blocked until ICE Gathering is complete
   160  	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
   161  
   162  	// Sets the LocalDescription, and starts our UDP listeners
   163  	if err = peerConnection.SetLocalDescription(answer); err != nil {
   164  		panic(err)
   165  	}
   166  
   167  	// Block until ICE Gathering is complete, disabling trickle ICE
   168  	// we do this because we only can exchange one signaling message
   169  	// in a production application you should exchange ICE Candidates via OnICECandidate
   170  	<-gatherComplete
   171  
   172  	// Output the answer in base64 so we can paste it in browser
   173  	fmt.Println(encode(peerConnection.LocalDescription()))
   174  
   175  	// Block forever
   176  	select {}
   177  }
   178  
   179  // Read from stdin until we get a newline
   180  func readUntilNewline() (in string) {
   181  	var err error
   182  
   183  	r := bufio.NewReader(os.Stdin)
   184  	for {
   185  		in, err = r.ReadString('\n')
   186  		if err != nil && !errors.Is(err, io.EOF) {
   187  			panic(err)
   188  		}
   189  
   190  		if in = strings.TrimSpace(in); len(in) > 0 {
   191  			break
   192  		}
   193  	}
   194  
   195  	fmt.Println("")
   196  	return
   197  }
   198  
   199  // JSON encode + base64 a SessionDescription
   200  func encode(obj *webrtc.SessionDescription) string {
   201  	b, err := json.Marshal(obj)
   202  	if err != nil {
   203  		panic(err)
   204  	}
   205  
   206  	return base64.StdEncoding.EncodeToString(b)
   207  }
   208  
   209  // Decode a base64 and unmarshal JSON into a SessionDescription
   210  func decode(in string, obj *webrtc.SessionDescription) {
   211  	b, err := base64.StdEncoding.DecodeString(in)
   212  	if err != nil {
   213  		panic(err)
   214  	}
   215  
   216  	if err = json.Unmarshal(b, obj); err != nil {
   217  		panic(err)
   218  	}
   219  }