github.com/pion/webrtc/v4@v4.0.1/examples/whip-whep/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 // whip-whep demonstrates how to use the WHIP/WHEP specifications to exchange SPD descriptions and stream media to a WebRTC client in the browser or OBS 8 package main 9 10 import ( 11 "fmt" 12 "io" 13 "net/http" 14 15 "github.com/pion/interceptor" 16 "github.com/pion/interceptor/pkg/intervalpli" 17 "github.com/pion/webrtc/v4" 18 ) 19 20 // nolint: gochecknoglobals 21 var ( 22 videoTrack *webrtc.TrackLocalStaticRTP 23 24 peerConnectionConfiguration = webrtc.Configuration{ 25 ICEServers: []webrtc.ICEServer{ 26 { 27 URLs: []string{"stun:stun.l.google.com:19302"}, 28 }, 29 }, 30 } 31 ) 32 33 // nolint:gocognit 34 func main() { 35 // Everything below is the Pion WebRTC API! Thanks for using it ❤️. 36 var err error 37 if videoTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, "video", "pion"); err != nil { 38 panic(err) 39 } 40 41 http.Handle("/", http.FileServer(http.Dir("."))) 42 http.HandleFunc("/whep", whepHandler) 43 http.HandleFunc("/whip", whipHandler) 44 45 fmt.Println("Open http://localhost:8080 to access this demo") 46 panic(http.ListenAndServe(":8080", nil)) // nolint: gosec 47 } 48 49 func whipHandler(w http.ResponseWriter, r *http.Request) { 50 // Read the offer from HTTP Request 51 offer, err := io.ReadAll(r.Body) 52 if err != nil { 53 panic(err) 54 } 55 56 // Create a MediaEngine object to configure the supported codec 57 m := &webrtc.MediaEngine{} 58 59 // Setup the codecs you want to use. 60 // We'll only use H264 but you can also define your own 61 if err = m.RegisterCodec(webrtc.RTPCodecParameters{ 62 RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil}, 63 PayloadType: 96, 64 }, webrtc.RTPCodecTypeVideo); err != nil { 65 panic(err) 66 } 67 68 // Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline. 69 // This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection` 70 // this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry 71 // for each PeerConnection. 72 i := &interceptor.Registry{} 73 74 // Register a intervalpli factory 75 // This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender. 76 // This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates 77 // A real world application should process incoming RTCP packets from viewers and forward them to senders 78 intervalPliFactory, err := intervalpli.NewReceiverInterceptor() 79 if err != nil { 80 panic(err) 81 } 82 i.Add(intervalPliFactory) 83 84 // Use the default set of Interceptors 85 if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil { 86 panic(err) 87 } 88 89 // Create the API object with the MediaEngine 90 api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)) 91 92 // Prepare the configuration 93 94 // Create a new RTCPeerConnection 95 peerConnection, err := api.NewPeerConnection(peerConnectionConfiguration) 96 if err != nil { 97 panic(err) 98 } 99 100 // Allow us to receive 1 video trac 101 if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil { 102 panic(err) 103 } 104 105 // Set a handler for when a new remote track starts, this handler saves buffers to disk as 106 // an ivf file, since we could have multiple video tracks we provide a counter. 107 // In your application this is where you would handle/process video 108 peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive 109 for { 110 pkt, _, err := track.ReadRTP() 111 if err != nil { 112 panic(err) 113 } 114 115 if err = videoTrack.WriteRTP(pkt); err != nil { 116 panic(err) 117 } 118 } 119 }) 120 121 // Send answer via HTTP Response 122 writeAnswer(w, peerConnection, offer, "/whip") 123 } 124 125 func whepHandler(w http.ResponseWriter, r *http.Request) { 126 // Read the offer from HTTP Request 127 offer, err := io.ReadAll(r.Body) 128 if err != nil { 129 panic(err) 130 } 131 132 // Create a new RTCPeerConnection 133 peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfiguration) 134 if err != nil { 135 panic(err) 136 } 137 138 // Add Video Track that is being written to from WHIP Session 139 rtpSender, err := peerConnection.AddTrack(videoTrack) 140 if err != nil { 141 panic(err) 142 } 143 144 // Read incoming RTCP packets 145 // Before these packets are returned they are processed by interceptors. For things 146 // like NACK this needs to be called. 147 go func() { 148 rtcpBuf := make([]byte, 1500) 149 for { 150 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 151 return 152 } 153 } 154 }() 155 156 // Send answer via HTTP Response 157 writeAnswer(w, peerConnection, offer, "/whep") 158 } 159 160 func writeAnswer(w http.ResponseWriter, peerConnection *webrtc.PeerConnection, offer []byte, path string) { 161 // Set the handler for ICE connection state 162 // This will notify you when the peer has connected/disconnected 163 peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { 164 fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String()) 165 166 if connectionState == webrtc.ICEConnectionStateFailed { 167 _ = peerConnection.Close() 168 } 169 }) 170 171 if err := peerConnection.SetRemoteDescription(webrtc.SessionDescription{Type: webrtc.SDPTypeOffer, SDP: string(offer)}); err != nil { 172 panic(err) 173 } 174 175 // Create channel that is blocked until ICE Gathering is complete 176 gatherComplete := webrtc.GatheringCompletePromise(peerConnection) 177 178 // Create answer 179 answer, err := peerConnection.CreateAnswer(nil) 180 if err != nil { 181 panic(err) 182 } else if err = peerConnection.SetLocalDescription(answer); err != nil { 183 panic(err) 184 } 185 186 // Block until ICE Gathering is complete, disabling trickle ICE 187 // we do this because we only can exchange one signaling message 188 // in a production application you should exchange ICE Candidates via OnICECandidate 189 <-gatherComplete 190 191 // WHIP+WHEP expects a Location header and a HTTP Status Code of 201 192 w.Header().Add("Location", path) 193 w.WriteHeader(http.StatusCreated) 194 195 // Write Answer with Candidates as HTTP Response 196 fmt.Fprint(w, peerConnection.LocalDescription().SDP) //nolint: errcheck 197 }