main.go 8.0 KB

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