package playback import ( "context" "errors" "fmt" ) var ErrVideoWorkerRequired = errors.New("video worker is required") type VideoSlot struct { worker *VideoWorker } func NewVideoSlot(worker *VideoWorker) (*VideoSlot, error) { if worker == nil { return nil, ErrVideoWorkerRequired } return &VideoSlot{worker: worker}, nil } func (s *VideoSlot) Run( ctx context.Context, initial FeedConfig, commands <-chan FeedConfig, ) error { if err := initial.Validate(); err != nil { return fmt.Errorf("validate initial video config: %w", err) } var ( workerCancel context.CancelFunc workerDone chan error ) start := func(config FeedConfig) { workerCtx, cancel := context.WithCancel(ctx) done := make(chan error, 1) workerCancel = cancel workerDone = done go func() { done <- s.worker.Run(workerCtx, config) }() } stop := func() { if workerCancel == nil { return } workerCancel() <-workerDone workerCancel = nil workerDone = nil } if initial.Active { start(initial) } for { select { case <-ctx.Done(): stop() return ctx.Err() case config, ok := <-commands: if !ok { stop() return nil } if err := config.Validate(); err != nil { // Ignore invalid commands without disturbing the current worker. continue } stop() if config.Active { start(config) } case <-workerDone: // The worker stopped naturally or exhausted its retries. // Clear its lifecycle, but keep the slot alive for future commands. workerCancel() workerCancel = nil workerDone = nil } } }