This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user