diff --git a/cmd/cmd.go b/cmd/cmd.go index 63640a3..7e26a05 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -30,6 +30,7 @@ const ( type MsgChangeView ViewType type MsgPlayEntry feedEntry type MsgWatchedEntry int64 +type MsgError error type MsgTick time.Time type MinifluxPlayer struct { diff --git a/cmd/feed_entries.go b/cmd/feed_entries.go index 53bd50a..981d439 100644 --- a/cmd/feed_entries.go +++ b/cmd/feed_entries.go @@ -64,6 +64,10 @@ func (m *feedEntriesModel) Init() tea.Cmd { func (m *feedEntriesModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case tea.WindowSizeMsg: + // h, v := docStyle.GetFrameSize() + // TODO: this is not ideal, hardcoding -2 here + m.entries.SetSize(msg.Width, msg.Height-2) case tea.KeyMsg: switch msg.String() { case "enter": diff --git a/cmd/mpv.go b/cmd/mpv.go index 21a19a3..6797792 100644 --- a/cmd/mpv.go +++ b/cmd/mpv.go @@ -1,7 +1,6 @@ package cmd import ( - "context" "fmt" "os" "os/exec" @@ -11,16 +10,17 @@ import ( "github.com/dexterlb/mpvipc" ) -type MPVPLayer struct { - entries []*feedEntry - finishedEntries []*feedEntry +type VideoID string +type MPVPLayer struct { c *mpvipc.Connection f *os.File + videoID VideoID videoPosition time.Duration videoDuration time.Duration isPlaying bool + finishedVideo string sync.RWMutex } @@ -33,54 +33,27 @@ func (mpv *MPVPLayer) Exit() error { return nil } -func (mpv *MPVPLayer) Queue(e feedEntry) error { - mpv.Lock() - defer mpv.Unlock() - - // TODO(eyJhb): implement checking - mpv.entries = append(mpv.entries, &e) - - if len(mpv.entries) == 1 { - return mpv.play(e.Link) - } - - return nil -} - -func (mpv *MPVPLayer) FinishedPlaying() []*feedEntry { - mpv.Lock() - defer mpv.Unlock() - - f := mpv.finishedEntries - mpv.finishedEntries = nil - - return f -} - -func (mpv *MPVPLayer) CurrentlyPlaying() *feedEntry { - mpv.Lock() - defer mpv.Unlock() - - if !mpv.isPlaying { - return nil - } - - return mpv.entries[0] -} - -func (mpv *MPVPLayer) play(url string) error { +func (mpv *MPVPLayer) Play(id VideoID, url string) error { if err := mpv.ensurePlayer(); err != nil { return err } + mpv.Lock() + defer mpv.Unlock() + _, err := mpv.c.Call("loadfile", url) if err != nil { return err } + mpv.videoID = id mpv.isPlaying = true - return err + if err := mpv.c.Set("pause", false); err != nil { + return err + } + + return nil } func (mpv *MPVPLayer) Stop() error { @@ -112,61 +85,43 @@ func (mpv *MPVPLayer) IsPlaying() bool { mpv.Lock() defer mpv.Unlock() - if mpv.c == nil || mpv.c.IsClosed() { - if mpv.isPlaying == true { - mpv.finishedVideo() - } + if mpv.c != nil && mpv.c.IsClosed() { return false } return mpv.isPlaying } -func (mpv *MPVPLayer) finishedVideo() (bool, error) { - if len(mpv.entries) > 0 { - mpv.finishedEntries = append(mpv.finishedEntries, mpv.entries[0]) - mpv.entries = mpv.entries[1:] - } - mpv.isPlaying = false - - if len(mpv.entries) == 1 { - if videoPercentageWatched(mpv.VideoPosition(), mpv.VideoDuration()) > 90 { - if err := mpv.play(mpv.entries[0].Link); err != nil { - return false, err - } - - } - } - - return false, nil -} - +// TODO: this fucking sucks, a lot, what the fuck func (mpv *MPVPLayer) ensurePlayer() error { + mpv.Lock() + defer mpv.Unlock() if mpv.c != nil && !mpv.c.IsClosed() { return nil } + // TODO: cleanup this file var err error - mpv.f, err = os.CreateTemp("", "example") + mpv.f, err = os.CreateTemp("", "mpvminiflux") if err != nil { return err } // close and delete file - defer func() { - if mpv.f == nil { - return - } + // defer func() { + // if mpv.f == nil { + // return + // } - filename := mpv.f.Name() - mpv.f.Close() - os.Remove(filename) - }() + // filename := mpv.f.Name() + // mpv.f.Close() + // os.Remove(filename) + // }() // defer mpv.finishedVideo() - ctx := context.TODO() - cmd := exec.CommandContext(ctx, + // ctx := context.TODO() + cmd := exec.Command( "mpv", "--keep-open=yes", "--idle", @@ -197,33 +152,46 @@ func (mpv *MPVPLayer) ensurePlayer() error { return err } + // TODO: this will never close any old ones, so it will keep spawning new ones go func() { for event := range events { if event.ID == 1 { if event.Data != nil { - mpv.Lock() - mpv.videoDuration = time.Duration(event.Data.(float64)) * time.Second - mpv.Unlock() - } - } else if event.ID == 2 { - if event.Data != nil { - mpv.Lock() - mpv.videoPosition = time.Duration(event.Data.(float64)) * time.Second - mpv.Unlock() - } - } else if event.ID == 3 { - // TODO: this might not fire once the media ends - if event.Data != nil { - if event.Data.(bool) == true { + data := event.Data.(float64) + + if data != 0 { mpv.Lock() - mpv.finishedVideo() + mpv.videoDuration = time.Duration(data) * time.Second mpv.Unlock() } } + } else if event.ID == 2 { + if event.Data != nil { + data := event.Data.(float64) + + if data != 0 { + mpv.Lock() + mpv.videoPosition = time.Duration(data) * time.Second + mpv.Unlock() + } + } + } else if event.ID == 3 { + if event.Data != nil && event.Data.(bool) == true { + mpv.Lock() + mpv.isPlaying = false + mpv.Unlock() + } + // TODO: this might not fire once the media ends + // if event.Data != nil { + // if event.Data.(bool) == true { + // mpv.Lock() + // mpv.finishedVideo() + // mpv.Unlock() + // } + // } } } }() return nil - // return cmd.Wait() } diff --git a/cmd/player.go b/cmd/player.go index 03033f1..67117db 100644 --- a/cmd/player.go +++ b/cmd/player.go @@ -27,7 +27,8 @@ type playerModel struct { // TODO: make this smarter // finished entries to send messages about - finishedEntries []int64 + currentlyPlaying *feedEntry + playQueue []feedEntry mpv *MPVPLayer @@ -44,25 +45,33 @@ func (m *playerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case MsgPlayEntry: f := feedEntry(msg) - go m.mpv.Queue(f) + m.queue(f) } // send message because it is finished - fe := m.mpv.FinishedPlaying() - if len(fe) > 0 { - var cmds []tea.Cmd - for _, e := range fe { - cmds = append(cmds, func() tea.Msg { return MsgWatchedEntry(e.ID) }) + // 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...) } - return m, nil + return m, tea.Batch(cmds...) } func (m *playerModel) View() string { // return "NO DONT ASK" - // return fmt.Sprintf("IsPlaying: %v", m.mpv.IsPlaying()) + 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()) @@ -70,7 +79,7 @@ func (m *playerModel) View() string { // truncate name if needed // name := m.entry.Name - name := m.mpv.CurrentlyPlaying().Name + name := m.currentlyPlaying.Name if len(name) > playerMaxTitleLength { name = name[0:playerMaxTitleLength] + "..." } @@ -86,3 +95,26 @@ func (m *playerModel) View() string { return "" } + +// other functions +func (m *playerModel) queue(f feedEntry) error { + 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 +}