Refactoring #3

Merged
itten merged 87 commits from refactoring into main 2026-09-01 23:52:36 +03:00
3 changed files with 132 additions and 40 deletions
Showing only changes of commit a4626fbb19 - Show all commits
+102 -40
View File
@@ -368,6 +368,28 @@ func main() {
}, },
}) })
} }
drawUnitStatus := func(label string, unit playback.Unit) {
status, ok := statusStore.Snapshot(unit)
if !ok {
cimgui.Text(fmt.Sprintf("%s: not started", label))
return
}
cimgui.Text(fmt.Sprintf("%s: %s", label, status.State))
cimgui.Text(fmt.Sprintf(
"Attempt: %d, failed: %d",
status.Attempt,
status.FailedAttempts,
))
if status.RetryIn > 0 {
cimgui.Text(fmt.Sprintf(
"Retry in: %s",
status.RetryIn.Round(time.Millisecond),
))
}
if status.Err != nil {
cimgui.TextWrapped(status.Err.Error())
}
}
playbackDone := make(chan error, 1) playbackDone := make(chan error, 1)
go func() { go func() {
@@ -524,20 +546,48 @@ func main() {
cimgui.End() cimgui.End()
} }
cimgui.Begin("Connection") cimgui.Begin("Connection")
cimgui.InputTextWithHint("Video domain", "/dev/shm/mxl", &videoDomainStr, 0, nil)
cimgui.InputTextWithHint("Audio domain", "/dev/shm/mxl", &audioDomainStr, 0, nil) snapshot, hasSnapshot := player.Controller.Snapshot()
cimgui.InputTextWithHint("Video UUID", "", &videoStr, 0, nil) videoConfigured := videoStr != ""
cimgui.InputTextWithHint("Audio UUID", "", &audioStr, 0, nil) audioConfigured := audioStr != ""
if snapshot, ok := player.Controller.Snapshot(); ok { if hasSnapshot {
videoActive = snapshot.Desired.Video.Active videoActive = snapshot.Desired.Video.Active
audioActive = snapshot.Desired.Audio.Active audioActive = snapshot.Desired.Audio.Active
videoConfigured = snapshot.Desired.Video.IsConfigured()
audioConfigured = snapshot.Desired.Audio.IsConfigured()
syncRequested = snapshot.Desired.SyncRequested syncRequested = snapshot.Desired.SyncRequested
cimgui.Text(fmt.Sprintf(
"Topology: %s (generation %d)",
snapshot.Plan.Topology,
snapshot.Generation,
))
} else {
cimgui.Text("Topology: starting")
} }
if cimgui.Button("Connect") {
cimgui.Separator()
cimgui.InputTextWithHint("Video domain", "/dev/shm/mxl", &videoDomainStr, 0, nil)
cimgui.InputTextWithHint("Video UUID", "", &videoStr, 0, nil)
cimgui.InputTextWithHint("Audio domain", "/dev/shm/mxl", &audioDomainStr, 0, nil)
cimgui.InputTextWithHint("Audio UUID", "", &audioStr, 0, nil)
if cimgui.Button("Apply feeds") {
doReconnect() doReconnect()
} }
cimgui.SameLine() cimgui.SameLine()
cimgui.Checkbox("Show stats", &showStats) if videoActive || audioActive {
if cimgui.Button("Stop all") {
enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopAll})
}
}
if (videoConfigured && !videoActive) || (audioConfigured && !audioActive) {
if videoActive || audioActive {
cimgui.SameLine()
}
if cimgui.Button("Resume all") {
enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAll})
}
}
if cimgui.Checkbox("Synchronize", &syncRequested) { if cimgui.Checkbox("Synchronize", &syncRequested) {
kind := playback.CommandDisableSync kind := playback.CommandDisableSync
if syncRequested { if syncRequested {
@@ -545,20 +595,36 @@ func main() {
} }
enqueueCommand(playback.SessionCommand{Kind: kind}) enqueueCommand(playback.SessionCommand{Kind: kind})
} }
if hasSnapshot && syncRequested && snapshot.Plan.Topology != playback.TopologySynchronized {
switch {
case !videoConfigured || !audioConfigured:
cimgui.TextWrapped("Sync requested: waiting for both feeds to be configured.")
case !videoActive || !audioActive:
cimgui.TextWrapped("Sync requested: waiting for both feeds to be active.")
case snapshot.Desired.Video.Domain != snapshot.Desired.Audio.Domain:
cimgui.TextWrapped("Sync requested, but native MXL sync requires matching domains. Playing independently.")
default:
cimgui.TextWrapped("Sync requested but currently unavailable. Playing independently.")
}
}
cimgui.Separator()
cimgui.Text("Video")
if videoActive { if videoActive {
if cimgui.Button("Stop video") { if cimgui.Button("Stop video") {
videoActive = false videoActive = false
enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopVideo}) enqueueCommand(playback.SessionCommand{Kind: playback.CommandStopVideo})
} }
} }
if !videoActive && videoStr != "" { if !videoActive && videoConfigured {
cimgui.SameLine() cimgui.SameLine()
if cimgui.Button("Resume video") { if cimgui.Button("Resume video") {
videoActive = true videoActive = true
enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeVideo}) enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeVideo})
} }
} }
if videoStr != "" { if videoConfigured {
cimgui.SameLine()
if cimgui.Button("Remove video") { if cimgui.Button("Remove video") {
videoActive = false videoActive = false
videoStr = "" videoStr = ""
@@ -567,24 +633,14 @@ func main() {
} }
if videoActive { if videoActive {
cimgui.Text("Video desired: active") cimgui.Text("Video desired: active")
} else if videoStr != "" { } else if videoConfigured {
cimgui.Text("Video desired: stopped") cimgui.Text("Video desired: stopped")
} else { } else {
cimgui.Text("Video desired: not configured") cimgui.Text("Video desired: not configured")
} }
if status, ok := statusStore.Snapshot(playback.UnitVideo); ok { cimgui.Separator()
cimgui.Text(fmt.Sprintf("Video actual: %s", status.State)) cimgui.Text("Audio")
cimgui.Text(fmt.Sprintf("Attempt: %d, failed: %d", status.Attempt, status.FailedAttempts))
if status.RetryIn > 0 {
cimgui.Text(fmt.Sprintf("Retry in: %s", status.RetryIn.Round(time.Millisecond)))
}
if status.Err != nil {
cimgui.TextWrapped(status.Err.Error())
}
} else {
cimgui.Text("Video actual: not started")
}
if audioActive { if audioActive {
if cimgui.Button("Stop audio") { if cimgui.Button("Stop audio") {
audioActive = false audioActive = false
@@ -592,14 +648,16 @@ func main() {
} }
} }
if !audioActive && audioStr != "" { if !audioActive && audioConfigured {
cimgui.SameLine()
if cimgui.Button("Resume audio") { if cimgui.Button("Resume audio") {
audioActive = true audioActive = true
enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAudio}) enqueueCommand(playback.SessionCommand{Kind: playback.CommandResumeAudio})
} }
} }
if audioStr != "" { if audioConfigured {
cimgui.SameLine()
if cimgui.Button("Remove audio") { if cimgui.Button("Remove audio") {
audioActive = false audioActive = false
audioStr = "" audioStr = ""
@@ -609,31 +667,35 @@ func main() {
if audioActive { if audioActive {
cimgui.Text("Audio desired: active") cimgui.Text("Audio desired: active")
} else if audioStr != "" { } else if audioConfigured {
cimgui.Text("Audio desired: stopped") cimgui.Text("Audio desired: stopped")
} else { } else {
cimgui.Text("Audio desired: not configured") cimgui.Text("Audio desired: not configured")
} }
if status, ok := statusStore.Snapshot(playback.UnitAudio); ok { cimgui.Separator()
cimgui.Text(fmt.Sprintf("Audio actual: %s", status.State)) cimgui.Text("Current playback")
cimgui.Text(fmt.Sprintf("Attempt: %d, failed: %d", status.Attempt, status.FailedAttempts)) if hasSnapshot {
if status.RetryIn > 0 { switch snapshot.Plan.Topology {
cimgui.Text(fmt.Sprintf("Retry in: %s", status.RetryIn.Round(time.Millisecond))) case playback.TopologySynchronized:
} drawUnitStatus("Synchronized group", playback.UnitSync)
if status.Err != nil { case playback.TopologyIndependent:
cimgui.TextWrapped(status.Err.Error()) if snapshot.Plan.Video.Active {
drawUnitStatus("Video", playback.UnitVideo)
}
if snapshot.Plan.Audio.Active {
drawUnitStatus("Audio", playback.UnitAudio)
}
case playback.TopologyIdle:
cimgui.Text("No active feeds")
} }
} else { } else {
cimgui.Text("Audio actual: not started") cimgui.Text("Playback controller is starting")
}
if status, ok := statusStore.Snapshot(playback.UnitSync); ok {
cimgui.Text(fmt.Sprintf("Sync actual: %s", status.State))
if status.Err != nil {
cimgui.TextWrapped(status.Err.Error())
}
} }
cimgui.Separator()
cimgui.Checkbox("Show stats", &showStats)
cimgui.End() cimgui.End()
gui.EndFrame() gui.EndFrame()
lastFrame = time.Now() lastFrame = time.Now()
+13
View File
@@ -10,6 +10,19 @@ const (
TopologySynchronized TopologySynchronized
) )
func (t SessionTopology) String() string {
switch t {
case TopologyIdle:
return "idle"
case TopologyIndependent:
return "independent"
case TopologySynchronized:
return "synchronized"
default:
return fmt.Sprintf("SessionTopology(%d)", uint8(t))
}
}
type SyncPredicate func(video, audio FeedConfig) bool type SyncPredicate func(video, audio FeedConfig) bool
type SessionPlan struct { type SessionPlan struct {
+17
View File
@@ -5,6 +5,23 @@ import (
"testing" "testing"
) )
func TestSessionTopologyString(t *testing.T) {
tests := []struct {
topology SessionTopology
want string
}{
{TopologyIdle, "idle"},
{TopologyIndependent, "independent"},
{TopologySynchronized, "synchronized"},
{SessionTopology(99), "SessionTopology(99)"},
}
for _, tt := range tests {
if got := tt.topology.String(); got != tt.want {
t.Errorf("%d.String() = %q, want %q", tt.topology, got, tt.want)
}
}
}
func TestBuildSessionPlan(t *testing.T) { func TestBuildSessionPlan(t *testing.T) {
base := validCommandSession() base := validCommandSession()
stoppedVideo := stoppedFeed(base.Video) stoppedVideo := stoppedFeed(base.Video)