177 lines
3.6 KiB
Go
177 lines
3.6 KiB
Go
package process
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os/exec"
|
|
"sync"
|
|
"time"
|
|
|
|
"tuner/models"
|
|
)
|
|
|
|
// MediaMTXManager manages the MediaMTX child process.
|
|
type MediaMTXManager struct {
|
|
mu sync.Mutex
|
|
cmd *exec.Cmd
|
|
binaryPath string
|
|
configPath string
|
|
startTime time.Time
|
|
lastError string
|
|
stopCh chan struct{}
|
|
stopped bool
|
|
}
|
|
|
|
// NewMediaMTXManager creates a new manager for the MediaMTX process.
|
|
func NewMediaMTXManager(binaryPath, configPath string) *MediaMTXManager {
|
|
return &MediaMTXManager{
|
|
binaryPath: binaryPath,
|
|
configPath: configPath,
|
|
}
|
|
}
|
|
|
|
// Start launches the MediaMTX process. It auto-restarts on unexpected exit.
|
|
func (m *MediaMTXManager) Start() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if m.cmd != nil && m.cmd.Process != nil {
|
|
return fmt.Errorf("mediamtx already running (pid %d)", m.cmd.Process.Pid)
|
|
}
|
|
|
|
return m.startLocked()
|
|
}
|
|
|
|
func (m *MediaMTXManager) startLocked() error {
|
|
_, err := exec.LookPath(m.binaryPath)
|
|
if err != nil {
|
|
m.lastError = fmt.Sprintf("binary not found: %s", m.binaryPath)
|
|
return fmt.Errorf("mediamtx binary not found: %s", m.binaryPath)
|
|
}
|
|
|
|
m.cmd = exec.Command(m.binaryPath, m.configPath)
|
|
m.lastError = ""
|
|
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)
|
|
}
|
|
|
|
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()
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
// Check if this was a deliberate stop
|
|
select {
|
|
case <-m.stopCh:
|
|
log.Println("[mediamtx] stopped")
|
|
return
|
|
default:
|
|
}
|
|
|
|
if err != nil {
|
|
m.lastError = err.Error()
|
|
log.Printf("[mediamtx] exited unexpectedly: %v — restarting in 2s", err)
|
|
} else {
|
|
log.Println("[mediamtx] exited unexpectedly (status 0) — restarting in 2s")
|
|
}
|
|
|
|
m.cmd = nil
|
|
|
|
// Auto-restart after a brief delay (without lock held)
|
|
go func() {
|
|
time.Sleep(2 * time.Second)
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.stopped {
|
|
return
|
|
}
|
|
if m.cmd != nil {
|
|
return // already restarted
|
|
}
|
|
if err := m.startLocked(); err != nil {
|
|
log.Printf("[mediamtx] auto-restart failed: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Stop kills the MediaMTX process.
|
|
func (m *MediaMTXManager) Stop() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.stopped = true
|
|
|
|
if m.cmd == nil || m.cmd.Process == nil {
|
|
return nil
|
|
}
|
|
|
|
if m.stopCh != nil {
|
|
close(m.stopCh)
|
|
}
|
|
|
|
if err := m.cmd.Process.Kill(); err != nil {
|
|
return fmt.Errorf("kill mediamtx: %w", err)
|
|
}
|
|
|
|
m.cmd = nil
|
|
log.Println("[mediamtx] killed")
|
|
return nil
|
|
}
|
|
|
|
// Restart stops and restarts the MediaMTX process.
|
|
func (m *MediaMTXManager) Restart() error {
|
|
if err := m.Stop(); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
return m.Start()
|
|
}
|
|
|
|
// Status returns the current process status.
|
|
func (m *MediaMTXManager) Status() models.ProcessInfo {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
info := models.ProcessInfo{
|
|
Error: m.lastError,
|
|
}
|
|
|
|
if m.cmd != nil && m.cmd.Process != nil {
|
|
info.Running = true
|
|
info.PID = m.cmd.Process.Pid
|
|
info.Uptime = time.Since(m.startTime).Truncate(time.Second).String()
|
|
}
|
|
|
|
return info
|
|
}
|