99 "net"
1010 "net/http"
1111 "os"
12+ "strconv"
1213 "strings"
14+ "sync"
1315
1416 "github.com/livepeer/go-livepeer/clog"
1517
@@ -23,9 +25,14 @@ import (
2325type WHEPServer struct {
2426 mediaEngine * webrtc.MediaEngine
2527 settings func (* webrtc.API )
28+ maxSessions int // playback limit per stream (default 5)
29+
30+ // Everything below must be protected by the mutex `mu`
31+ mu sync.Mutex
32+ sessions map [string ]int // session count per stream
2633}
2734
28- func (s * WHEPServer ) CreateWHEP (ctx context.Context , w http.ResponseWriter , r * http.Request , mediaReader io.ReadCloser ) {
35+ func (s * WHEPServer ) CreateWHEP (ctx context.Context , w http.ResponseWriter , r * http.Request , mediaReader io.ReadCloser , streamName string ) {
2936 clog .Info (ctx , "creating whep" , "user-agent" , r .Header .Get ("User-Agent" ), "ip" , r .RemoteAddr )
3037
3138 // Must have Content-Type: application/sdp (the spec strongly recommends it)
@@ -46,18 +53,27 @@ func (s *WHEPServer) CreateWHEP(ctx context.Context, w http.ResponseWriter, r *h
4653 offer .Type = webrtc .SDPTypeOffer
4754 offer .SDP = string (offerBytes )
4855
56+ // add session to limiter. released whenever the peerconnection closes
57+ releaseSession , ok := s .addSession (streamName )
58+ if ! ok {
59+ http .Error (w , "Too many viewers for this stream" , http .StatusTooManyRequests )
60+ return
61+ }
62+
4963 api := webrtc .NewAPI (webrtc .WithMediaEngine (s .mediaEngine ), s .settings )
5064 peerConnection , err := api .NewPeerConnection (WebrtcConfig )
5165 if err != nil {
5266 clog .InfofErr (ctx , "Failed to create peerconnection" , err )
5367 http .Error (w , "Failed to create PeerConnection" , http .StatusInternalServerError )
54- peerConnection . Close ()
68+ releaseSession () // no peerconnection, so release immediately
5569 return
5670 }
71+
5772 peerConnection .OnICEConnectionStateChange (func (connectionState webrtc.ICEConnectionState ) {
5873 clog .Info (ctx , "whep ice connection state changed" , "state" , connectionState )
5974 if connectionState == webrtc .ICEConnectionStateFailed || connectionState == webrtc .ICEConnectionStateClosed {
6075 mediaReader .Close ()
76+ releaseSession ()
6177 }
6278 })
6379
@@ -81,6 +97,8 @@ func (s *WHEPServer) CreateWHEP(ctx context.Context, w http.ResponseWriter, r *h
8197 err := errors .New ("SDP did not expect any media" )
8298 clog .InfofErr (ctx , "SDP did not expect any media" , err )
8399 http .Error (w , err .Error (), http .StatusBadRequest )
100+ peerConnection .Close ()
101+ return
84102 }
85103
86104 mpegtsReader := mpegts.Reader {
@@ -192,6 +210,7 @@ func (s *WHEPServer) CreateWHEP(ctx context.Context, w http.ResponseWriter, r *h
192210 if err := t .Stop (); err != nil {
193211 clog .InfofErr (ctx , "error stopping transceiver kind=%s" , kind , err )
194212 http .Error (w , "Error stopping transceiver" , http .StatusBadRequest )
213+ peerConnection .Close ()
195214 return false
196215 }
197216 clog .Info (ctx , "Stopped transceiver" , "kind" , kind )
@@ -241,15 +260,16 @@ func (s *WHEPServer) CreateWHEP(ctx context.Context, w http.ResponseWriter, r *h
241260 err := mpegtsReader .Read ()
242261 if err != nil {
243262 clog .InfofErr (ctx , "error reading mpegts" , err )
244- if err == io .EOF {
245- peerConnection .Close ()
246- }
263+ peerConnection .Close ()
247264 return
248265 }
249266 }
250267 }()
251268}
252269
270+ const WhepUnlimitedPlaybacks = - 1
271+ const WhepDefaultPlaybacks = 5
272+
253273func NewWHEPServer () * WHEPServer {
254274 me := & webrtc.MediaEngine {}
255275 me .RegisterDefaultCodecs ()
@@ -272,10 +292,54 @@ func NewWHEPServer() *WHEPServer {
272292 iceFailedTimeout ,
273293 iceKeepAliveInterval ,
274294 )
295+ maxSessions := WhepDefaultPlaybacks
296+ sessions := os .Getenv ("LIVE_AI_WHEP_MAX_SESSIONS" )
297+ if "unlimited" == sessions {
298+ maxSessions = WhepUnlimitedPlaybacks
299+ } else if "" == sessions {
300+ // use default
301+ } else {
302+ limit , err := strconv .Atoi (sessions )
303+ if err != nil || limit <= 0 {
304+ log .Fatal ("Invalid max WHEP sessions" )
305+ }
306+ maxSessions = limit
307+ }
275308 return & WHEPServer {
276309 mediaEngine : me ,
277310 settings : webrtc .WithSettingEngine (se ),
311+ sessions : make (map [string ]int ),
312+ maxSessions : maxSessions ,
313+ }
314+ }
315+
316+ // increments the active session count if under limit. if limit exceeded,
317+ // returns ok=false, otherwise returns a release() func (idempotent)
318+ func (s * WHEPServer ) addSession (stream string ) (release func (), ok bool ) {
319+ if s .maxSessions == WhepUnlimitedPlaybacks {
320+ return func () {}, true
321+ }
322+ s .mu .Lock ()
323+ if s .sessions [stream ] >= s .maxSessions {
324+ s .mu .Unlock ()
325+ return nil , false
278326 }
327+ s .sessions [stream ]++
328+ s .mu .Unlock ()
329+
330+ var once sync.Once
331+ return func () {
332+ once .Do (func () {
333+ s .mu .Lock ()
334+ if s .sessions [stream ] > 0 {
335+ s .sessions [stream ]--
336+ if s .sessions [stream ] == 0 {
337+ delete (s .sessions , stream )
338+ }
339+ }
340+ s .mu .Unlock ()
341+ })
342+ }, true
279343}
280344
281345// Implements the TrackLocal interface in Pion
0 commit comments