github.com/pion/webrtc/v3@v3.2.24/examples/play-from-disk-renegotiation/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  // play-from-disk-renegotiation demonstrates Pion WebRTC's renegotiation abilities.
     8  package main
     9  
    10  import (
    11  	"encoding/json"
    12  	"fmt"
    13  	"math/rand"
    14  	"net/http"
    15  	"os"
    16  	"time"
    17  
    18  	"github.com/pion/randutil"
    19  	"github.com/pion/webrtc/v3"
    20  	"github.com/pion/webrtc/v3/pkg/media"
    21  	"github.com/pion/webrtc/v3/pkg/media/ivfreader"
    22  )
    23  
    24  var peerConnection *webrtc.PeerConnection //nolint
    25  
    26  // doSignaling exchanges all state of the local PeerConnection and is called
    27  // every time a video is added or removed
    28  func doSignaling(w http.ResponseWriter, r *http.Request) {
    29  	var offer webrtc.SessionDescription
    30  	if err := json.NewDecoder(r.Body).Decode(&offer); err != nil {
    31  		panic(err)
    32  	}
    33  
    34  	if err := peerConnection.SetRemoteDescription(offer); err != nil {
    35  		panic(err)
    36  	}
    37  
    38  	// Create channel that is blocked until ICE Gathering is complete
    39  	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
    40  
    41  	answer, err := peerConnection.CreateAnswer(nil)
    42  	if err != nil {
    43  		panic(err)
    44  	} else if err = peerConnection.SetLocalDescription(answer); err != nil {
    45  		panic(err)
    46  	}
    47  
    48  	// Block until ICE Gathering is complete, disabling trickle ICE
    49  	// we do this because we only can exchange one signaling message
    50  	// in a production application you should exchange ICE Candidates via OnICECandidate
    51  	<-gatherComplete
    52  
    53  	response, err := json.Marshal(*peerConnection.LocalDescription())
    54  	if err != nil {
    55  		panic(err)
    56  	}
    57  
    58  	w.Header().Set("Content-Type", "application/json")
    59  	if _, err := w.Write(response); err != nil {
    60  		panic(err)
    61  	}
    62  }
    63  
    64  // Add a single video track
    65  func createPeerConnection(w http.ResponseWriter, r *http.Request) {
    66  	if peerConnection.ConnectionState() != webrtc.PeerConnectionStateNew {
    67  		panic(fmt.Sprintf("createPeerConnection called in non-new state (%s)", peerConnection.ConnectionState()))
    68  	}
    69  
    70  	doSignaling(w, r)
    71  	fmt.Println("PeerConnection has been created")
    72  }
    73  
    74  // Add a single video track
    75  func addVideo(w http.ResponseWriter, r *http.Request) {
    76  	videoTrack, err := webrtc.NewTrackLocalStaticSample(
    77  		webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8},
    78  		fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
    79  		fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
    80  	)
    81  	if err != nil {
    82  		panic(err)
    83  	}
    84  	rtpSender, err := peerConnection.AddTrack(videoTrack)
    85  	if err != nil {
    86  		panic(err)
    87  	}
    88  
    89  	// Read incoming RTCP packets
    90  	// Before these packets are returned they are processed by interceptors. For things
    91  	// like NACK this needs to be called.
    92  	go func() {
    93  		rtcpBuf := make([]byte, 1500)
    94  		for {
    95  			if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
    96  				return
    97  			}
    98  		}
    99  	}()
   100  
   101  	go writeVideoToTrack(videoTrack)
   102  	doSignaling(w, r)
   103  	fmt.Println("Video track has been added")
   104  }
   105  
   106  // Remove a single sender
   107  func removeVideo(w http.ResponseWriter, r *http.Request) {
   108  	if senders := peerConnection.GetSenders(); len(senders) != 0 {
   109  		if err := peerConnection.RemoveTrack(senders[0]); err != nil {
   110  			panic(err)
   111  		}
   112  	}
   113  
   114  	doSignaling(w, r)
   115  	fmt.Println("Video track has been removed")
   116  }
   117  
   118  func main() {
   119  	rand.Seed(time.Now().UTC().UnixNano())
   120  
   121  	var err error
   122  	if peerConnection, err = webrtc.NewPeerConnection(webrtc.Configuration{}); err != nil {
   123  		panic(err)
   124  	}
   125  	defer func() {
   126  		if cErr := peerConnection.Close(); cErr != nil {
   127  			fmt.Printf("cannot close peerConnection: %v\n", cErr)
   128  		}
   129  	}()
   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  			fmt.Println("Peer Connection has gone to failed exiting")
   141  			os.Exit(0)
   142  		}
   143  	})
   144  
   145  	http.Handle("/", http.FileServer(http.Dir(".")))
   146  	http.HandleFunc("/createPeerConnection", createPeerConnection)
   147  	http.HandleFunc("/addVideo", addVideo)
   148  	http.HandleFunc("/removeVideo", removeVideo)
   149  
   150  	go func() {
   151  		fmt.Println("Open http://localhost:8080 to access this demo")
   152  		// nolint: gosec
   153  		panic(http.ListenAndServe(":8080", nil))
   154  	}()
   155  
   156  	// Block forever
   157  	select {}
   158  }
   159  
   160  // Read a video file from disk and write it to a webrtc.Track
   161  // When the video has been completely read this exits without error
   162  func writeVideoToTrack(t *webrtc.TrackLocalStaticSample) {
   163  	// Open a IVF file and start reading using our IVFReader
   164  	file, err := os.Open("output.ivf")
   165  	if err != nil {
   166  		panic(err)
   167  	}
   168  
   169  	ivf, header, err := ivfreader.NewWith(file)
   170  	if err != nil {
   171  		panic(err)
   172  	}
   173  
   174  	// 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.
   175  	// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
   176  	//
   177  	// It is important to use a time.Ticker instead of time.Sleep because
   178  	// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
   179  	// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
   180  	ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
   181  	for ; true; <-ticker.C {
   182  		frame, _, err := ivf.ParseNextFrame()
   183  		if err != nil {
   184  			fmt.Printf("Finish writing video track: %s ", err)
   185  			return
   186  		}
   187  
   188  		if err = t.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
   189  			fmt.Printf("Finish writing video track: %s ", err)
   190  			return
   191  		}
   192  	}
   193  }