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:
@@ -15,17 +15,25 @@ import (
|
||||
|
||||
// FFmpegManager manages an FFmpeg child process for IPTV relay.
|
||||
type FFmpegManager struct {
|
||||
mu sync.Mutex
|
||||
cmd *exec.Cmd
|
||||
startTime time.Time
|
||||
lastError string
|
||||
streamURL string
|
||||
health models.StreamHealth
|
||||
mu sync.Mutex
|
||||
cmd *exec.Cmd
|
||||
ingestMode string
|
||||
audioMode string
|
||||
startTime time.Time
|
||||
lastError string
|
||||
streamURL string
|
||||
health models.StreamHealth
|
||||
}
|
||||
|
||||
// NewFFmpegManager creates a new FFmpeg process manager.
|
||||
func NewFFmpegManager() *FFmpegManager {
|
||||
return &FFmpegManager{}
|
||||
func NewFFmpegManager(ingestMode, audioMode string) *FFmpegManager {
|
||||
if ingestMode != "rtsp" {
|
||||
ingestMode = "rtmp"
|
||||
}
|
||||
if audioMode != "copy" {
|
||||
audioMode = "aac"
|
||||
}
|
||||
return &FFmpegManager{ingestMode: ingestMode, audioMode: audioMode}
|
||||
}
|
||||
|
||||
// Start spawns an FFmpeg process to pull from streamURL and push to MediaMTX.
|
||||
@@ -37,17 +45,39 @@ func (f *FFmpegManager) Start(streamURL string) error {
|
||||
// Kill existing process if running
|
||||
f.stopLocked()
|
||||
|
||||
f.cmd = exec.Command("ffmpeg",
|
||||
args := []string{
|
||||
"-loglevel", "warning",
|
||||
"-progress", "pipe:1",
|
||||
"-reconnect", "1",
|
||||
"-reconnect_streamed", "1",
|
||||
"-reconnect_delay_max", "5",
|
||||
"-i", streamURL,
|
||||
"-c", "copy",
|
||||
"-f", "flv",
|
||||
"rtmp://localhost:1935/live/stream",
|
||||
)
|
||||
}
|
||||
if f.ingestMode == "rtsp" {
|
||||
if f.audioMode == "copy" {
|
||||
args = append(args, "-c", "copy")
|
||||
} else {
|
||||
args = append(args,
|
||||
"-c:v", "copy",
|
||||
"-c:a", "aac",
|
||||
"-ar", "48000",
|
||||
"-b:a", "160k",
|
||||
)
|
||||
}
|
||||
args = append(args,
|
||||
"-f", "rtsp",
|
||||
"-rtsp_transport", "tcp",
|
||||
"rtsp://localhost:8554/live/stream",
|
||||
)
|
||||
} else {
|
||||
args = append(args,
|
||||
"-c", "copy",
|
||||
"-f", "flv",
|
||||
"rtmp://localhost:1935/live/stream",
|
||||
)
|
||||
}
|
||||
|
||||
f.cmd = exec.Command("ffmpeg", args...)
|
||||
f.streamURL = streamURL
|
||||
f.lastError = ""
|
||||
f.health = models.StreamHealth{}
|
||||
@@ -68,7 +98,7 @@ func (f *FFmpegManager) Start(streamURL string) error {
|
||||
}
|
||||
|
||||
f.startTime = time.Now()
|
||||
log.Printf("[ffmpeg] started with pid %d, source: %s", f.cmd.Process.Pid, streamURL)
|
||||
log.Printf("[ffmpeg] started with pid %d, ingest=%s, audio=%s, source: %s", f.cmd.Process.Pid, f.ingestMode, f.audioMode, streamURL)
|
||||
|
||||
// Pass cmd reference so goroutines can detect process replacement
|
||||
cmd := f.cmd
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -18,6 +19,7 @@ type MediaMTXManager struct {
|
||||
cmd *exec.Cmd
|
||||
binaryPath string
|
||||
configPath string
|
||||
extraEnv []string
|
||||
startTime time.Time
|
||||
lastError string
|
||||
stopCh chan struct{}
|
||||
@@ -32,6 +34,14 @@ func NewMediaMTXManager(binaryPath, configPath string) *MediaMTXManager {
|
||||
}
|
||||
}
|
||||
|
||||
// SetEnv sets additional environment variables (e.g. MTX_HLSVARIANT) that are
|
||||
// passed to MediaMTX. Must be called before Start.
|
||||
func (m *MediaMTXManager) SetEnv(env []string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.extraEnv = env
|
||||
}
|
||||
|
||||
// Start launches the MediaMTX process. It auto-restarts on unexpected exit.
|
||||
func (m *MediaMTXManager) Start() error {
|
||||
m.mu.Lock()
|
||||
@@ -52,6 +62,9 @@ func (m *MediaMTXManager) startLocked() error {
|
||||
}
|
||||
|
||||
m.cmd = exec.Command(m.binaryPath, m.configPath)
|
||||
if len(m.extraEnv) > 0 {
|
||||
m.cmd.Env = append(os.Environ(), m.extraEnv...)
|
||||
}
|
||||
m.lastError = ""
|
||||
m.stopped = false
|
||||
m.stopCh = make(chan struct{})
|
||||
|
||||
Reference in New Issue
Block a user