Skip to content

Commit 5c3d582

Browse files
kmansoftJoeTurki
authored andcommitted
WHIP-WHEP example improvements
- Add TWCC extension, and generator. - Handle CORS preflight requests. - Add audio track. - Handle EOF without panicking.
1 parent f06b6bc commit 5c3d582

File tree

2 files changed

+140
-36
lines changed

2 files changed

+140
-36
lines changed

examples/whip-whep/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ <h3> ICE Connection States </h3>
3131

3232
window.doWHEP = () => {
3333
peerConnection.addTransceiver('video', { direction: 'recvonly' })
34+
peerConnection.addTransceiver('audio', { direction: 'recvonly' })
3435

3536
peerConnection.ontrack = function (event) {
3637
document.getElementById('videoPlayer').srcObject = event.streams[0]
@@ -57,7 +58,7 @@ <h3> ICE Connection States </h3>
5758
}
5859

5960
window.doWHIP = () => {
60-
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
61+
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
6162
.then(stream => {
6263
document.getElementById('videoPlayer').srcObject = stream
6364
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream))

examples/whip-whep/main.go

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,20 @@
99
package main
1010

1111
import (
12+
"errors"
1213
"fmt"
1314
"io"
1415
"net/http"
1516

1617
"github.com/pion/interceptor"
1718
"github.com/pion/interceptor/pkg/intervalpli"
18-
"github.com/pion/interceptor/pkg/packetdump"
19-
"github.com/pion/interceptor/pkg/report"
20-
"github.com/pion/rtp"
2119
"github.com/pion/webrtc/v4"
2220
)
2321

2422
// nolint: gochecknoglobals
2523
var (
2624
videoTrack *webrtc.TrackLocalStaticRTP
25+
audioTrack *webrtc.TrackLocalStaticRTP
2726

2827
peerConnectionConfiguration = webrtc.Configuration{
2928
ICEServers: []webrtc.ICEServer{
@@ -43,6 +42,11 @@ func main() {
4342
}, "video", "pion"); err != nil {
4443
panic(err)
4544
}
45+
if audioTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
46+
MimeType: webrtc.MimeTypeOpus,
47+
}, "audio", "pion"); err != nil {
48+
panic(err)
49+
}
4650

4751
http.Handle("/", http.FileServer(http.Dir(".")))
4852
http.HandleFunc("/whep", whepHandler)
@@ -53,6 +57,17 @@ func main() {
5357
}
5458

5559
func whipHandler(res http.ResponseWriter, req *http.Request) { // nolint: cyclop
60+
fmt.Printf("Request to %s, method = %s\n", req.URL, req.Method)
61+
62+
res.Header().Add("Access-Control-Allow-Origin", "*")
63+
res.Header().Add("Access-Control-Allow-Methods", "POST")
64+
res.Header().Add("Access-Control-Allow-Headers", "*")
65+
res.Header().Add("Access-Control-Allow-Headers", "Authorization")
66+
67+
if req.Method == http.MethodOptions {
68+
return
69+
}
70+
5671
// Read the offer from HTTP Request
5772
offer, err := io.ReadAll(req.Body)
5873
if err != nil {
@@ -62,8 +77,8 @@ func whipHandler(res http.ResponseWriter, req *http.Request) { // nolint: cyclop
6277
// Create a MediaEngine object to configure the supported codec
6378
mediaEngine := &webrtc.MediaEngine{}
6479

65-
// Setup the codecs you want to use.
66-
// We'll only use H264 but you can also define your own
80+
// Set up the codecs you want to use.
81+
// We'll only use H264 and Opus but you can also define your own
6782
if err = mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
6883
RTPCodecCapability: webrtc.RTPCodecCapability{
6984
MimeType: webrtc.MimeTypeH264, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
@@ -72,8 +87,16 @@ func whipHandler(res http.ResponseWriter, req *http.Request) { // nolint: cyclop
7287
}, webrtc.RTPCodecTypeVideo); err != nil {
7388
panic(err)
7489
}
90+
if err = mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
91+
RTPCodecCapability: webrtc.RTPCodecCapability{
92+
MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 2, SDPFmtpLine: "", RTCPFeedback: nil,
93+
},
94+
PayloadType: 97,
95+
}, webrtc.RTPCodecTypeAudio); err != nil {
96+
panic(err)
97+
}
7598

76-
// Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
99+
// Create an InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
77100
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
78101
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
79102
// for each PeerConnection.
@@ -103,62 +126,127 @@ func whipHandler(res http.ResponseWriter, req *http.Request) { // nolint: cyclop
103126
panic(err)
104127
}
105128

106-
// Allow us to receive 1 video trac
129+
// Allow us to receive 1 video track and 1 audio track
107130
if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil {
108131
panic(err)
109132
}
133+
if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
134+
panic(err)
135+
}
110136

111137
// Set a handler for when a new remote track starts, this handler saves buffers to disk as
112138
// an ivf file, since we could have multiple video tracks we provide a counter.
113139
// In your application this is where you would handle/process video
114140
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
115141
go func() {
116142
for {
117-
_, _, rtcpErr := receiver.ReadRTCP()
118-
if rtcpErr != nil {
119-
panic(rtcpErr)
143+
_, _, err := receiver.ReadRTCP()
144+
if err != nil {
145+
if errors.Is(err, io.EOF) {
146+
fmt.Printf("***** EOF reading RTCP from publish peer connection\n")
147+
148+
break
149+
} else {
150+
panic(err)
151+
}
120152
}
121153
}
122154
}()
123-
for {
124-
pkt, _, err := track.ReadRTP()
125-
if err != nil {
126-
panic(err)
127-
}
128-
if err = videoTrack.WriteRTP(pkt); err != nil {
129-
panic(err)
155+
go func() {
156+
for {
157+
pkt, _, err := track.ReadRTP()
158+
if err != nil {
159+
if errors.Is(err, io.EOF) {
160+
fmt.Printf("***** EOF reading RTP from publish peer connection\n")
161+
162+
break
163+
} else {
164+
panic(err)
165+
}
166+
}
167+
168+
// Strip any WHIP extensions before forwarding to WHEP
169+
pkt.Header.Extensions = nil
170+
pkt.Header.Extension = false
171+
172+
if track.Kind() == webrtc.RTPCodecTypeVideo {
173+
if err = videoTrack.WriteRTP(pkt); err != nil {
174+
panic(err)
175+
}
176+
} else if track.Kind() == webrtc.RTPCodecTypeAudio {
177+
if err = audioTrack.WriteRTP(pkt); err != nil {
178+
panic(err)
179+
}
180+
}
130181
}
131-
}
182+
}()
132183
})
133184
// Send answer via HTTP Response
134185
writeAnswer(res, peerConnection, offer, "/whip")
135186
}
136187

137-
func whepHandler(res http.ResponseWriter, req *http.Request) {
188+
func whepHandler(res http.ResponseWriter, req *http.Request) { //nolint:cyclop
189+
fmt.Printf("Request to %s, method = %s\n", req.URL, req.Method)
190+
191+
res.Header().Add("Access-Control-Allow-Origin", "*")
192+
res.Header().Add("Access-Control-Allow-Methods", "POST")
193+
res.Header().Add("Access-Control-Allow-Headers", "*")
194+
res.Header().Add("Access-Control-Allow-Headers", "Authorization")
195+
196+
if req.Method == http.MethodOptions {
197+
return
198+
}
199+
138200
// Read the offer from HTTP Request
139201
offer, err := io.ReadAll(req.Body)
140202
if err != nil {
141203
panic(err)
142204
}
143205

144-
interceptorRegistry := &interceptor.Registry{}
145-
packetDump, err := packetdump.NewSenderInterceptor(
146-
// filter out all RTP packets, only RTCP packets will be logged
147-
packetdump.RTPFilter(func(_ *rtp.Packet) bool {
148-
return false
149-
}),
150-
)
151-
if err != nil {
206+
// Create a MediaEngine object to configure the supported codec
207+
media := &webrtc.MediaEngine{}
208+
209+
// Set up the codecs you want to use.
210+
if err = media.RegisterCodec(webrtc.RTPCodecParameters{
211+
RTPCodecCapability: webrtc.RTPCodecCapability{
212+
MimeType: webrtc.MimeTypeH264,
213+
ClockRate: 90000,
214+
Channels: 0,
215+
SDPFmtpLine: "",
216+
RTCPFeedback: nil,
217+
},
218+
PayloadType: 96,
219+
}, webrtc.RTPCodecTypeVideo); err != nil {
152220
panic(err)
153221
}
154-
interceptorRegistry.Add(packetDump)
155-
senderInterceptor, err := report.NewSenderInterceptor()
156-
if err != nil {
222+
if err = media.RegisterCodec(webrtc.RTPCodecParameters{
223+
RTPCodecCapability: webrtc.RTPCodecCapability{
224+
MimeType: webrtc.MimeTypeOpus,
225+
ClockRate: 48000,
226+
Channels: 2,
227+
SDPFmtpLine: "",
228+
RTCPFeedback: nil,
229+
},
230+
PayloadType: 97,
231+
}, webrtc.RTPCodecTypeAudio); err != nil {
232+
panic(err)
233+
}
234+
235+
// Create an InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
236+
ir := &interceptor.Registry{}
237+
238+
// Use the default set of Interceptors
239+
if err = webrtc.RegisterDefaultInterceptors(media, ir); err != nil {
240+
panic(err)
241+
}
242+
243+
// We want TWCC in case the subscriber supports it
244+
if err = webrtc.ConfigureTWCCHeaderExtensionSender(media, ir); err != nil {
157245
panic(err)
158246
}
159-
interceptorRegistry.Add(senderInterceptor)
160247

161-
api := webrtc.NewAPI(webrtc.WithInterceptorRegistry(interceptorRegistry))
248+
// Create the API object with the MediaEngine
249+
api := webrtc.NewAPI(webrtc.WithMediaEngine(media), webrtc.WithInterceptorRegistry(ir))
162250

163251
// Create a new RTCPeerConnection
164252
peerConnection, err := api.NewPeerConnection(peerConnectionConfiguration)
@@ -167,18 +255,33 @@ func whepHandler(res http.ResponseWriter, req *http.Request) {
167255
}
168256

169257
// Add Video Track that is being written to from WHIP Session
170-
rtpSender, err := peerConnection.AddTrack(videoTrack)
258+
rtpSenderVideo, err := peerConnection.AddTrack(videoTrack)
259+
if err != nil {
260+
panic(err)
261+
}
262+
// Add Audio Track that is being written to from WHIP Session
263+
rtpSenderAudio, err := peerConnection.AddTrack(audioTrack)
171264
if err != nil {
172265
panic(err)
173266
}
174267

175-
// Read incoming RTCP packets
268+
// Read incoming RTCP packets for video
176269
// Before these packets are returned they are processed by interceptors. For things
177270
// like NACK this needs to be called.
178271
go func() {
179272
rtcpBuf := make([]byte, 1500)
180273
for {
181-
if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil {
274+
if _, _, rtcpErr := rtpSenderVideo.Read(rtcpBuf); rtcpErr != nil {
275+
return
276+
}
277+
}
278+
}()
279+
280+
// Read incoming RTCP packets for audio
281+
go func() {
282+
rtcpBuf := make([]byte, 1500)
283+
for {
284+
if _, _, rtcpErr := rtpSenderAudio.Read(rtcpBuf); rtcpErr != nil {
182285
return
183286
}
184287
}

0 commit comments

Comments
 (0)