package playback import ( "context" "errors" "sync" ) type PlaylistController struct { playlist Playlist retry RetryPolicy sessions chan<- SessionCommand mu sync.RWMutex snapshot PlaylistSnapshot hasSnapshot bool } type PlaylistSnapshot struct { State PlaylistState Entry PlaylistEntry } var ( ErrNilSessionCommandChannel = errors.New("session-command channel is nil") ) func NewPlaylistController( playlist Playlist, retry RetryPolicy, sessions chan<- SessionCommand, ) (*PlaylistController, error) { if err := playlist.Validate(); err != nil { return nil, err } if err := retry.Validate(); err != nil { return nil, err } if sessions == nil { return nil, ErrNilSessionCommandChannel } return &PlaylistController{ playlist: playlist, retry: retry, sessions: sessions, }, nil } func (c *PlaylistController) Run( ctx context.Context, commands <-chan PlaylistCommand, ) error { state := PlaylistState{} c.publish(state) for { select { case <-ctx.Done(): return ctx.Err() case command, ok := <-commands: if !ok { return nil } next, sessionCommand, apply, err := ApplyPlaylistSelection( c.playlist, state, command, c.retry, ) if err != nil { continue } if apply { select { case <-ctx.Done(): return ctx.Err() case c.sessions <- sessionCommand: } } state = next c.publish(state) } } } func (c *PlaylistController) Snapshot() (PlaylistSnapshot, bool) { c.mu.RLock() defer c.mu.RUnlock() return c.snapshot, c.hasSnapshot } func (c *PlaylistController) publish(state PlaylistState) { entry, _ := state.Entry(c.playlist) c.mu.Lock() c.snapshot = PlaylistSnapshot{State: state, Entry: entry} c.hasSnapshot = true c.mu.Unlock() }