diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md index 5888fba..e637322 100644 --- a/REFACTORING_PLAN.md +++ b/REFACTORING_PLAN.md @@ -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,22 +256,24 @@ 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 } type Playlist struct { - Entries []PlaylistEntry - Loop bool + 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: diff --git a/cmd/mxl-player/playlist_runtime.go b/cmd/mxl-player/playlist_runtime.go index 9aa6ed0..95d0b17 100644 --- a/cmd/mxl-player/playlist_runtime.go +++ b/cmd/mxl-player/playlist_runtime.go @@ -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") @@ -18,10 +18,14 @@ var ( ) type playerPlaylist struct { - Controller *playback.PlaylistController - Coordinator *playback.PlaylistReadinessCoordinator - Commands chan playback.PlaylistCommand - Readiness chan playback.PlaylistReadiness + Controller *playback.PlaylistController + + // 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 diff --git a/cmd/mxl-player/playlist_runtime_test.go b/cmd/mxl-player/playlist_runtime_test.go index 19ebc69..faf276e 100644 --- a/cmd/mxl-player/playlist_runtime_test.go +++ b/cmd/mxl-player/playlist_runtime_test.go @@ -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") } diff --git a/imgui.ini b/imgui.ini index 9d5a103..d88865f 100644 --- a/imgui.ini +++ b/imgui.ini @@ -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] diff --git a/internal/playback/playlist_controller.go b/internal/playback/playlist_controller.go index 57e4261..0972ab7 100644 --- a/internal/playback/playlist_controller.go +++ b/internal/playback/playlist_controller.go @@ -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 { diff --git a/internal/playback/playlist_controller_timing_test.go b/internal/playback/playlist_controller_timing_test.go index 56e6ad7..cb9fe0f 100644 --- a/internal/playback/playlist_controller_timing_test.go +++ b/internal/playback/playlist_controller_timing_test.go @@ -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) }() diff --git a/internal/playback/playlist_readiness.go b/internal/playback/playlist_event_coordinator.go similarity index 80% rename from internal/playback/playlist_readiness.go rename to internal/playback/playlist_event_coordinator.go index 6aabd2d..9f6ba90 100644 --- a/internal/playback/playlist_readiness.go +++ b/internal/playback/playlist_event_coordinator.go @@ -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") + 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") + 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() diff --git a/internal/playback/playlist_readiness_coordinator_test.go b/internal/playback/playlist_event_coordinator_test.go similarity index 80% rename from internal/playback/playlist_readiness_coordinator_test.go rename to internal/playback/playlist_event_coordinator_test.go index 9620560..877cc43 100644 --- a/internal/playback/playlist_readiness_coordinator_test.go +++ b/internal/playback/playlist_event_coordinator_test.go @@ -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: diff --git a/internal/playback/playlist_failure_test.go b/internal/playback/playlist_failure_test.go index 6d1f92c..975ec65 100644 --- a/internal/playback/playlist_failure_test.go +++ b/internal/playback/playlist_failure_test.go @@ -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) }() diff --git a/internal/playback/playlist_pipeline_test.go b/internal/playback/playlist_pipeline_test.go index db5ca03..9147e63 100644 --- a/internal/playback/playlist_pipeline_test.go +++ b/internal/playback/playlist_pipeline_test.go @@ -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)