This commit is contained in:
@@ -87,6 +87,63 @@ func eq(a, b []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type hlsMuxersListResp struct {
|
||||||
|
Items []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Clients []interface{} `json:"clients"` // echte HLS-Viewer (HTTP-Clients)
|
||||||
|
} `json:"items"`
|
||||||
|
// manche Builds liefern "items" flach; das fangen wir ab:
|
||||||
|
Name string `json:"name"`
|
||||||
|
Clients []interface{} `json:"clients"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchHLSClientMap(ctx context.Context, base, user, pass string) (map[string]int, error) {
|
||||||
|
u := strings.TrimRight(base, "/") + "/v3/hlsmuxers/list"
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user != "" || pass != "" {
|
||||||
|
req.SetBasicAuth(user, pass)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Wenn noch kein einziger HLS-Client existiert, liefert MediaMTX oft 200 mit leerer Liste
|
||||||
|
// (oder 200 mit items=[]). 404 sollte hier nicht auftreten – falls doch, behandeln wir es als "leer".
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
return map[string]int{}, nil
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("hlsmuxers/list: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out hlsMuxersListResp
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string]int)
|
||||||
|
|
||||||
|
if len(out.Items) > 0 {
|
||||||
|
for _, it := range out.Items {
|
||||||
|
m[it.Name] = len(it.Clients)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: flache Struktur (ein einzelner Muxer)
|
||||||
|
if out.Name != "" {
|
||||||
|
m[out.Name] = len(out.Clients)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
type mtxHLSPayload struct {
|
type mtxHLSPayload struct {
|
||||||
// v1.15.0 liefert einen Muxer mit Clients-Liste (Feldname kann "clients" heißen)
|
// v1.15.0 liefert einen Muxer mit Clients-Liste (Feldname kann "clients" heißen)
|
||||||
Item struct {
|
Item struct {
|
||||||
@@ -146,48 +203,6 @@ func mustLoadTemplates() *template.Template {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
type mtxPathGetResp struct {
|
|
||||||
Item struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Readers []interface{} `json:"readers"`
|
|
||||||
} `json:"item"`
|
|
||||||
// einige Builds liefern die Felder auch „flach“:
|
|
||||||
Name string `json:"name"`
|
|
||||||
Readers []interface{} `json:"readers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchMTXViewers(ctx context.Context, base, user, pass, name string) (int, error) {
|
|
||||||
u := strings.TrimRight(base, "/") + "/v3/paths/get/" + url.PathEscape(name)
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if user != "" || pass != "" {
|
|
||||||
req.SetBasicAuth(user, pass)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return 0, fmt.Errorf("mtx get %s: %s", name, resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
var out mtxPathGetResp
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Priorität: verschachtelt unter item.*, sonst flach
|
|
||||||
if out.Item.Readers != nil {
|
|
||||||
return len(out.Item.Readers), nil
|
|
||||||
}
|
|
||||||
return len(out.Readers), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
tpls = mustLoadTemplates()
|
tpls = mustLoadTemplates()
|
||||||
@@ -304,17 +319,30 @@ func main() {
|
|||||||
if len(allowed) > 0 && !allowed[p.Name] {
|
if len(allowed) > 0 && !allowed[p.Name] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// echte HLS-Viewerzahlen einmalig holen (Batch)
|
||||||
|
hlsMap := map[string]int{}
|
||||||
|
{
|
||||||
|
ctxH, cancelH := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
tmp, err := fetchHLSClientMap(ctxH, mtxAPI, os.Getenv("MTX_API_USER"), os.Getenv("MTX_API_PASS"))
|
||||||
|
cancelH()
|
||||||
|
if err == nil && tmp != nil {
|
||||||
|
hlsMap = tmp
|
||||||
|
} else {
|
||||||
|
// optional: log.Printf("warn: hlsmuxers/list: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewers := 0
|
viewers := 0
|
||||||
|
|
||||||
// 1) Versuche echte HLS-Clients zu zählen
|
// 1) Echte HLS-Zuschauer aus der Batch-Map
|
||||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second)
|
if v, ok := hlsMap[p.Name]; ok {
|
||||||
if v, err := hlsViewers(ctx2, mtxAPI, os.Getenv("MTX_API_USER"), os.Getenv("MTX_API_PASS"), p.Name); err == nil {
|
|
||||||
viewers = v
|
viewers = v
|
||||||
}
|
}
|
||||||
cancel2()
|
|
||||||
|
|
||||||
// 2) Fallback: wenn HLS-API nicht greift (z.B. stream per RTSP/WebRTC gelesen),
|
// 2) Fallback: Nicht-HLS-Reader (oder wenn Map leer ist)
|
||||||
// nimm Pfad-Reader (kann >0 bei Nicht-HLS sein, bei HLS meist 1 = Muxer)
|
// Bei HLS ist p.Viewers() meist 1 (der Muxer), aber das wollen wir nur nutzen,
|
||||||
|
// wenn wir keinen HLS-Wert haben.
|
||||||
if viewers == 0 {
|
if viewers == 0 {
|
||||||
if v := p.Viewers(); v > 0 {
|
if v := p.Viewers(); v > 0 {
|
||||||
viewers = v
|
viewers = v
|
||||||
@@ -435,17 +463,29 @@ func apiStreams(w http.ResponseWriter, r *http.Request) {
|
|||||||
if len(allowed) > 0 && !allowed[p.Name] {
|
if len(allowed) > 0 && !allowed[p.Name] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// echte HLS-Viewerzahlen einmalig holen (Batch)
|
||||||
|
hlsMap := map[string]int{}
|
||||||
|
{
|
||||||
|
ctxH, cancelH := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
tmp, err := fetchHLSClientMap(ctxH, mtxAPI, os.Getenv("MTX_API_USER"), os.Getenv("MTX_API_PASS"))
|
||||||
|
cancelH()
|
||||||
|
if err == nil && tmp != nil {
|
||||||
|
hlsMap = tmp
|
||||||
|
} else {
|
||||||
|
// optional: log.Printf("warn: hlsmuxers/list: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewers := 0
|
viewers := 0
|
||||||
|
|
||||||
// 1) Versuche echte HLS-Clients zu zählen
|
// 1) Echte HLS-Zuschauer aus der Batch-Map
|
||||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second)
|
if v, ok := hlsMap[p.Name]; ok {
|
||||||
if v, err := hlsViewers(ctx2, mtxAPI, os.Getenv("MTX_API_USER"), os.Getenv("MTX_API_PASS"), p.Name); err == nil {
|
|
||||||
viewers = v
|
viewers = v
|
||||||
}
|
}
|
||||||
cancel2()
|
|
||||||
|
|
||||||
// 2) Fallback: wenn HLS-API nicht greift (z.B. stream per RTSP/WebRTC gelesen),
|
// 2) Fallback: Nicht-HLS-Reader (oder wenn Map leer ist)
|
||||||
// nimm Pfad-Reader (kann >0 bei Nicht-HLS sein, bei HLS meist 1 = Muxer)
|
// Bei HLS ist p.Viewers() meist 1 (der Muxer), aber das wollen wir nur nutzen,
|
||||||
|
// wenn wir keinen HLS-Wert haben.
|
||||||
if viewers == 0 {
|
if viewers == 0 {
|
||||||
if v := p.Viewers(); v > 0 {
|
if v := p.Viewers(); v > 0 {
|
||||||
viewers = v
|
viewers = v
|
||||||
|
|||||||
Reference in New Issue
Block a user