main.go 5.8 KB

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