Smoother daemon: no-op reloads, batched placement, muted audio, graceful teardown
- Skip stream restarts when a reload resolves to the identical wall (same URLs, tile geometry, player settings) — saving an unrelated config edit no longer blanks the screen. - Replace per-tile placement polling (a swaymsg fork per tile per second, plus 150ms get_tree polling per tile at startup) with one shared loop: a single get_tree snapshot per tick, re-placing only drifted windows, relaxing to a 2s tick once settled. - Mute streams by default (--no-audio) to skip an audio decoder per stream; opt back in with player.audio: true. - Graceful, parallel mpv teardown via cmd.Cancel/WaitDelay, fixing the double-Wait race between Stop and Supervise and cutting worst-case layout switches from ~2s x N streams to ~2s total. - status: probe mpv IPC outside the daemon mutex so a slow probe can't block layout switches. - View sync: reuse the Protect session across ticks (re-login only on expiry) and pick up view_refresh_seconds changes without a restart. - Health strikes keyed by player, not slot, so they never carry across layout switches; prune stale entries. - New `rtsp-streamer reload` CLI; fix dead sort in `layout ls`; back off on persistent control-socket accept errors; fsync config before the atomic rename (SD-card power-cut safety); bump IPC client deadline; gofmt stragglers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,11 @@ func (p *Player) args() []string {
|
||||
"--framedrop=decoder+vo",
|
||||
"--demuxer-lavf-o=stimeout=5000000,fflags=+nobuffer,flags=+low_delay",
|
||||
}
|
||||
if !p.cfg.Audio {
|
||||
// A wall of simultaneous feeds is unwatchable with sound, and skipping
|
||||
// the audio decoder saves CPU per stream on the Pi.
|
||||
args = append(args, "--no-audio")
|
||||
}
|
||||
if p.cfg.MaxFPS > 0 {
|
||||
args = append(args, fmt.Sprintf("--vf=fps=%d", p.cfg.MaxFPS))
|
||||
}
|
||||
@@ -121,6 +126,11 @@ func (p *Player) start(ctx context.Context) error {
|
||||
p.log.Warn("stale ipc socket", "err", err)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, "mpv", p.args()...)
|
||||
// On ctx cancel, ask mpv to exit cleanly first; WaitDelay escalates to
|
||||
// SIGKILL if it hasn't gone away shortly after. This makes layout teardown
|
||||
// graceful and parallel — every supervisor reaps its own process.
|
||||
cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGTERM) }
|
||||
cmd.WaitDelay = 2 * time.Second
|
||||
// Inherit the caller's environment (WAYLAND_DISPLAY etc. must be set).
|
||||
cmd.Env = os.Environ()
|
||||
// Discard mpv's chatty stdout/stderr; errors surface via exit code.
|
||||
@@ -174,7 +184,11 @@ func (p *Player) Supervise(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Stop terminates the mpv process (SIGTERM, then SIGKILL after a grace period).
|
||||
// Stop asks the mpv process to exit (SIGTERM, escalating to SIGKILL if it
|
||||
// ignores it). It never calls Wait — Supervise owns the reap, notices the
|
||||
// exit, and relaunches unless its context has been cancelled. Watching p.cmd
|
||||
// (which Supervise clears after reaping) instead of double-Waiting avoids a
|
||||
// race on the process handle.
|
||||
func (p *Player) Stop() {
|
||||
p.mu.Lock()
|
||||
cmd := p.cmd
|
||||
@@ -182,14 +196,21 @@ func (p *Player) Stop() {
|
||||
if cmd == nil || cmd.Process == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
done := make(chan struct{})
|
||||
go func() { _, _ = cmd.Process.Wait(); close(done) }()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
_ = cmd.Process.Kill()
|
||||
}
|
||||
proc := cmd.Process
|
||||
_ = proc.Signal(syscall.SIGTERM)
|
||||
go func() {
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
p.mu.Lock()
|
||||
alive := p.cmd == cmd
|
||||
p.mu.Unlock()
|
||||
if !alive {
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
_ = proc.Kill()
|
||||
}()
|
||||
}
|
||||
|
||||
// Command sends a JSON IPC command to mpv and returns the decoded reply. Used
|
||||
|
||||
Reference in New Issue
Block a user