Tuner v1: HLS pipeline with playback/ingest modes

FFmpeg -> MediaMTX -> HLS relay with runtime-configurable modes:
- PLAYBACK_MODE: standard (mpegts), fmp4 (HEVC), llhls
- INGEST_MODE: rtmp, rtsp
- INGEST_AUDIO_MODE: aac, copy
- Hourly playlist auto-refresh from upstream URL
- Fullscreen video UI with collapsible sidebar, upstream-down banner
- Same-origin HLS proxy for HTTPS deployments

Tagged as the last HLS-based version before the mpegts.js (v2) rework.
This commit is contained in:
2026-06-12 19:17:15 -07:00
parent a41c3ee56c
commit 88a189de34
20 changed files with 814 additions and 123 deletions

View File

@@ -3,8 +3,11 @@ package api
import (
"encoding/json"
"net/http"
"time"
)
const upstreamDownAfter = 10 * time.Second
// HandleStatus returns the current stream status as JSON.
// GET /api/status
func (a *App) HandleStatus(w http.ResponseWriter, r *http.Request) {
@@ -20,12 +23,26 @@ func (a *App) HandleStatus(w http.ResponseWriter, r *http.Request) {
// Attach live stream health metrics when FFmpeg is running
status.Health = a.FFmpeg.Health()
// Auto-clear transitioning once FFmpeg is producing health data
// Auto-clear transitioning once FFmpeg is producing health data.
// If no progress arrives for a while after a channel switch, flag upstream_down
// so all watchers see an "Upstream down?" warning via polling.
if status.Transitioning && status.Health != nil && status.Health.Speed != "" {
a.mu.Lock()
a.Status.Transitioning = false
a.Status.UpstreamDown = false
a.mu.Unlock()
status.Transitioning = false
status.UpstreamDown = false
} else if status.Transitioning {
a.mu.RLock()
switchStartedAt := a.SwitchStartedAt
a.mu.RUnlock()
if !switchStartedAt.IsZero() && time.Since(switchStartedAt) >= upstreamDownAfter {
a.mu.Lock()
a.Status.UpstreamDown = true
a.mu.Unlock()
status.UpstreamDown = true
}
}
w.Header().Set("Content-Type", "application/json")