package cmd import ( "fmt" "regexp" "strings" "sync" "time" tea "github.com/charmbracelet/bubbletea" ) const ( playerMaxTitleLength = 30 ) var ( regexpPosDur = regexp.MustCompile(`AV: (\d+):(\d+):(\d+) \/ (\d+):(\d+):(\d+)`) ) type playerModel struct { // entry *feedEntry // isPlayingVideo bool // // video information // videoDuration time.Duration // videoPosition time.Duration // TODO: make this smarter // finished entries to send messages about currentlyPlaying *feedEntry playQueue []feedEntry mpv *MPVPLayer sync.RWMutex } func (m *playerModel) Init() tea.Cmd { m.mpv = NewMPVPlayer() return nil } func (m *playerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case MsgPlayEntry: f := feedEntry(msg) m.queue(f) } // send message because it is finished // we have a currentlyPlaying entry, but it is no longer playing // which means we finished watching it var cmds []tea.Cmd if m.currentlyPlaying != nil && m.mpv.IsPlaying() == false { if videoPercentageWatched(m.mpv.VideoPosition(), m.mpv.VideoDuration()) > 90 { t := m.currentlyPlaying.ID cmds = append(cmds, func() tea.Msg { return MsgWatchedEntry(t) }) } // unset currentlyPlaying m.currentlyPlaying = nil if err := m.playNext(); err != nil { cmds = append(cmds, func() tea.Msg { return MsgError(err) }) } } return m, tea.Batch(cmds...) } func (m *playerModel) View() string { // return "NO DONT ASK" // return fmt.Sprintf("IsPlaying: %v, Pos: %v, Dur: %v, Per: %v", m.mpv.IsPlaying(), m.mpv.VideoPosition(), m.mpv.VideoDuration(), videoPercentageWatched(m.mpv.VideoPosition(), m.mpv.VideoDuration())) if m.mpv.IsPlaying() { timePos := time.Time{}.Add(m.mpv.VideoPosition()) timeDur := time.Time{}.Add(m.mpv.VideoDuration()) // truncate name if needed // name := m.entry.Name name := m.currentlyPlaying.Name name = strings.TrimPrefix(name, "[QUEUE] ") if len(name) > playerMaxTitleLength { name = name[0:playerMaxTitleLength] + "..." } return fmt.Sprintf("Playing: %s (%s/%s %.1f%%)", name, timePos.Format(time.TimeOnly), timeDur.Format(time.TimeOnly), videoPercentageWatched(m.mpv.VideoPosition(), m.mpv.VideoDuration()), ) } return "" } // other functions func (m *playerModel) queue(f feedEntry) error { // check if already exists // TODO: handle actually showing errros? queueIndex := -1 for i, e := range m.playQueue { if e.ID == f.ID { queueIndex = i } } if queueIndex >= 0 && len(m.playQueue) > 1 { m.playQueue = append([]feedEntry{f}, append(m.playQueue[:queueIndex], m.playQueue[queueIndex+1:]...)...) } else { m.playQueue = append(m.playQueue, f) } if len(m.playQueue) == 1 && m.mpv.IsPlaying() == false { m.currentlyPlaying = &m.playQueue[0] m.playQueue = m.playQueue[1:] return m.mpv.Play(VideoID("TODO-remove-this"), m.currentlyPlaying.Link) } return nil } func (m *playerModel) playNext() error { if len(m.playQueue) > 0 && m.mpv.IsPlaying() == false { m.currentlyPlaying = &m.playQueue[0] m.playQueue = m.playQueue[1:] return m.mpv.Play(VideoID("TODO-remove-this"), m.currentlyPlaying.Link) } return nil }