This commit is contained in:
2025-09-21 19:20:28 +02:00
parent 6166b8111e
commit ca9cbce083
4 changed files with 47 additions and 22 deletions

View File

@@ -147,12 +147,16 @@ func main() {
func apiStreams(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
c := &mtx.Client{BaseURL: mtxAPI, User: os.Getenv("MTX_API_USER"), Pass: os.Getenv("MTX_API_PASS")}
pl, err := c.Paths(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
_ = json.NewEncoder(w).Encode(map[string]any{"error": err.Error()})
return
}
allowed := map[string]bool{}
if streamsCSV != "" {
for _, s := range strings.Split(streamsCSV, ",") {
@@ -174,7 +178,7 @@ func apiStreams(w http.ResponseWriter, r *http.Request) {
out.Items = append(out.Items, item{Name: p.Name, Live: p.Live(), Viewers: p.Viewers()})
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(out)
_ = json.NewEncoder(w).Encode(out)
}
func pageIndex(w http.ResponseWriter, r *http.Request) {

View File

@@ -1,25 +1,35 @@
async function load(){
const r = await fetch('/api/streams');
const data = await r.json();
let data;
try {
const r = await fetch('/api/streams', { cache: 'no-store' });
if (!r.ok) throw new Error('api '+r.status);
data = await r.json();
} catch (e) {
console.warn('streams api error:', e);
// UI freundlich degradieren
document.getElementById('list').innerHTML =
'<div class="card"><div class="muted">Keine Daten (API-Fehler)</div></div>';
return;
}
const q = (document.getElementById('filter').value||'').toLowerCase();
const list = document.getElementById('list');
list.innerHTML = '';
data.items
.filter(it => !q || it.name.toLowerCase().includes(q))
.forEach(it => {
const a = document.createElement('a');
a.href = '/' + encodeURIComponent(it.name);
a.className = 'card';
a.innerHTML = `
<div class="row space-between">
<div>
<div class="title-strong">${it.name}</div>
<div class="muted">Zuschauer: ${it.viewers}</div>
</div>
<div class="pill ${it.live ? 'live':'off'}">${it.live ? 'LIVE' : 'Offline'}</div>
</div>`;
list.appendChild(a);
});
data.items.filter(it => !q || it.name.toLowerCase().includes(q)).forEach(it => {
const a = document.createElement('a');
a.href = '/' + encodeURIComponent(it.name);
a.className = 'card';
a.innerHTML = `
<div class="row space-between">
<div>
<div class="title-strong">${it.name}</div>
<div class="muted">Zuschauer: ${it.viewers}</div>
</div>
<div class="pill ${it.live ? 'live':'off'}">${it.live ? 'LIVE' : 'Offline'}</div>
</div>`;
list.appendChild(a);
});
}
document.getElementById('filter').addEventListener('input', load);
load(); setInterval(load, 3000);
document.getElementById('reload').addEventListener('click', load);
load();
setInterval(load, 3000);

View File

@@ -11,6 +11,17 @@
}
async function refresh(){
let data;
try{
const r = await fetch('/api/streams', { cache: 'no-store' });
if (!r.ok) throw new Error('api '+r.status);
data = await r.json();
}catch(e){
console.warn('streams api error:', e);
setLive(false);
viewersEl.textContent = 'Zuschauer: ';
return;
}
const r = await fetch('/api/streams');
const d = await r.json();
const it = d.items.find(x=>x.name===name);

View File

@@ -16,7 +16,7 @@
<h1 class="title-no-margin">🎬 Streams</h1>
<div class="row">
<input id="filter" placeholder="Filter (z. B. stream1)">
<a href="/refresh" class="pill">Neu laden</a>
<button id="reload" class="pill" type="button">Neu laden</button>
</div>
</div>
<p class="muted">RTMP Ingest: <code>rtmp://HOST/&lt;name&gt;</code> · HLS: <code>http(s)://HOST/hls/&lt;name&gt;</code></p>