udpate stuff
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -17,6 +20,7 @@ type FFmpegManager struct {
|
||||
startTime time.Time
|
||||
lastError string
|
||||
streamURL string
|
||||
health models.StreamHealth
|
||||
}
|
||||
|
||||
// NewFFmpegManager creates a new FFmpeg process manager.
|
||||
@@ -24,7 +28,7 @@ func NewFFmpegManager() *FFmpegManager {
|
||||
return &FFmpegManager{}
|
||||
}
|
||||
|
||||
// Start spawns an FFmpeg process to pull from streamURL and push RTMP to MediaMTX.
|
||||
// Start spawns an FFmpeg process to pull from streamURL and push to MediaMTX.
|
||||
// It kills any existing FFmpeg process first.
|
||||
func (f *FFmpegManager) Start(streamURL string) error {
|
||||
f.mu.Lock()
|
||||
@@ -34,7 +38,11 @@ func (f *FFmpegManager) Start(streamURL string) error {
|
||||
f.stopLocked()
|
||||
|
||||
f.cmd = exec.Command("ffmpeg",
|
||||
"-re",
|
||||
"-loglevel", "warning",
|
||||
"-progress", "pipe:1",
|
||||
"-reconnect", "1",
|
||||
"-reconnect_streamed", "1",
|
||||
"-reconnect_delay_max", "5",
|
||||
"-i", streamURL,
|
||||
"-c", "copy",
|
||||
"-f", "flv",
|
||||
@@ -42,6 +50,17 @@ func (f *FFmpegManager) Start(streamURL string) error {
|
||||
)
|
||||
f.streamURL = streamURL
|
||||
f.lastError = ""
|
||||
f.health = models.StreamHealth{}
|
||||
|
||||
// Capture stdout for progress stats, stderr for errors
|
||||
stdout, err := f.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdout pipe: %w", err)
|
||||
}
|
||||
stderr, err := f.cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stderr pipe: %w", err)
|
||||
}
|
||||
|
||||
if err := f.cmd.Start(); err != nil {
|
||||
f.lastError = err.Error()
|
||||
@@ -51,18 +70,59 @@ 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)
|
||||
|
||||
// Monitor in background
|
||||
go f.monitor()
|
||||
// Pass cmd reference so goroutines can detect process replacement
|
||||
cmd := f.cmd
|
||||
go f.parseProgress(stdout)
|
||||
go f.logStderr(stderr)
|
||||
go f.monitor(cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FFmpegManager) monitor() {
|
||||
err := f.cmd.Wait()
|
||||
// parseProgress reads FFmpeg -progress output and updates health stats.
|
||||
func (f *FFmpegManager) parseProgress(r io.Reader) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
f.mu.Lock()
|
||||
switch k {
|
||||
case "speed":
|
||||
f.health.Speed = v
|
||||
case "fps":
|
||||
f.health.FPS = v
|
||||
case "bitrate":
|
||||
f.health.Bitrate = v
|
||||
case "drop_frames":
|
||||
f.health.DropFrames = v
|
||||
case "dup_frames":
|
||||
f.health.DupFrames = v
|
||||
}
|
||||
f.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FFmpegManager) logStderr(r io.Reader) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
log.Printf("[ffmpeg] %s", scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FFmpegManager) monitor(cmd *exec.Cmd) {
|
||||
err := cmd.Wait()
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
// If the process was replaced by a new Start() call, don't touch state
|
||||
if f.cmd != cmd {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
f.lastError = err.Error()
|
||||
log.Printf("[ffmpeg] exited: %v", err)
|
||||
@@ -96,6 +156,19 @@ func (f *FFmpegManager) stopLocked() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Health returns the latest stream health stats from FFmpeg progress.
|
||||
func (f *FFmpegManager) Health() *models.StreamHealth {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if f.cmd == nil || f.health.Speed == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
h := f.health // copy
|
||||
return &h
|
||||
}
|
||||
|
||||
// Status returns the current FFmpeg process status.
|
||||
func (f *FFmpegManager) Status() models.ProcessInfo {
|
||||
f.mu.Lock()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"sync"
|
||||
@@ -54,6 +56,10 @@ func (m *MediaMTXManager) startLocked() error {
|
||||
m.stopped = false
|
||||
m.stopCh = make(chan struct{})
|
||||
|
||||
// Capture stdout/stderr for logging
|
||||
stdout, _ := m.cmd.StdoutPipe()
|
||||
stderr, _ := m.cmd.StderrPipe()
|
||||
|
||||
if err := m.cmd.Start(); err != nil {
|
||||
m.lastError = err.Error()
|
||||
return fmt.Errorf("start mediamtx: %w", err)
|
||||
@@ -62,12 +68,22 @@ func (m *MediaMTXManager) startLocked() error {
|
||||
m.startTime = time.Now()
|
||||
log.Printf("[mediamtx] started with pid %d", m.cmd.Process.Pid)
|
||||
|
||||
go m.logOutput("stdout", stdout)
|
||||
go m.logOutput("stderr", stderr)
|
||||
|
||||
// Monitor in background and auto-restart on unexpected exit
|
||||
go m.monitor()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MediaMTXManager) logOutput(name string, r io.Reader) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
log.Printf("[mediamtx:%s] %s", name, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MediaMTXManager) monitor() {
|
||||
err := m.cmd.Wait()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user