|
|
|
@ -1,8 +1,18 @@
@@ -1,8 +1,18 @@
|
|
|
|
|
/* |
|
|
|
|
Viewer counting doesn't just count the number of websocket clients that are currently connected, |
|
|
|
|
because people may be watching the stream outside of the web browser via any HLS video client. |
|
|
|
|
Instead we keep track of requests and consider each unique IP as a "viewer". |
|
|
|
|
As a signal, however, we do use the websocket disconnect from a client as a signal that a viewer |
|
|
|
|
dropped and we call ViewerDisconnected(). |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
package main |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"encoding/json" |
|
|
|
|
"fmt" |
|
|
|
|
"io/ioutil" |
|
|
|
|
"log" |
|
|
|
|
"math" |
|
|
|
|
"os" |
|
|
|
|
"time" |
|
|
|
@ -10,40 +20,53 @@ import (
@@ -10,40 +20,53 @@ import (
|
|
|
|
|
|
|
|
|
|
type Stats struct { |
|
|
|
|
streamConnected bool `json:"-"` |
|
|
|
|
ViewerCount int `json:"viewerCount"` |
|
|
|
|
SessionMaxViewerCount int `json:"sessionMaxViewerCount"` |
|
|
|
|
OverallMaxViewerCount int `json:"overallMaxViewerCount"` |
|
|
|
|
LastDisconnectTime time.Time `json:"lastDisconnectTime"` |
|
|
|
|
|
|
|
|
|
clients map[string]time.Time |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) Setup() { |
|
|
|
|
ticker := time.NewTicker(2 * time.Minute) |
|
|
|
|
quit := make(chan struct{}) |
|
|
|
|
s.clients = make(map[string]time.Time) |
|
|
|
|
|
|
|
|
|
statsSaveTimer := time.NewTicker(2 * time.Minute) |
|
|
|
|
go func() { |
|
|
|
|
for { |
|
|
|
|
select { |
|
|
|
|
case <-ticker.C: |
|
|
|
|
case <-statsSaveTimer.C: |
|
|
|
|
s.save() |
|
|
|
|
case <-quit: |
|
|
|
|
ticker.Stop() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
staleViewerPurgeTimer := time.NewTicker(5 * time.Second) |
|
|
|
|
go func() { |
|
|
|
|
for { |
|
|
|
|
select { |
|
|
|
|
case <-staleViewerPurgeTimer.C: |
|
|
|
|
s.purgeStaleViewers() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) IsStreamConnected() bool { |
|
|
|
|
return s.streamConnected |
|
|
|
|
func (s *Stats) purgeStaleViewers() { |
|
|
|
|
for clientID, lastConnectedtime := range s.clients { |
|
|
|
|
timeSinceLastActive := time.Since(lastConnectedtime).Minutes() |
|
|
|
|
if timeSinceLastActive > 2 { |
|
|
|
|
s.ViewerDisconnected(clientID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) SetViewerCount(count int) { |
|
|
|
|
s.ViewerCount = count |
|
|
|
|
s.SessionMaxViewerCount = int(math.Max(float64(s.ViewerCount), float64(s.SessionMaxViewerCount))) |
|
|
|
|
s.OverallMaxViewerCount = int(math.Max(float64(s.SessionMaxViewerCount), float64(s.OverallMaxViewerCount))) |
|
|
|
|
func (s *Stats) IsStreamConnected() bool { |
|
|
|
|
return s.streamConnected |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) GetViewerCount() int { |
|
|
|
|
return s.ViewerCount |
|
|
|
|
return len(s.clients) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) GetSessionMaxViewerCount() int { |
|
|
|
@ -54,10 +77,20 @@ func (s *Stats) GetOverallMaxViewerCount() int {
@@ -54,10 +77,20 @@ func (s *Stats) GetOverallMaxViewerCount() int {
|
|
|
|
|
return s.OverallMaxViewerCount |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) ViewerConnected() { |
|
|
|
|
func (s *Stats) SetClientActive(clientID string) { |
|
|
|
|
fmt.Println("Marking client active:", clientID) |
|
|
|
|
|
|
|
|
|
s.clients[clientID] = time.Now() |
|
|
|
|
s.SessionMaxViewerCount = int(math.Max(float64(s.GetViewerCount()), float64(s.SessionMaxViewerCount))) |
|
|
|
|
s.OverallMaxViewerCount = int(math.Max(float64(s.SessionMaxViewerCount), float64(s.OverallMaxViewerCount))) |
|
|
|
|
|
|
|
|
|
fmt.Println("Now", s.GetViewerCount(), "clients connected.") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) ViewerDisconnected() { |
|
|
|
|
func (s *Stats) ViewerDisconnected(clientID string) { |
|
|
|
|
log.Println("Removed client", clientID) |
|
|
|
|
|
|
|
|
|
delete(s.clients, clientID) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (s *Stats) StreamConnected() { |
|
|
|
|