github.com/pion/webrtc/v3@v3.2.24/examples/play-from-disk/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 demonstrates how to send video and/or audio to your browser from files saved to disk.
     8  package main
     9  
    10  import (
    11  	"context"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"os"
    16  	"time"
    17  
    18  	"github.com/pion/webrtc/v3"
    19  	"github.com/pion/webrtc/v3/examples/internal/signal"
    20  	"github.com/pion/webrtc/v3/pkg/media"
    21  	"github.com/pion/webrtc/v3/pkg/media/ivfreader"
    22  	"github.com/pion/webrtc/v3/pkg/media/oggreader"
    23  )
    24  
    25  const (
    26  	audioFileName   = "output.ogg"
    27  	videoFileName   = "output.ivf"
    28  	oggPageDuration = time.Millisecond * 20
    29  )
    30  
    31  // nolint:gocognit
    32  func main() {
    33  	// Assert that we have an audio or video file
    34  	_, err := os.Stat(videoFileName)
    35  	haveVideoFile := !os.IsNotExist(err)
    36  
    37  	_, err = os.Stat(audioFileName)
    38  	haveAudioFile := !os.IsNotExist(err)
    39  
    40  	if !haveAudioFile && !haveVideoFile {
    41  		panic("Could not find `" + audioFileName + "` or `" + videoFileName + "`")
    42  	}
    43  
    44  	// Create a new RTCPeerConnection
    45  	peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
    46  		ICEServers: []webrtc.ICEServer{
    47  			{
    48  				URLs: []string{"stun:stun.l.google.com:19302"},
    49  			},
    50  		},
    51  	})
    52  	if err != nil {
    53  		panic(err)
    54  	}
    55  	defer func() {
    56  		if cErr := peerConnection.Close(); cErr != nil {
    57  			fmt.Printf("cannot close peerConnection: %v\n", cErr)
    58  		}
    59  	}()
    60  
    61  	iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background())
    62  
    63  	if haveVideoFile {
    64  		file, openErr := os.Open(videoFileName)
    65  		if openErr != nil {
    66  			panic(openErr)
    67  		}
    68  
    69  		_, header, openErr := ivfreader.NewWith(file)
    70  		if openErr != nil {
    71  			panic(openErr)
    72  		}
    73  
    74  		// Determine video codec
    75  		var trackCodec string
    76  		switch header.FourCC {
    77  		case "AV01":
    78  			trackCodec = webrtc.MimeTypeAV1
    79  		case "VP90":
    80  			trackCodec = webrtc.MimeTypeVP9
    81  		case "VP80":
    82  			trackCodec = webrtc.MimeTypeVP8
    83  		default:
    84  			panic(fmt.Sprintf("Unable to handle FourCC %s", header.FourCC))
    85  		}
    86  
    87  		// Create a video track
    88  		videoTrack, videoTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: trackCodec}, "video", "pion")
    89  		if videoTrackErr != nil {
    90  			panic(videoTrackErr)
    91  		}
    92  
    93  		rtpSender, videoTrackErr := peerConnection.AddTrack(videoTrack)
    94  		if videoTrackErr != nil {
    95  			panic(videoTrackErr)
    96  		}
    97  
    98  		// Read incoming RTCP packets
    99  		// Before these packets are returned they are processed by interceptors. For things
   100  		// like NACK this needs to be called.
   101  		go func() {
   102  			rtcpBuf := make([]byte, 1500)
   103  			for {
   104  				if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
   105  					return
   106  				}
   107  			}
   108  		}()
   109  
   110  		go func() {
   111  			// Open a IVF file and start reading using our IVFReader
   112  			file, ivfErr := os.Open(videoFileName)
   113  			if ivfErr != nil {
   114  				panic(ivfErr)
   115  			}
   116  
   117  			ivf, header, ivfErr := ivfreader.NewWith(file)
   118  			if ivfErr != nil {
   119  				panic(ivfErr)
   120  			}
   121  
   122  			// Wait for connection established
   123  			<-iceConnectedCtx.Done()
   124  
   125  			// 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.
   126  			// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
   127  			//
   128  			// It is important to use a time.Ticker instead of time.Sleep because
   129  			// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
   130  			// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
   131  			ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
   132  			for ; true; <-ticker.C {
   133  				frame, _, ivfErr := ivf.ParseNextFrame()
   134  				if errors.Is(ivfErr, io.EOF) {
   135  					fmt.Printf("All video frames parsed and sent")
   136  					os.Exit(0)
   137  				}
   138  
   139  				if ivfErr != nil {
   140  					panic(ivfErr)
   141  				}
   142  
   143  				if ivfErr = videoTrack.WriteSample(media.Sample{Data: frame, Duration: time.Second}); ivfErr != nil {
   144  					panic(ivfErr)
   145  				}
   146  			}
   147  		}()
   148  	}
   149  
   150  	if haveAudioFile {
   151  		// Create a audio track
   152  		audioTrack, audioTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion")
   153  		if audioTrackErr != nil {
   154  			panic(audioTrackErr)
   155  		}
   156  
   157  		rtpSender, audioTrackErr := peerConnection.AddTrack(audioTrack)
   158  		if audioTrackErr != nil {
   159  			panic(audioTrackErr)
   160  		}
   161  
   162  		// Read incoming RTCP packets
   163  		// Before these packets are returned they are processed by interceptors. For things
   164  		// like NACK this needs to be called.
   165  		go func() {
   166  			rtcpBuf := make([]byte, 1500)
   167  			for {
   168  				if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
   169  					return
   170  				}
   171  			}
   172  		}()
   173  
   174  		go func() {
   175  			// Open a OGG file and start reading using our OGGReader
   176  			file, oggErr := os.Open(audioFileName)
   177  			if oggErr != nil {
   178  				panic(oggErr)
   179  			}
   180  
   181  			// Open on oggfile in non-checksum mode.
   182  			ogg, _, oggErr := oggreader.NewWith(file)
   183  			if oggErr != nil {
   184  				panic(oggErr)
   185  			}
   186  
   187  			// Wait for connection established
   188  			<-iceConnectedCtx.Done()
   189  
   190  			// Keep track of last granule, the difference is the amount of samples in the buffer
   191  			var lastGranule uint64
   192  
   193  			// It is important to use a time.Ticker instead of time.Sleep because
   194  			// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
   195  			// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
   196  			ticker := time.NewTicker(oggPageDuration)
   197  			for ; true; <-ticker.C {
   198  				pageData, pageHeader, oggErr := ogg.ParseNextPage()
   199  				if errors.Is(oggErr, io.EOF) {
   200  					fmt.Printf("All audio pages parsed and sent")
   201  					os.Exit(0)
   202  				}
   203  
   204  				if oggErr != nil {
   205  					panic(oggErr)
   206  				}
   207  
   208  				// The amount of samples is the difference between the last and current timestamp
   209  				sampleCount := float64(pageHeader.GranulePosition - lastGranule)
   210  				lastGranule = pageHeader.GranulePosition
   211  				sampleDuration := time.Duration((sampleCount/48000)*1000) * time.Millisecond
   212  
   213  				if oggErr = audioTrack.WriteSample(media.Sample{Data: pageData, Duration: sampleDuration}); oggErr != nil {
   214  					panic(oggErr)
   215  				}
   216  			}
   217  		}()
   218  	}
   219  
   220  	// Set the handler for ICE connection state
   221  	// This will notify you when the peer has connected/disconnected
   222  	peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
   223  		fmt.Printf("Connection State has changed %s \n", connectionState.String())
   224  		if connectionState == webrtc.ICEConnectionStateConnected {
   225  			iceConnectedCtxCancel()
   226  		}
   227  	})
   228  
   229  	// Set the handler for Peer connection state
   230  	// This will notify you when the peer has connected/disconnected
   231  	peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
   232  		fmt.Printf("Peer Connection State has changed: %s\n", s.String())
   233  
   234  		if s == webrtc.PeerConnectionStateFailed {
   235  			// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
   236  			// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
   237  			// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
   238  			fmt.Println("Peer Connection has gone to failed exiting")
   239  			os.Exit(0)
   240  		}
   241  	})
   242  
   243  	// Wait for the offer to be pasted
   244  	offer := webrtc.SessionDescription{}
   245  	signal.Decode(signal.MustReadStdin(), &offer)
   246  
   247  	// Set the remote SessionDescription
   248  	if err = peerConnection.SetRemoteDescription(offer); err != nil {
   249  		panic(err)
   250  	}
   251  
   252  	// Create answer
   253  	answer, err := peerConnection.CreateAnswer(nil)
   254  	if err != nil {
   255  		panic(err)
   256  	}
   257  
   258  	// Create channel that is blocked until ICE Gathering is complete
   259  	gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
   260  
   261  	// Sets the LocalDescription, and starts our UDP listeners
   262  	if err = peerConnection.SetLocalDescription(answer); err != nil {
   263  		panic(err)
   264  	}
   265  
   266  	// Block until ICE Gathering is complete, disabling trickle ICE
   267  	// we do this because we only can exchange one signaling message
   268  	// in a production application you should exchange ICE Candidates via OnICECandidate
   269  	<-gatherComplete
   270  
   271  	// Output the answer in base64 so we can paste it in browser
   272  	fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
   273  
   274  	// Block forever
   275  	select {}
   276  }