parent
92041c4c23
commit
98fce01b52
@ -4,7 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/owncast/owncast/controllers"
|
||||
"github.com/owncast/owncast/core"
|
||||
"github.com/owncast/owncast/core/user"
|
||||
"github.com/owncast/owncast/metrics"
|
||||
"github.com/owncast/owncast/models"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -17,3 +21,23 @@ func GetViewersOverTime(w http.ResponseWriter, r *http.Request) {
|
||||
log.Errorln(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveViewers returns currently connected clients.
|
||||
func GetActiveViewers(w http.ResponseWriter, r *http.Request) {
|
||||
c := core.GetActiveViewers()
|
||||
viewers := []models.Viewer{}
|
||||
for _, v := range c {
|
||||
viewers = append(viewers, *v)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(viewers); err != nil {
|
||||
controllers.InternalErrorHandler(w, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ExternalGetActiveViewers returns currently connected clients.
|
||||
func ExternalGetActiveViewers(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
|
||||
GetConnectedChatClients(w, r)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/owncast/owncast/config"
|
||||
"github.com/owncast/owncast/core"
|
||||
"github.com/owncast/owncast/core/data"
|
||||
"github.com/owncast/owncast/models"
|
||||
"github.com/owncast/owncast/router/middleware"
|
||||
"github.com/owncast/owncast/utils"
|
||||
)
|
||||
@ -42,8 +43,8 @@ func HandleHLSRequest(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/x-mpegURL")
|
||||
|
||||
// Use this as an opportunity to mark this viewer as active.
|
||||
id := utils.GenerateClientIDFromRequest(r)
|
||||
core.SetViewerIDActive(id)
|
||||
viewer := models.GenerateViewerFromRequest(r)
|
||||
core.SetViewerActive(&viewer)
|
||||
} else {
|
||||
cacheTime := utils.GetCacheDurationSecondsForPath(relativePath)
|
||||
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheTime))
|
||||
|
@ -4,11 +4,11 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncast/owncast/core"
|
||||
"github.com/owncast/owncast/utils"
|
||||
"github.com/owncast/owncast/models"
|
||||
)
|
||||
|
||||
// Ping is fired by a client to show they are still an active viewer.
|
||||
func Ping(w http.ResponseWriter, r *http.Request) {
|
||||
id := utils.GenerateClientIDFromRequest(r)
|
||||
core.SetViewerIDActive(id)
|
||||
viewer := models.GenerateViewerFromRequest(r)
|
||||
core.SetViewerActive(&viewer)
|
||||
}
|
||||
|
@ -8,12 +8,14 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/owncast/owncast/core/data"
|
||||
"github.com/owncast/owncast/geoip"
|
||||
"github.com/owncast/owncast/models"
|
||||
)
|
||||
|
||||
var (
|
||||
l = &sync.RWMutex{}
|
||||
_activeViewerPurgeTimeout = time.Second * 15
|
||||
_geoIPClient = geoip.NewClient()
|
||||
)
|
||||
|
||||
func setupStats() error {
|
||||
@ -63,30 +65,45 @@ func RemoveChatClient(clientID string) {
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// SetViewerIDActive sets a client as active and connected.
|
||||
func SetViewerIDActive(id string) {
|
||||
// SetViewerActive sets a client as active and connected.
|
||||
func SetViewerActive(viewer *models.Viewer) {
|
||||
// Don't update viewer counts if a live stream session is not active.
|
||||
if !_stats.StreamConnected {
|
||||
return
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
_stats.Viewers[id] = time.Now()
|
||||
// Asynchronously, optionally, fetch GeoIP data.
|
||||
go func(viewer *models.Viewer) {
|
||||
viewer.Geo = _geoIPClient.GetGeoFromIP(viewer.IPAddress)
|
||||
}(viewer)
|
||||
|
||||
// Don't update viewer counts if a live stream session is not active.
|
||||
if _stats.StreamConnected {
|
||||
_stats.SessionMaxViewerCount = int(math.Max(float64(len(_stats.Viewers)), float64(_stats.SessionMaxViewerCount)))
|
||||
_stats.OverallMaxViewerCount = int(math.Max(float64(_stats.SessionMaxViewerCount), float64(_stats.OverallMaxViewerCount)))
|
||||
if _, exists := _stats.Viewers[viewer.ClientID]; exists {
|
||||
_stats.Viewers[viewer.ClientID].LastSeen = time.Now()
|
||||
} else {
|
||||
_stats.Viewers[viewer.ClientID] = viewer
|
||||
}
|
||||
_stats.SessionMaxViewerCount = int(math.Max(float64(len(_stats.Viewers)), float64(_stats.SessionMaxViewerCount)))
|
||||
_stats.OverallMaxViewerCount = int(math.Max(float64(_stats.SessionMaxViewerCount), float64(_stats.OverallMaxViewerCount)))
|
||||
}
|
||||
|
||||
// GetActiveViewers will return the active viewers.
|
||||
func GetActiveViewers() map[string]*models.Viewer {
|
||||
return _stats.Viewers
|
||||
}
|
||||
|
||||
func pruneViewerCount() {
|
||||
viewers := make(map[string]time.Time)
|
||||
viewers := make(map[string]*models.Viewer)
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
for viewerID := range _stats.Viewers {
|
||||
viewerLastSeenTime := _stats.Viewers[viewerID]
|
||||
for viewerID, viewer := range _stats.Viewers {
|
||||
viewerLastSeenTime := _stats.Viewers[viewerID].LastSeen
|
||||
if time.Since(viewerLastSeenTime) < _activeViewerPurgeTimeout {
|
||||
viewers[viewerID] = viewerLastSeenTime
|
||||
viewers[viewerID] = viewer
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +129,7 @@ func getSavedStats() models.Stats {
|
||||
|
||||
result := models.Stats{
|
||||
ChatClients: make(map[string]models.Client),
|
||||
Viewers: make(map[string]time.Time),
|
||||
Viewers: make(map[string]*models.Viewer),
|
||||
SessionMaxViewerCount: data.GetPeakSessionViewerCount(),
|
||||
OverallMaxViewerCount: data.GetPeakOverallViewerCount(),
|
||||
LastDisconnectTime: savedLastDisconnectTime,
|
||||
|
@ -1,8 +1,6 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/owncast/owncast/utils"
|
||||
)
|
||||
|
||||
@ -12,8 +10,8 @@ type Stats struct {
|
||||
OverallMaxViewerCount int `json:"overallMaxViewerCount"`
|
||||
LastDisconnectTime *utils.NullTime `json:"lastDisconnectTime"`
|
||||
|
||||
StreamConnected bool `json:"-"`
|
||||
LastConnectTime *utils.NullTime `json:"-"`
|
||||
ChatClients map[string]Client `json:"-"`
|
||||
Viewers map[string]time.Time `json:"-"`
|
||||
StreamConnected bool `json:"-"`
|
||||
LastConnectTime *utils.NullTime `json:"-"`
|
||||
ChatClients map[string]Client `json:"-"`
|
||||
Viewers map[string]*Viewer `json:"-"`
|
||||
}
|
||||
|
30
models/viewer.go
Normal file
30
models/viewer.go
Normal file
@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/owncast/owncast/geoip"
|
||||
"github.com/owncast/owncast/utils"
|
||||
)
|
||||
|
||||
// Viewer represents a single video viewer.
|
||||
type Viewer struct {
|
||||
FirstSeen time.Time `json:"firstSeen"`
|
||||
LastSeen time.Time `json:"-"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
IPAddress string `json:"ipAddress"`
|
||||
ClientID string `json:"clientID"`
|
||||
Geo *geoip.GeoDetails `json:"geo"`
|
||||
}
|
||||
|
||||
// GenerateViewerFromRequest will return a chat client from a http request.
|
||||
func GenerateViewerFromRequest(req *http.Request) Viewer {
|
||||
return Viewer{
|
||||
FirstSeen: time.Now(),
|
||||
LastSeen: time.Now(),
|
||||
UserAgent: req.UserAgent(),
|
||||
IPAddress: utils.GetIPAddressFromRequest(req),
|
||||
ClientID: utils.GenerateClientIDFromRequest(req),
|
||||
}
|
||||
}
|
@ -97,6 +97,9 @@ func Start() error {
|
||||
// Get viewer count over time
|
||||
http.HandleFunc("/api/admin/viewersOverTime", middleware.RequireAdminAuth(admin.GetViewersOverTime))
|
||||
|
||||
// Get active viewers
|
||||
http.HandleFunc("/api/admin/viewers", middleware.RequireAdminAuth(admin.GetActiveViewers))
|
||||
|
||||
// Get hardware stats
|
||||
http.HandleFunc("/api/admin/hardwarestats", middleware.RequireAdminAuth(admin.GetHardwareStats))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user