package process import ( "fmt" "log" "os/exec" "sync" "time" "tuner/models" ) // 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 } // NewFFmpegManager creates a new FFmpeg process manager. func NewFFmpegManager() *FFmpegManager { return &FFmpegManager{} } // Start spawns an FFmpeg process to pull from streamURL and push RTMP to MediaMTX. // It kills any existing FFmpeg process first. func (f *FFmpegManager) Start(streamURL string) error { f.mu.Lock() defer f.mu.Unlock() // Kill existing process if running f.stopLocked() f.cmd = exec.Command("ffmpeg", "-re", "-i", streamURL, "-c", "copy", "-f", "flv", "rtmp://localhost:1935/live/stream", ) f.streamURL = streamURL f.lastError = "" if err := f.cmd.Start(); err != nil { f.lastError = err.Error() return fmt.Errorf("start ffmpeg: %w", err) } f.startTime = time.Now() log.Printf("[ffmpeg] started with pid %d, source: %s", f.cmd.Process.Pid, streamURL) // Monitor in background go f.monitor() return nil } func (f *FFmpegManager) monitor() { err := f.cmd.Wait() f.mu.Lock() defer f.mu.Unlock() if err != nil { f.lastError = err.Error() log.Printf("[ffmpeg] exited: %v", err) } else { log.Println("[ffmpeg] exited (status 0)") } f.cmd = nil } // Stop kills the FFmpeg process. func (f *FFmpegManager) Stop() error { f.mu.Lock() defer f.mu.Unlock() return f.stopLocked() } func (f *FFmpegManager) stopLocked() error { if f.cmd == nil || f.cmd.Process == nil { return nil } if err := f.cmd.Process.Kill(); err != nil { return fmt.Errorf("kill ffmpeg: %w", err) } // Wait for the process to fully exit to avoid zombies _ = f.cmd.Wait() f.cmd = nil log.Println("[ffmpeg] killed") return nil } // Status returns the current FFmpeg process status. func (f *FFmpegManager) Status() models.ProcessInfo { f.mu.Lock() defer f.mu.Unlock() info := models.ProcessInfo{ Error: f.lastError, } if f.cmd != nil && f.cmd.Process != nil { info.Running = true info.PID = f.cmd.Process.Pid info.Uptime = time.Since(f.startTime).Truncate(time.Second).String() } return info }