73 lines
1.7 KiB
Go
73 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"sync"
|
|
)
|
|
|
|
// videoController owns the optional fpv-video launcher. It never touches WFB
|
|
// directly; fpv-video remains the sole owner of the video receiver lifecycle.
|
|
type videoController struct {
|
|
command string
|
|
mu sync.Mutex
|
|
cmd *exec.Cmd
|
|
done chan error
|
|
}
|
|
|
|
func newVideoController(command string) *videoController {
|
|
return &videoController{command: command}
|
|
}
|
|
|
|
func (v *videoController) Set(active bool) error {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
|
|
if active {
|
|
if v.cmd != nil {
|
|
return nil
|
|
}
|
|
v.cmd = exec.Command(v.command)
|
|
v.cmd.Stdout, v.cmd.Stderr = os.Stdout, os.Stderr
|
|
if err := v.cmd.Start(); err != nil {
|
|
v.cmd = nil
|
|
return fmt.Errorf("start %s: %w", v.command, err)
|
|
}
|
|
v.done = make(chan error, 1)
|
|
cmd := v.cmd
|
|
go func() { v.done <- cmd.Wait() }()
|
|
return nil
|
|
}
|
|
|
|
if v.cmd == nil {
|
|
return nil
|
|
}
|
|
if err := v.cmd.Process.Signal(os.Interrupt); err != nil {
|
|
return fmt.Errorf("stop %s: %w", v.command, err)
|
|
}
|
|
<-v.done
|
|
v.cmd, v.done = nil, nil
|
|
return nil
|
|
}
|
|
|
|
// Running reports whether the video launcher is currently alive. It is the
|
|
// supervisor's decoded-video presence for the OSD snapshot: the launcher is
|
|
// the only video decoder this process knows about. A launcher that exited
|
|
// on its own stays recorded until the next Set; its pending wait result
|
|
// proves it is no longer alive without consuming the signal the stop path
|
|
// relies on. Reading this never signals or waits on the launcher, so it
|
|
// never disturbs the video path.
|
|
func (v *videoController) Running() bool {
|
|
v.mu.Lock()
|
|
defer v.mu.Unlock()
|
|
if v.cmd == nil {
|
|
return false
|
|
}
|
|
return len(v.done) == 0
|
|
}
|
|
|
|
func (v *videoController) Stop() error {
|
|
return v.Set(false)
|
|
}
|