explicit info in GUI for playlist
This commit is contained in:
@@ -811,6 +811,33 @@ func main() {
|
||||
} else {
|
||||
cimgui.Text("End behavior: stop")
|
||||
}
|
||||
cimgui.Text(fmt.Sprintf(
|
||||
"Failure behavior: %s",
|
||||
configuredPlaylist.OnFailure,
|
||||
))
|
||||
|
||||
if hasPlaylistSnapshot && playlistSnapshot.HasFailure {
|
||||
failure := playlistSnapshot.Failure
|
||||
name := failure.EntryName
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("Entry %d", failure.EntryIndex+1)
|
||||
}
|
||||
cimgui.SeparatorText("Last failure")
|
||||
cimgui.TextWrapped(fmt.Sprintf(
|
||||
"%s: %s failed",
|
||||
name,
|
||||
failure.Status.Unit,
|
||||
))
|
||||
cimgui.Text(fmt.Sprintf(
|
||||
"Attempts: %d | failed attempts: %d",
|
||||
failure.Status.Attempt,
|
||||
failure.Status.FailedAttempts,
|
||||
))
|
||||
cimgui.Text(fmt.Sprintf("Policy: %s", failure.Policy))
|
||||
if failure.Status.Err != nil {
|
||||
cimgui.TextWrapped(failure.Status.Err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
preview := "No entry selected"
|
||||
if hasPlaylistSnapshot && playlistSnapshot.State.HasSelection {
|
||||
|
||||
@@ -4,8 +4,8 @@ Size=400,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][Settings & Info]
|
||||
Pos=580,0
|
||||
Size=700,720
|
||||
Pos=1220,0
|
||||
Size=700,1080
|
||||
Collapsed=0
|
||||
|
||||
[Window][Stats]
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
type PlaylistEvent struct {
|
||||
Revision uint64
|
||||
Kind PlaylistEventKind
|
||||
Failure Status
|
||||
}
|
||||
|
||||
// PlaylistReadiness is retained as an alias for callers that only publish
|
||||
@@ -47,13 +48,24 @@ type PlaylistController struct {
|
||||
mu sync.RWMutex
|
||||
snapshot PlaylistSnapshot
|
||||
hasSnapshot bool
|
||||
failure PlaylistFailure
|
||||
}
|
||||
|
||||
type PlaylistFailure struct {
|
||||
EntryIndex int
|
||||
EntryName string
|
||||
Revision uint64
|
||||
Policy PlaylistFailurePolicy
|
||||
Status Status
|
||||
}
|
||||
|
||||
type PlaylistSnapshot struct {
|
||||
State PlaylistState
|
||||
Entry PlaylistEntry
|
||||
Revision uint64
|
||||
Timing PlaylistTimingState
|
||||
State PlaylistState
|
||||
Entry PlaylistEntry
|
||||
Revision uint64
|
||||
Timing PlaylistTimingState
|
||||
Failure PlaylistFailure
|
||||
HasFailure bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -155,6 +167,7 @@ func (c *PlaylistController) Run(
|
||||
continue
|
||||
}
|
||||
if apply {
|
||||
c.clearFailure()
|
||||
stopTimer()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -179,6 +192,13 @@ func (c *PlaylistController) Run(
|
||||
continue
|
||||
}
|
||||
stopTimer()
|
||||
c.setFailure(PlaylistFailure{
|
||||
EntryIndex: state.CurrentIndex,
|
||||
EntryName: c.playlist.Entries[state.CurrentIndex].Name,
|
||||
Revision: revision,
|
||||
Policy: c.playlist.OnFailure,
|
||||
Status: ready.Failure,
|
||||
})
|
||||
// A failed entry must not retain a live or apparently active
|
||||
// duration clock, even when the policy is to wait.
|
||||
timing = NewPlaylistTiming(revision, timing.Duration)
|
||||
@@ -285,15 +305,29 @@ func (c *PlaylistController) publish(
|
||||
|
||||
c.mu.Lock()
|
||||
c.snapshot = PlaylistSnapshot{
|
||||
State: state,
|
||||
Entry: entry,
|
||||
Revision: revision,
|
||||
Timing: timing,
|
||||
State: state,
|
||||
Entry: entry,
|
||||
Revision: revision,
|
||||
Timing: timing,
|
||||
Failure: c.failure,
|
||||
HasFailure: c.failure.Revision != 0,
|
||||
}
|
||||
c.hasSnapshot = true
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *PlaylistController) setFailure(failure PlaylistFailure) {
|
||||
c.mu.Lock()
|
||||
c.failure = failure
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *PlaylistController) clearFailure() {
|
||||
c.mu.Lock()
|
||||
c.failure = PlaylistFailure{}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func stopPlaylistTimer(timer playlistTimer) {
|
||||
if timer == nil || timer.Stop() {
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package playback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -152,7 +153,15 @@ func TestPlaylistControllerFailurePolicy(t *testing.T) {
|
||||
|
||||
commands <- PlaylistCommand{Kind: PlaylistSelect, Index: 0}
|
||||
<-sessions
|
||||
events <- PlaylistEvent{Revision: 1, Kind: PlaylistEventFailed}
|
||||
failureErr := errors.New("flow unavailable")
|
||||
events <- PlaylistEvent{
|
||||
Revision: 1,
|
||||
Kind: PlaylistEventFailed,
|
||||
Failure: Status{
|
||||
Unit: UnitVideo, State: StateFailed,
|
||||
Attempt: 2, FailedAttempts: 2, Err: failureErr,
|
||||
},
|
||||
}
|
||||
|
||||
if test.wantAdvance {
|
||||
select {
|
||||
@@ -160,16 +169,23 @@ func TestPlaylistControllerFailurePolicy(t *testing.T) {
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("failure did not advance playlist")
|
||||
}
|
||||
snapshot, _ := controller.Snapshot()
|
||||
snapshot := waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
|
||||
return snapshot.State.CurrentIndex == 1 && snapshot.Revision == 2
|
||||
})
|
||||
if snapshot.State.CurrentIndex != 1 || snapshot.Revision != 2 {
|
||||
t.Fatalf("snapshot = %#v, want index 1 revision 2", snapshot)
|
||||
}
|
||||
assertPlaylistFailure(t, snapshot, failureErr)
|
||||
} else {
|
||||
select {
|
||||
case command := <-sessions:
|
||||
t.Fatalf("unexpected session command: %#v", command)
|
||||
case <-time.After(20 * time.Millisecond):
|
||||
}
|
||||
snapshot := waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
|
||||
return snapshot.HasFailure
|
||||
})
|
||||
assertPlaylistFailure(t, snapshot, failureErr)
|
||||
}
|
||||
|
||||
cancel()
|
||||
@@ -179,3 +195,16 @@ func TestPlaylistControllerFailurePolicy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertPlaylistFailure(t *testing.T, snapshot PlaylistSnapshot, wantErr error) {
|
||||
t.Helper()
|
||||
if !snapshot.HasFailure {
|
||||
t.Fatal("snapshot has no playlist failure")
|
||||
}
|
||||
failure := snapshot.Failure
|
||||
if failure.EntryIndex != 0 || failure.EntryName != "first" ||
|
||||
failure.Status.Unit != UnitVideo || failure.Status.Attempt != 2 ||
|
||||
failure.Status.FailedAttempts != 2 || !errors.Is(failure.Status.Err, wantErr) {
|
||||
t.Fatalf("failure = %#v", failure)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +112,14 @@ func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
|
||||
continue
|
||||
}
|
||||
statuses := c.statuses.SnapshotAll()
|
||||
if IsSessionFailed(sessionSnapshot, statuses) {
|
||||
if failure, failed := SessionFailureStatus(sessionSnapshot, statuses); failed {
|
||||
if playlistSnapshot.Revision == emittedFailedRevision {
|
||||
continue
|
||||
}
|
||||
failed := PlaylistEvent{
|
||||
Revision: playlistSnapshot.Revision,
|
||||
Kind: PlaylistEventFailed,
|
||||
Failure: failure,
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -217,24 +218,39 @@ func IsSessionFailed(
|
||||
session SessionSnapshot,
|
||||
statuses PlaybackStatusSnapshot,
|
||||
) bool {
|
||||
_, failed := SessionFailureStatus(session, statuses)
|
||||
return failed
|
||||
}
|
||||
|
||||
func SessionFailureStatus(
|
||||
session SessionSnapshot,
|
||||
statuses PlaybackStatusSnapshot,
|
||||
) (Status, bool) {
|
||||
if statuses.Generation != session.Generation {
|
||||
return false
|
||||
return Status{}, false
|
||||
}
|
||||
|
||||
switch session.Plan.Topology {
|
||||
case TopologyIndependent:
|
||||
return (session.Plan.Video.Active && statusIsFailed(
|
||||
if session.Plan.Video.Active && statusIsFailed(
|
||||
statuses.Video, statuses.HasVideo, session.Generation, session.Plan.Video,
|
||||
)) || (session.Plan.Audio.Active && statusIsFailed(
|
||||
) {
|
||||
return statuses.Video, true
|
||||
}
|
||||
if session.Plan.Audio.Active && statusIsFailed(
|
||||
statuses.Audio, statuses.HasAudio, session.Generation, session.Plan.Audio,
|
||||
))
|
||||
) {
|
||||
return statuses.Audio, true
|
||||
}
|
||||
return Status{}, false
|
||||
case TopologySynchronized:
|
||||
return statuses.HasSync &&
|
||||
failed := statuses.HasSync &&
|
||||
statuses.Sync.Generation == session.Generation &&
|
||||
statuses.Sync.State == StateFailed &&
|
||||
sameSyncSource(statuses.Sync.Pair, session.Plan.Sync)
|
||||
return statuses.Sync, failed
|
||||
default:
|
||||
return false
|
||||
return Status{}, false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user