feat: harden engine config, discovery and backpressure stats
- config: validate port/quality/fps/source/stream-index ranges on load and before engine start (flinger.Config.Validate) - discovery: add Announce option to disable multicast (GUI checkbox, CLI --no-announce); absent JSON key keeps the default true - backpressure: count dropped frames in the TCP sender, expose via engine Status and a live counter in the GUI status label - docs: record M3/M4 decisions and X11 coverage via the portal backend
This commit is contained in:
@@ -48,23 +48,48 @@ type Config struct {
|
||||
Audio bool
|
||||
// StreamIndex selects which monitor to capture (screen source only).
|
||||
StreamIndex int
|
||||
// Announce controls whether the stream is advertised via UDP multicast.
|
||||
// When disabled, receivers must connect by IP manually.
|
||||
Announce bool
|
||||
}
|
||||
|
||||
// DefaultConfig returns the recommended defaults.
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Port: 9756,
|
||||
Quality: 80,
|
||||
FPS: 30,
|
||||
Source: "screen",
|
||||
Audio: true,
|
||||
Port: 9756,
|
||||
Quality: 80,
|
||||
FPS: 30,
|
||||
Source: "screen",
|
||||
Audio: true,
|
||||
Announce: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks the config for out-of-range or unsupported values.
|
||||
func (c Config) Validate() error {
|
||||
if c.Port < 1 || c.Port > 65535 {
|
||||
return errors.New("port must be 1–65535")
|
||||
}
|
||||
if c.Quality < 1 || c.Quality > 100 {
|
||||
return errors.New("quality must be 1–100")
|
||||
}
|
||||
if c.FPS < 1 || c.FPS > 240 {
|
||||
return errors.New("fps must be 1–240")
|
||||
}
|
||||
if c.Source != "screen" && c.Source != "pattern" {
|
||||
return errors.New("source must be screen or pattern")
|
||||
}
|
||||
if c.StreamIndex < 0 {
|
||||
return errors.New("stream index must be >= 0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status is a point-in-time snapshot of the running engine.
|
||||
type Status struct {
|
||||
Running bool
|
||||
Frames int64
|
||||
Dropped int64
|
||||
Conns int
|
||||
}
|
||||
|
||||
@@ -87,6 +112,10 @@ type Engine struct {
|
||||
// New creates an engine from cfg. Capture is opened eagerly so that
|
||||
// misconfiguration (e.g. no screen-share permission) surfaces before Start.
|
||||
func New(cfg Config) (*Engine, error) {
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
e := &Engine{cfg: cfg}
|
||||
|
||||
sender := output.New()
|
||||
@@ -124,8 +153,8 @@ func New(cfg Config) (*Engine, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Start begins the audio, video and stats loops and starts announcing the
|
||||
// stream. It is idempotent.
|
||||
// Start begins the audio, video and stats loops and (unless disabled in the
|
||||
// config) starts announcing the stream. It is idempotent.
|
||||
func (e *Engine) Start() {
|
||||
if e.stop != nil {
|
||||
return
|
||||
@@ -134,7 +163,9 @@ func (e *Engine) Start() {
|
||||
e.start = time.Now()
|
||||
e.stop = make(chan struct{})
|
||||
|
||||
e.announcer = discovery.Start(e.cfg.Name, e.sender.Port())
|
||||
if e.cfg.Announce {
|
||||
e.announcer = discovery.Start(e.cfg.Name, e.sender.Port())
|
||||
}
|
||||
|
||||
var src io.ReadCloser
|
||||
switch {
|
||||
@@ -180,6 +211,7 @@ func (e *Engine) Status() Status {
|
||||
return Status{
|
||||
Running: e.stop != nil,
|
||||
Frames: e.frames.Load(),
|
||||
Dropped: e.sender.Dropped(),
|
||||
Conns: e.sender.NumConns(),
|
||||
}
|
||||
}
|
||||
@@ -276,7 +308,11 @@ func (e *Engine) statsLoop() {
|
||||
select {
|
||||
case <-tick.C:
|
||||
st := e.Status()
|
||||
log.Printf("flinger: %d frames, %d conns", st.Frames, st.Conns)
|
||||
if st.Dropped > 0 {
|
||||
log.Printf("flinger: %d frames, %d dropped, %d conns", st.Frames, st.Dropped, st.Conns)
|
||||
} else {
|
||||
log.Printf("flinger: %d frames, %d conns", st.Frames, st.Conns)
|
||||
}
|
||||
case <-e.stop:
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user