owncast/stats.go

140 lines
3 KiB
Go

/*
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"
)
type Stats struct {
streamConnected bool `json:"-"`
SessionMaxViewerCount int `json:"sessionMaxViewerCount"`
OverallMaxViewerCount int `json:"overallMaxViewerCount"`
LastDisconnectTime time.Time `json:"lastDisconnectTime"`
clients map[string]time.Time
}
func (s *Stats) Setup() {
s.clients = make(map[string]time.Time)
statsSaveTimer := time.NewTicker(1 * time.Minute)
go func() {
for {
select {
case <-statsSaveTimer.C:
s.save()
}
}
}()
staleViewerPurgeTimer := time.NewTicker(3 * time.Second)
go func() {
for {
select {
case <-staleViewerPurgeTimer.C:
s.purgeStaleViewers()
}
}
}()
}
func (s *Stats) purgeStaleViewers() {
for clientID, lastConnectedtime := range s.clients {
timeSinceLastActive := time.Since(lastConnectedtime).Minutes()
if timeSinceLastActive > 2 {
s.ViewerDisconnected(clientID)
}
}
}
func (s *Stats) IsStreamConnected() bool {
return s.streamConnected
}
func (s *Stats) GetViewerCount() int {
return len(s.clients)
}
func (s *Stats) GetSessionMaxViewerCount() int {
return s.SessionMaxViewerCount
}
func (s *Stats) GetOverallMaxViewerCount() int {
return s.OverallMaxViewerCount
}
func (s *Stats) SetClientActive(clientID string) {
if _, ok := s.clients[clientID]; !ok {
fmt.Println("Marking client active:", clientID, s.GetViewerCount()+1, "clients connected.")
}
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)))
}
func (s *Stats) ViewerDisconnected(clientID string) {
log.Println("Removed client", clientID)
delete(s.clients, clientID)
}
func (s *Stats) StreamConnected() {
s.streamConnected = true
timeSinceDisconnect := time.Since(s.LastDisconnectTime).Minutes()
if timeSinceDisconnect > 15 {
s.SessionMaxViewerCount = 0
}
}
func (s *Stats) StreamDisconnected() {
s.streamConnected = false
s.LastDisconnectTime = time.Now()
}
func (s *Stats) save() {
jsonData, err := json.Marshal(&s)
verifyError(err)
f, err := os.Create("config/stats.json")
defer f.Close()
verifyError(err)
_, err = f.Write(jsonData)
verifyError(err)
}
func getSavedStats() *Stats {
filePath := "config/stats.json"
if !fileExists(filePath) {
return &Stats{}
}
jsonFile, err := ioutil.ReadFile(filePath)
var stats Stats
err = json.Unmarshal(jsonFile, &stats)
if err != nil {
panic(err)
}
return &stats
}