From 3addc882d5582ee9755fe1230de5a28e195b53de Mon Sep 17 00:00:00 2001 From: jbergner Date: Sun, 21 Sep 2025 22:25:28 +0200 Subject: [PATCH] bugfix-12 --- cmd/dashboard/main.go | 148 +++++++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 54 deletions(-) diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go index d2c3aed..999e28a 100644 --- a/cmd/dashboard/main.go +++ b/cmd/dashboard/main.go @@ -87,6 +87,63 @@ func eq(a, b []byte) bool { 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 { // v1.15.0 liefert einen Muxer mit Clients-Liste (Feldname kann "clients" heißen) Item struct { @@ -146,48 +203,6 @@ func mustLoadTemplates() *template.Template { 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() { log.SetFlags(log.LstdFlags | log.Lshortfile) tpls = mustLoadTemplates() @@ -304,17 +319,30 @@ func main() { if len(allowed) > 0 && !allowed[p.Name] { 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 - // 1) Versuche echte HLS-Clients zu zählen - ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second) - if v, err := hlsViewers(ctx2, mtxAPI, os.Getenv("MTX_API_USER"), os.Getenv("MTX_API_PASS"), p.Name); err == nil { + // 1) Echte HLS-Zuschauer aus der Batch-Map + if v, ok := hlsMap[p.Name]; ok { viewers = v } - cancel2() - // 2) Fallback: wenn HLS-API nicht greift (z.B. stream per RTSP/WebRTC gelesen), - // nimm Pfad-Reader (kann >0 bei Nicht-HLS sein, bei HLS meist 1 = Muxer) + // 2) Fallback: Nicht-HLS-Reader (oder wenn Map leer ist) + // 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 v := p.Viewers(); v > 0 { viewers = v @@ -435,17 +463,29 @@ func apiStreams(w http.ResponseWriter, r *http.Request) { if len(allowed) > 0 && !allowed[p.Name] { 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 - // 1) Versuche echte HLS-Clients zu zählen - ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second) - if v, err := hlsViewers(ctx2, mtxAPI, os.Getenv("MTX_API_USER"), os.Getenv("MTX_API_PASS"), p.Name); err == nil { + // 1) Echte HLS-Zuschauer aus der Batch-Map + if v, ok := hlsMap[p.Name]; ok { viewers = v } - cancel2() - // 2) Fallback: wenn HLS-API nicht greift (z.B. stream per RTSP/WebRTC gelesen), - // nimm Pfad-Reader (kann >0 bei Nicht-HLS sein, bei HLS meist 1 = Muxer) + // 2) Fallback: Nicht-HLS-Reader (oder wenn Map leer ist) + // 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 v := p.Viewers(); v > 0 { viewers = v