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