github.com/pion/webrtc/v4@v4.0.1/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/v4" 20 "github.com/pion/webrtc/v4/pkg/media" 21 "github.com/pion/webrtc/v4/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 if s == webrtc.PeerConnectionStateClosed { 145 // PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify 146 fmt.Println("Peer Connection has gone to closed exiting") 147 os.Exit(0) 148 } 149 }) 150 151 http.Handle("/", http.FileServer(http.Dir("."))) 152 http.HandleFunc("/createPeerConnection", createPeerConnection) 153 http.HandleFunc("/addVideo", addVideo) 154 http.HandleFunc("/removeVideo", removeVideo) 155 156 go func() { 157 fmt.Println("Open http://localhost:8080 to access this demo") 158 // nolint: gosec 159 panic(http.ListenAndServe(":8080", nil)) 160 }() 161 162 // Block forever 163 select {} 164 } 165 166 // Read a video file from disk and write it to a webrtc.Track 167 // When the video has been completely read this exits without error 168 func writeVideoToTrack(t *webrtc.TrackLocalStaticSample) { 169 // Open a IVF file and start reading using our IVFReader 170 file, err := os.Open("output.ivf") 171 if err != nil { 172 panic(err) 173 } 174 175 ivf, header, err := ivfreader.NewWith(file) 176 if err != nil { 177 panic(err) 178 } 179 180 // 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. 181 // This isn't required since the video is timestamped, but we will such much higher loss if we send all at once. 182 // 183 // It is important to use a time.Ticker instead of time.Sleep because 184 // * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data 185 // * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343) 186 ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000)) 187 defer ticker.Stop() 188 for ; true; <-ticker.C { 189 frame, _, err := ivf.ParseNextFrame() 190 if err != nil { 191 fmt.Printf("Finish writing video track: %s ", err) 192 return 193 } 194 195 if err = t.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil { 196 fmt.Printf("Finish writing video track: %s ", err) 197 return 198 } 199 } 200 }