architecture cleanup

This commit is contained in:
Dmitry Sergeev
2026-09-02 00:08:53 +03:00
parent f3f53fc7ad
commit e7b032ce77
10 changed files with 141 additions and 122 deletions
+22 -8
View File
@@ -11,8 +11,20 @@ at any time. Synchronization is a runtime relationship between the slots, not a
startup mode.
The design must leave a clean extension point for an `mxlfabrics` reader after
the local MXL player is stable. It must also support a later playlist layer
without moving playlist timing or selection into media readers.
the local MXL player is stable. Playlist timing and selection must remain above
media readers and playback workers.
## Current status
- Stages 0 through 11 are complete.
- The reader-factory extension point from Stage 12 is complete; the
`mxlfabrics` implementation remains future work.
- Stage 13 is complete, including manual/timed navigation, looping,
pause/resume, failure policies, playlist-level retry configuration, and GUI
diagnostics.
- Playlist lifecycle messages are explicit `PlaylistEvent` values produced by
`PlaylistEventCoordinator`; runtime cancellation owns shutdown, so shared
event channels are not closed by either endpoint.
## Required behaviour
@@ -61,7 +73,7 @@ without moving playlist timing or selection into media readers.
- Stopping both members of a synchronized group closes the group atomically.
- Stop commands cancel active reads and retry backoff promptly.
### Future playlist behaviour
### Playlist behaviour
A playlist is an ordered list of playback entries. Each entry describes a
complete desired session state and may contain:
@@ -244,15 +256,15 @@ transition.
4. Start independent workers for both configured slots.
5. Let either worker begin playing without waiting for the other.
## Future playlist model
## Playlist model
The exact public types can be chosen later, but the intended model is:
The implemented model is conceptually:
```go
type PlaylistEntry struct {
Name string
VideoUUID string
AudioUUID string
Video PlaylistFeed // domain + UUID, optional
Audio PlaylistFeed // domain + UUID, optional
SyncRequested bool
Duration time.Duration
}
@@ -260,6 +272,8 @@ type PlaylistEntry struct {
type Playlist struct {
Entries []PlaylistEntry
Loop bool
OnFailure PlaylistFailurePolicy // wait or next
Retry *RetryPolicy // optional playlist-wide override
}
```
@@ -505,7 +519,7 @@ Acceptance criteria:
- Fake readers can drive all controller and supervisor tests.
- Local MXL remains the reference implementation.
### Stage 13 — Simple playlist
### Stage 13 — Simple playlist (complete)
Work:
+18 -14
View File
@@ -9,7 +9,7 @@ import (
"mxl-player/internal/playback"
)
const playlistReadinessInterval = 10 * time.Millisecond
const playlistEventInterval = 10 * time.Millisecond
var (
ErrPlayerPlaybackRequired = errors.New("player playback is required")
@@ -19,9 +19,13 @@ var (
type playerPlaylist struct {
Controller *playback.PlaylistController
Coordinator *playback.PlaylistReadinessCoordinator
Commands chan playback.PlaylistCommand
Readiness chan playback.PlaylistReadiness
// commands is written by GUI-facing methods and consumed by Controller.
// events is written by coordinator and consumed by Controller. Run owns
// both goroutine lifecycles; cancellation replaces channel closing.
coordinator *playback.PlaylistEventCoordinator
commands chan playback.PlaylistCommand
events chan playback.PlaylistEvent
}
func newPlayerPlaylist(
@@ -40,7 +44,7 @@ func newPlayerPlaylist(
}
commands := make(chan playback.PlaylistCommand, 32)
readiness := make(chan playback.PlaylistReadiness, 8)
events := make(chan playback.PlaylistEvent, 8)
controller, err := playback.NewPlaylistController(
playlist,
retry,
@@ -49,12 +53,12 @@ func newPlayerPlaylist(
if err != nil {
return nil, err
}
coordinator, err := playback.NewPlaylistReadinessCoordinator(
coordinator, err := playback.NewPlaylistEventCoordinator(
controller,
player.Controller,
player.Status,
readiness,
playlistReadinessInterval,
events,
playlistEventInterval,
)
if err != nil {
return nil, err
@@ -62,9 +66,9 @@ func newPlayerPlaylist(
return &playerPlaylist{
Controller: controller,
Coordinator: coordinator,
Commands: commands,
Readiness: readiness,
coordinator: coordinator,
commands: commands,
events: events,
}, nil
}
@@ -74,10 +78,10 @@ func (p *playerPlaylist) Run(ctx context.Context) error {
results := make(chan error, 2)
go func() {
results <- p.Controller.Run(runCtx, p.Commands, p.Readiness)
results <- p.Controller.Run(runCtx, p.commands, p.events)
}()
go func() {
results <- p.Coordinator.Run(runCtx)
results <- p.coordinator.Run(runCtx)
}()
first := <-results
@@ -121,7 +125,7 @@ func (p *playerPlaylist) Resume() bool {
func (p *playerPlaylist) enqueue(command playback.PlaylistCommand) bool {
select {
case p.Commands <- command:
case p.commands <- command:
return true
default:
return false
+4 -4
View File
@@ -104,10 +104,10 @@ func TestNewPlayerPlaylistWiresComponents(t *testing.T) {
if err != nil {
t.Fatalf("newPlayerPlaylist() error = %v", err)
}
if runtime.Controller == nil || runtime.Coordinator == nil {
if runtime.Controller == nil || runtime.coordinator == nil {
t.Fatalf("runtime components = %#v", runtime)
}
if runtime.Commands == nil || runtime.Readiness == nil {
if runtime.commands == nil || runtime.events == nil {
t.Fatalf("runtime channels = %#v", runtime)
}
}
@@ -135,7 +135,7 @@ func TestPlayerPlaylistNavigationHelpers(t *testing.T) {
if !test.send() {
t.Fatal("navigation helper returned false")
}
if got := <-runtime.Commands; got != test.want {
if got := <-runtime.commands; got != test.want {
t.Fatalf("navigation command = %#v, want %#v", got, test.want)
}
}
@@ -150,7 +150,7 @@ func TestPlayerPlaylistNavigationQueueFull(t *testing.T) {
if err != nil {
t.Fatalf("newPlayerPlaylist() error = %v", err)
}
for range cap(runtime.Commands) {
for range cap(runtime.commands) {
if !runtime.Next() {
t.Fatal("queue filled before reaching capacity")
}
+2 -2
View File
@@ -4,8 +4,8 @@ Size=400,400
Collapsed=0
[Window][Settings & Info]
Pos=1220,0
Size=700,1080
Pos=580,0
Size=700,720
Collapsed=0
[Window][Stats]
+11 -12
View File
@@ -20,10 +20,6 @@ type PlaylistEvent struct {
Failure Status
}
// PlaylistReadiness is retained as an alias for callers that only publish
// ready events. Its zero Kind is PlaylistEventReady.
type PlaylistReadiness = PlaylistEvent
type playlistTimer interface {
C() <-chan time.Time
Stop() bool
@@ -100,7 +96,7 @@ func NewPlaylistController(
func (c *PlaylistController) Run(
ctx context.Context,
commands <-chan PlaylistCommand,
readiness <-chan PlaylistReadiness,
events <-chan PlaylistEvent,
) error {
state := PlaylistState{}
revision := uint64(0)
@@ -182,13 +178,13 @@ func (c *PlaylistController) Run(
state = next
c.publish(state, revision, timing)
case ready, ok := <-readiness:
case event, ok := <-events:
if !ok {
readiness = nil
events = nil
continue
}
if ready.Kind == PlaylistEventFailed {
if ready.Revision != revision {
if event.Kind == PlaylistEventFailed {
if event.Revision != revision {
continue
}
stopTimer()
@@ -197,7 +193,7 @@ func (c *PlaylistController) Run(
EntryName: c.playlist.Entries[state.CurrentIndex].Name,
Revision: revision,
Policy: c.playlist.OnFailure,
Status: ready.Failure,
Status: event.Failure,
})
// A failed entry must not retain a live or apparently active
// duration clock, even when the policy is to wait.
@@ -228,8 +224,11 @@ func (c *PlaylistController) Run(
c.publish(state, revision, timing)
continue
}
if event.Kind != PlaylistEventReady {
continue
}
if timing.Paused &&
ready.Revision == timing.Revision &&
event.Revision == timing.Revision &&
timing.Duration > 0 &&
!timing.Expired {
timing.Ready = true
@@ -238,7 +237,7 @@ func (c *PlaylistController) Run(
}
nextTiming, started := StartPlaylistTiming(
timing,
ready.Revision,
event.Revision,
c.now(),
)
if !started {
@@ -68,9 +68,11 @@ func TestPlaylistControllerStartsOneTimerForMatchingReadiness(t *testing.T) {
return snapshot.Revision == 1
})
readiness <- PlaylistReadiness{Revision: 0}
readiness <- PlaylistEvent{Revision: 0}
assertNoPlaylistTimer(t, timers)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1, Kind: PlaylistEventKind(99)}
assertNoPlaylistTimer(t, timers)
readiness <- PlaylistEvent{Revision: 1}
timer := receiveFakePlaylistTimer(t, timers)
snapshot := waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
@@ -79,7 +81,7 @@ func TestPlaylistControllerStartsOneTimerForMatchingReadiness(t *testing.T) {
if snapshot.Timing.Deadline != now.Add(10*time.Second) {
t.Fatalf("deadline = %v, want %v", snapshot.Timing.Deadline, now.Add(10*time.Second))
}
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
assertNoPlaylistTimer(t, timers)
if timer.isStopped() {
t.Fatal("timer stopped after duplicate readiness")
@@ -101,7 +103,7 @@ func TestPlaylistControllerTimerExpiryAdvances(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistNext}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
timer := receiveFakePlaylistTimer(t, timers)
timer.fire(now.Add(10 * time.Second))
@@ -132,7 +134,7 @@ func TestPlaylistControllerLoopingTimerExpiryWraps(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistSelect, Index: 1}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
timer := receiveFakePlaylistTimer(t, timers)
waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
return snapshot.Timing.Started
@@ -160,7 +162,7 @@ func TestPlaylistControllerFinalExpiryStopsWithoutCommand(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistSelect, Index: 1}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
timer := receiveFakePlaylistTimer(t, timers)
waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
return snapshot.Timing.Started
@@ -192,7 +194,7 @@ func TestPlaylistControllerManualSelectionStopsOldTimer(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistNext}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
oldTimer := receiveFakePlaylistTimer(t, timers)
commands <- PlaylistCommand{Kind: PlaylistSelect, Index: 1}
@@ -229,7 +231,7 @@ func TestPlaylistControllerZeroDurationDoesNotCreateTimer(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistNext}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
assertNoPlaylistTimer(t, timers)
close(commands)
@@ -244,7 +246,7 @@ func TestPlaylistControllerCancellationStopsTimer(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistNext}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
timer := receiveFakePlaylistTimer(t, timers)
cancel()
if err := waitForPlaylistResult(t, result); !errors.Is(err, context.Canceled) {
@@ -262,7 +264,7 @@ func TestPlaylistControllerPauseAndResumeTimer(t *testing.T) {
commands <- PlaylistCommand{Kind: PlaylistNext}
_ = receivePlaylistSession(t, sessions)
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
oldTimer := receiveFakePlaylistTimer(t, timers)
waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
return snapshot.Timing.Started
@@ -344,7 +346,7 @@ func TestPlaylistControllerRecordsQueuedReadinessWhilePaused(t *testing.T) {
return snapshot.Timing.Paused
})
readiness <- PlaylistReadiness{Revision: 1}
readiness <- PlaylistEvent{Revision: 1}
waitForPlaylistSnapshot(t, controller, func(snapshot PlaylistSnapshot) bool {
return snapshot.Timing.Paused && snapshot.Timing.Ready
})
@@ -368,7 +370,7 @@ func startTimedPlaylistController(
) (
*PlaylistController,
chan PlaylistCommand,
chan PlaylistReadiness,
chan PlaylistEvent,
chan SessionCommand,
chan *fakePlaylistTimer,
time.Time,
@@ -390,7 +392,7 @@ func startTimedPlaylistController(
return timer
}
commands := make(chan PlaylistCommand, 16)
readiness := make(chan PlaylistReadiness, 16)
readiness := make(chan PlaylistEvent, 16)
ctx, cancel := context.WithCancel(context.Background())
result := make(chan error, 1)
go func() { result <- controller.Run(ctx, commands, readiness) }()
@@ -18,45 +18,45 @@ type PlaybackStatusSnapshotSource interface {
SnapshotAll() PlaybackStatusSnapshot
}
type playlistReadinessTicker interface {
type playlistEventTicker interface {
C() <-chan time.Time
Stop()
}
type playlistReadinessTickerFactory func(time.Duration) playlistReadinessTicker
type playlistEventTickerFactory func(time.Duration) playlistEventTicker
type realPlaylistReadinessTicker struct {
type realPlaylistEventTicker struct {
ticker *time.Ticker
}
func (t realPlaylistReadinessTicker) C() <-chan time.Time { return t.ticker.C }
func (t realPlaylistReadinessTicker) Stop() { t.ticker.Stop() }
func (t realPlaylistEventTicker) C() <-chan time.Time { return t.ticker.C }
func (t realPlaylistEventTicker) Stop() { t.ticker.Stop() }
var (
ErrPlaylistSnapshotSourceRequired = errors.New("playlist snapshot source is required")
ErrSessionSnapshotSourceRequired = errors.New("session snapshot source is required")
ErrStatusSnapshotSourceRequired = errors.New("playback status snapshot source is required")
ErrPlaylistReadinessOutputRequired = errors.New("playlist readiness output channel is required")
ErrPlaylistReadinessInterval = errors.New("playlist readiness interval must be positive")
ErrPlaylistEventOutputRequired = errors.New("playlist event output channel is required")
ErrPlaylistEventInterval = errors.New("playlist event interval must be positive")
)
type PlaylistReadinessCoordinator struct {
type PlaylistEventCoordinator struct {
playlist PlaylistSnapshotSource
session SessionSnapshotSource
statuses PlaybackStatusSnapshotSource
output chan<- PlaylistReadiness
output chan<- PlaylistEvent
interval time.Duration
newTicker playlistReadinessTickerFactory
newTicker playlistEventTickerFactory
}
func NewPlaylistReadinessCoordinator(
func NewPlaylistEventCoordinator(
playlist PlaylistSnapshotSource,
session SessionSnapshotSource,
statuses PlaybackStatusSnapshotSource,
output chan<- PlaylistReadiness,
output chan<- PlaylistEvent,
interval time.Duration,
) (*PlaylistReadinessCoordinator, error) {
) (*PlaylistEventCoordinator, error) {
if playlist == nil {
return nil, ErrPlaylistSnapshotSourceRequired
}
@@ -67,25 +67,25 @@ func NewPlaylistReadinessCoordinator(
return nil, ErrStatusSnapshotSourceRequired
}
if output == nil {
return nil, ErrPlaylistReadinessOutputRequired
return nil, ErrPlaylistEventOutputRequired
}
if interval <= 0 {
return nil, ErrPlaylistReadinessInterval
return nil, ErrPlaylistEventInterval
}
return &PlaylistReadinessCoordinator{
return &PlaylistEventCoordinator{
playlist: playlist,
session: session,
statuses: statuses,
output: output,
interval: interval,
newTicker: func(interval time.Duration) playlistReadinessTicker {
return realPlaylistReadinessTicker{ticker: time.NewTicker(interval)}
newTicker: func(interval time.Duration) playlistEventTicker {
return realPlaylistEventTicker{ticker: time.NewTicker(interval)}
},
}, nil
}
func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
func (c *PlaylistEventCoordinator) Run(ctx context.Context) error {
ticker := c.newTicker(c.interval)
defer ticker.Stop()
@@ -139,7 +139,7 @@ func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
continue
}
ready := PlaylistReadiness{Revision: playlistSnapshot.Revision}
ready := PlaylistEvent{Revision: playlistSnapshot.Revision}
select {
case <-ctx.Done():
return ctx.Err()
@@ -63,55 +63,55 @@ func (s *fakePlaybackStatusSnapshotSource) set(snapshot PlaybackStatusSnapshot)
s.mu.Unlock()
}
type fakePlaylistReadinessTicker struct {
type fakePlaylistEventTicker struct {
ch chan time.Time
mu sync.Mutex
stopped bool
}
func newFakePlaylistReadinessTicker() *fakePlaylistReadinessTicker {
return &fakePlaylistReadinessTicker{ch: make(chan time.Time, 16)}
func newFakePlaylistEventTicker() *fakePlaylistEventTicker {
return &fakePlaylistEventTicker{ch: make(chan time.Time, 16)}
}
func (t *fakePlaylistReadinessTicker) C() <-chan time.Time { return t.ch }
func (t *fakePlaylistReadinessTicker) Stop() {
func (t *fakePlaylistEventTicker) C() <-chan time.Time { return t.ch }
func (t *fakePlaylistEventTicker) Stop() {
t.mu.Lock()
t.stopped = true
t.mu.Unlock()
}
func (t *fakePlaylistReadinessTicker) tick() { t.ch <- time.Now() }
func (t *fakePlaylistReadinessTicker) isStopped() bool {
func (t *fakePlaylistEventTicker) tick() { t.ch <- time.Now() }
func (t *fakePlaylistEventTicker) isStopped() bool {
t.mu.Lock()
defer t.mu.Unlock()
return t.stopped
}
func TestNewPlaylistReadinessCoordinatorValidatesDependencies(t *testing.T) {
func TestNewPlaylistEventCoordinatorValidatesDependencies(t *testing.T) {
playlist := &fakePlaylistSnapshotSource{}
session := &fakeSessionSnapshotSource{}
statuses := &fakePlaybackStatusSnapshotSource{}
output := make(chan PlaylistReadiness)
output := make(chan PlaylistEvent)
tests := []struct {
name string
playlist PlaylistSnapshotSource
session SessionSnapshotSource
statuses PlaybackStatusSnapshotSource
output chan<- PlaylistReadiness
output chan<- PlaylistEvent
interval time.Duration
wantErr error
}{
{name: "playlist", session: session, statuses: statuses, output: output, interval: time.Millisecond, wantErr: ErrPlaylistSnapshotSourceRequired},
{name: "session", playlist: playlist, statuses: statuses, output: output, interval: time.Millisecond, wantErr: ErrSessionSnapshotSourceRequired},
{name: "statuses", playlist: playlist, session: session, output: output, interval: time.Millisecond, wantErr: ErrStatusSnapshotSourceRequired},
{name: "output", playlist: playlist, session: session, statuses: statuses, interval: time.Millisecond, wantErr: ErrPlaylistReadinessOutputRequired},
{name: "interval", playlist: playlist, session: session, statuses: statuses, output: output, wantErr: ErrPlaylistReadinessInterval},
{name: "output", playlist: playlist, session: session, statuses: statuses, interval: time.Millisecond, wantErr: ErrPlaylistEventOutputRequired},
{name: "interval", playlist: playlist, session: session, statuses: statuses, output: output, wantErr: ErrPlaylistEventInterval},
{name: "valid", playlist: playlist, session: session, statuses: statuses, output: output, interval: time.Millisecond},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
coordinator, err := NewPlaylistReadinessCoordinator(
coordinator, err := NewPlaylistEventCoordinator(
test.playlist,
test.session,
test.statuses,
@@ -119,7 +119,7 @@ func TestNewPlaylistReadinessCoordinatorValidatesDependencies(t *testing.T) {
test.interval,
)
if !errors.Is(err, test.wantErr) {
t.Fatalf("NewPlaylistReadinessCoordinator() error = %v, want %v", err, test.wantErr)
t.Fatalf("NewPlaylistEventCoordinator() error = %v, want %v", err, test.wantErr)
}
if test.wantErr != nil && coordinator != nil {
t.Fatalf("coordinator = %#v, want nil", coordinator)
@@ -174,10 +174,10 @@ func TestPlaylistEntryMatchesSession(t *testing.T) {
}
}
func TestPlaylistReadinessCoordinatorEmitsOncePerRevision(t *testing.T) {
func TestPlaylistEventCoordinatorEmitsOncePerRevision(t *testing.T) {
playlist, session, statuses := readyVideoSnapshots(1)
output := make(chan PlaylistReadiness, 4)
coordinator, ticker, cancel, result := startReadinessCoordinator(
output := make(chan PlaylistEvent, 4)
coordinator, ticker, cancel, result := startEventCoordinator(
t,
playlist,
session,
@@ -188,16 +188,16 @@ func TestPlaylistReadinessCoordinatorEmitsOncePerRevision(t *testing.T) {
defer cancel()
ticker.tick()
if got := receivePlaylistReadiness(t, output); got.Revision != 1 {
if got := receivePlaylistEvent(t, output); got.Revision != 1 {
t.Fatalf("readiness revision = %d, want 1", got.Revision)
}
ticker.tick()
assertNoPlaylistReadiness(t, output)
assertNoPlaylistEvent(t, output)
next := playlistSnapshotForVideo(2)
playlist.set(next, true)
ticker.tick()
if got := receivePlaylistReadiness(t, output); got.Revision != 2 {
if got := receivePlaylistEvent(t, output); got.Revision != 2 {
t.Fatalf("readiness revision = %d, want 2", got.Revision)
}
@@ -210,10 +210,10 @@ func TestPlaylistReadinessCoordinatorEmitsOncePerRevision(t *testing.T) {
}
}
func TestPlaylistReadinessCoordinatorWaitsForAllConditions(t *testing.T) {
func TestPlaylistEventCoordinatorWaitsForAllConditions(t *testing.T) {
playlist, session, statuses := readyVideoSnapshots(1)
output := make(chan PlaylistReadiness, 1)
_, ticker, cancel, result := startReadinessCoordinator(t, playlist, session, statuses, output)
output := make(chan PlaylistEvent, 1)
_, ticker, cancel, result := startEventCoordinator(t, playlist, session, statuses, output)
defer cancel()
tests := []struct {
@@ -256,7 +256,7 @@ func TestPlaylistReadinessCoordinatorWaitsForAllConditions(t *testing.T) {
statuses.set(validStatuses.snapshot)
test.mutate()
ticker.tick()
assertNoPlaylistReadiness(t, output)
assertNoPlaylistEvent(t, output)
})
}
@@ -264,10 +264,10 @@ func TestPlaylistReadinessCoordinatorWaitsForAllConditions(t *testing.T) {
_ = waitForPlaylistResult(t, result)
}
func TestPlaylistReadinessCoordinatorCancellationWhileBlockedSending(t *testing.T) {
func TestPlaylistEventCoordinatorCancellationWhileBlockedSending(t *testing.T) {
playlist, session, statuses := readyVideoSnapshots(1)
output := make(chan PlaylistReadiness)
_, ticker, cancel, result := startReadinessCoordinator(t, playlist, session, statuses, output)
output := make(chan PlaylistEvent)
_, ticker, cancel, result := startEventCoordinator(t, playlist, session, statuses, output)
ticker.tick()
time.Sleep(time.Millisecond)
@@ -322,15 +322,15 @@ func playlistSnapshotForVideo(revision uint64) PlaylistSnapshot {
}
}
func startReadinessCoordinator(
func startEventCoordinator(
t *testing.T,
playlist PlaylistSnapshotSource,
session SessionSnapshotSource,
statuses PlaybackStatusSnapshotSource,
output chan<- PlaylistReadiness,
) (*PlaylistReadinessCoordinator, *fakePlaylistReadinessTicker, context.CancelFunc, <-chan error) {
output chan<- PlaylistEvent,
) (*PlaylistEventCoordinator, *fakePlaylistEventTicker, context.CancelFunc, <-chan error) {
t.Helper()
coordinator, err := NewPlaylistReadinessCoordinator(
coordinator, err := NewPlaylistEventCoordinator(
playlist,
session,
statuses,
@@ -338,28 +338,28 @@ func startReadinessCoordinator(
time.Millisecond,
)
if err != nil {
t.Fatalf("NewPlaylistReadinessCoordinator() error = %v", err)
t.Fatalf("NewPlaylistEventCoordinator() error = %v", err)
}
ticker := newFakePlaylistReadinessTicker()
coordinator.newTicker = func(time.Duration) playlistReadinessTicker { return ticker }
ticker := newFakePlaylistEventTicker()
coordinator.newTicker = func(time.Duration) playlistEventTicker { return ticker }
ctx, cancel := context.WithCancel(context.Background())
result := make(chan error, 1)
go func() { result <- coordinator.Run(ctx) }()
return coordinator, ticker, cancel, result
}
func receivePlaylistReadiness(t *testing.T, output <-chan PlaylistReadiness) PlaylistReadiness {
func receivePlaylistEvent(t *testing.T, output <-chan PlaylistEvent) PlaylistEvent {
t.Helper()
select {
case readiness := <-output:
return readiness
case <-time.After(time.Second):
t.Fatal("timed out waiting for playlist readiness")
return PlaylistReadiness{}
return PlaylistEvent{}
}
}
func assertNoPlaylistReadiness(t *testing.T, output <-chan PlaylistReadiness) {
func assertNoPlaylistEvent(t *testing.T, output <-chan PlaylistEvent) {
t.Helper()
select {
case readiness := <-output:
+1 -1
View File
@@ -146,7 +146,7 @@ func TestPlaylistControllerFailurePolicy(t *testing.T) {
t.Fatalf("NewPlaylistController() error = %v", err)
}
commands := make(chan PlaylistCommand, 2)
events := make(chan PlaylistReadiness, 2)
events := make(chan PlaylistEvent, 2)
ctx, cancel := context.WithCancel(context.Background())
result := make(chan error, 1)
go func() { result <- controller.Run(ctx, commands, events) }()
+5 -5
View File
@@ -116,7 +116,7 @@ type playlistPipelineHarness struct {
sessions chan SessionCommand
session *fakeSessionSnapshotSource
statuses *fakePlaybackStatusSnapshotSource
ticker *fakePlaylistReadinessTicker
ticker *fakePlaylistEventTicker
cancel context.CancelFunc
results chan error
}
@@ -128,17 +128,17 @@ func startPlaylistPipeline(t *testing.T, playlist Playlist) *playlistPipelineHar
if err != nil {
t.Fatal(err)
}
events := make(chan PlaylistReadiness, 8)
events := make(chan PlaylistEvent, 8)
session := &fakeSessionSnapshotSource{}
statuses := &fakePlaybackStatusSnapshotSource{}
coordinator, err := NewPlaylistReadinessCoordinator(
coordinator, err := NewPlaylistEventCoordinator(
controller, session, statuses, events, time.Millisecond,
)
if err != nil {
t.Fatal(err)
}
ticker := newFakePlaylistReadinessTicker()
coordinator.newTicker = func(time.Duration) playlistReadinessTicker { return ticker }
ticker := newFakePlaylistEventTicker()
coordinator.newTicker = func(time.Duration) playlistEventTicker { return ticker }
commands := make(chan PlaylistCommand, 8)
ctx, cancel := context.WithCancel(context.Background())
results := make(chan error, 2)