Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4626fbb19 | |||
| 047f334413 |
+116
-53
@@ -342,30 +342,53 @@ func main() {
|
|||||||
videoActive = videoStr != ""
|
videoActive = videoStr != ""
|
||||||
audioActive = audioStr != ""
|
audioActive = audioStr != ""
|
||||||
|
|
||||||
if videoStr == "" {
|
videoConfig := playback.FeedConfig{}
|
||||||
enqueueCommand(playback.SessionCommand{Kind: playback.CommandRemoveVideo})
|
if videoActive {
|
||||||
} else {
|
videoConfig = playback.FeedConfig{
|
||||||
enqueueCommand(playback.SessionCommand{
|
|
||||||
Kind: playback.CommandSetVideo,
|
|
||||||
Config: playback.FeedConfig{
|
|
||||||
Domain: videoDomainStr,
|
Domain: videoDomainStr,
|
||||||
UUID: videoStr,
|
UUID: videoStr,
|
||||||
Active: true,
|
Active: true,
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if audioStr == "" {
|
}
|
||||||
enqueueCommand(playback.SessionCommand{Kind: playback.CommandRemoveAudio})
|
audioConfig := playback.FeedConfig{}
|
||||||
} else {
|
if audioActive {
|
||||||
enqueueCommand(playback.SessionCommand{
|
audioConfig = playback.FeedConfig{
|
||||||
Kind: playback.CommandSetAudio,
|
|
||||||
Config: playback.FeedConfig{
|
|
||||||
Domain: audioDomainStr,
|
Domain: audioDomainStr,
|
||||||
UUID: audioStr,
|
UUID: audioStr,
|
||||||
Active: true,
|
Active: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueCommand(playback.SessionCommand{
|
||||||
|
Kind: playback.CommandSetSession,
|
||||||
|
Session: playback.SessionConfig{
|
||||||
|
Video: videoConfig,
|
||||||
|
Audio: audioConfig,
|
||||||
|
SyncRequested: syncRequested,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
@@ -523,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 {
|
||||||
@@ -544,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 = ""
|
||||||
@@ -566,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
|
||||||
@@ -591,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 = ""
|
||||||
@@ -608,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)
|
||||||
|
case playback.TopologyIndependent:
|
||||||
|
if snapshot.Plan.Video.Active {
|
||||||
|
drawUnitStatus("Video", playback.UnitVideo)
|
||||||
}
|
}
|
||||||
if status.Err != nil {
|
if snapshot.Plan.Audio.Active {
|
||||||
cimgui.TextWrapped(status.Err.Error())
|
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()
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ import (
|
|||||||
"mxl-player/internal/playback"
|
"mxl-player/internal/playback"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type playerAudioSink interface {
|
||||||
|
playback.AudioSink
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ playerAudioSink = (*output.SDLAudioSink)(nil)
|
||||||
|
|
||||||
type playerPlayback struct {
|
type playerPlayback struct {
|
||||||
Controller *playback.SessionController
|
Controller *playback.SessionController
|
||||||
Commands chan playback.SessionCommand
|
Commands chan playback.SessionCommand
|
||||||
Video *playback.VideoBridge
|
Video *playback.VideoBridge
|
||||||
Status *playback.StatusStore
|
Status *playback.StatusStore
|
||||||
Audio *output.SDLAudioSink
|
Audio playerAudioSink
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPlayerPlayback(
|
func newPlayerPlayback(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Size=200,200
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Connection]
|
[Window][Connection]
|
||||||
Pos=322,387
|
Pos=322,130
|
||||||
Size=618,275
|
Size=661,444
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ const (
|
|||||||
CommandRemoveAudio
|
CommandRemoveAudio
|
||||||
CommandEnableSync
|
CommandEnableSync
|
||||||
CommandDisableSync
|
CommandDisableSync
|
||||||
|
CommandSetSession
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionCommand struct {
|
type SessionCommand struct {
|
||||||
Kind SessionCommandKind
|
Kind SessionCommandKind
|
||||||
Config FeedConfig // Used only by SetVideo and SetAudio.
|
Config FeedConfig // Used only by SetVideo and SetAudio.
|
||||||
|
Session SessionConfig // Used only by SetSession.
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -99,6 +101,12 @@ func ApplySessionCommand(
|
|||||||
case CommandDisableSync:
|
case CommandDisableSync:
|
||||||
next.SyncRequested = false
|
next.SyncRequested = false
|
||||||
|
|
||||||
|
case CommandSetSession:
|
||||||
|
next = command.Session
|
||||||
|
// Retry policy belongs to the running controller configuration, not to
|
||||||
|
// GUI or playlist session selections.
|
||||||
|
next.Retry = current.Retry
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return current, ErrUnknownSessionCommand
|
return current, ErrUnknownSessionCommand
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,27 @@ func TestApplySessionCommand(t *testing.T) {
|
|||||||
command: SessionCommand{Kind: CommandDisableSync},
|
command: SessionCommand{Kind: CommandDisableSync},
|
||||||
want: func() SessionConfig { c := base; c.SyncRequested = false; return c }(),
|
want: func() SessionConfig { c := base; c.SyncRequested = false; return c }(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "set complete session atomically and preserve retry",
|
||||||
|
current: base,
|
||||||
|
command: SessionCommand{
|
||||||
|
Kind: CommandSetSession,
|
||||||
|
Session: SessionConfig{
|
||||||
|
Video: newVideo,
|
||||||
|
Audio: newAudio,
|
||||||
|
SyncRequested: false,
|
||||||
|
Retry: RetryPolicy{
|
||||||
|
MaxAttempts: 99,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: SessionConfig{
|
||||||
|
Video: newVideo,
|
||||||
|
Audio: newAudio,
|
||||||
|
SyncRequested: false,
|
||||||
|
Retry: base.Retry,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -157,6 +178,17 @@ func TestApplySessionCommandFailurePreservesState(t *testing.T) {
|
|||||||
ErrAudioNotConfigured,
|
ErrAudioNotConfigured,
|
||||||
},
|
},
|
||||||
{"unknown command", base, SessionCommand{Kind: 255}, ErrUnknownSessionCommand},
|
{"unknown command", base, SessionCommand{Kind: 255}, ErrUnknownSessionCommand},
|
||||||
|
{
|
||||||
|
"invalid complete session",
|
||||||
|
base,
|
||||||
|
SessionCommand{
|
||||||
|
Kind: CommandSetSession,
|
||||||
|
Session: SessionConfig{
|
||||||
|
Video: FeedConfig{UUID: "video", Active: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ErrFeedDomainRequired,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -240,6 +240,39 @@ func TestSessionControllerUpdatesOnlyChangedIndependentSlot(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionControllerReplacesSynchronizedPairWithOneCommand(t *testing.T) {
|
||||||
|
events := make(chan controllerEvent, 32)
|
||||||
|
controller := newRecordingController(t, events)
|
||||||
|
initial := validCommandSession()
|
||||||
|
commands := make(chan SessionCommand)
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- controller.Run(context.Background(), initial, commands) }()
|
||||||
|
start := receiveControllerEvent(t, events)
|
||||||
|
if start.unit != UnitSync || start.action != "start" {
|
||||||
|
t.Fatalf("initial event = %+v, want sync start", start)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := initial
|
||||||
|
next.Video = FeedConfig{Domain: "/next", UUID: "next-video", Active: true}
|
||||||
|
next.Audio = FeedConfig{Domain: "/next", UUID: "next-audio", Active: true}
|
||||||
|
commands <- SessionCommand{Kind: CommandSetSession, Session: next}
|
||||||
|
event := receiveControllerEvent(t, events)
|
||||||
|
wantPair := SyncPairConfig{Video: next.Video, Audio: next.Audio}
|
||||||
|
if event.unit != UnitSync || event.action != "command" || event.pair != wantPair {
|
||||||
|
t.Fatalf("replacement event = %+v, want one sync command for %#v", event, wantPair)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case event := <-events:
|
||||||
|
t.Fatalf("atomic replacement emitted an extra event: %+v", event)
|
||||||
|
case <-time.After(20 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
close(commands)
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
t.Fatalf("Run() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionControllerStopsIndependentSlotsBeforeStartingSync(t *testing.T) {
|
func TestSessionControllerStopsIndependentSlotsBeforeStartingSync(t *testing.T) {
|
||||||
events := make(chan controllerEvent, 32)
|
events := make(chan controllerEvent, 32)
|
||||||
controller := newRecordingController(t, events)
|
controller := newRecordingController(t, events)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user