update more!

This commit is contained in:
eyjhb 2025-01-26 22:37:37 +01:00
parent 1efe2d08e1
commit a706a38252
Signed by: eyjhb
GPG key ID: 609F508E3239F920
2 changed files with 169 additions and 0 deletions

157
cmd/player.go Normal file
View file

@ -0,0 +1,157 @@
package cmd
import (
"bufio"
"context"
"fmt"
"os/exec"
"regexp"
"strconv"
"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
finishedEntries []int64
sync.RWMutex
}
func (m *playerModel) Init() tea.Cmd {
return nil
}
func (m *playerModel) playEntry(f feedEntry) tea.Cmd {
if m.isPlayingVideo == true {
return nil
}
m.entry = &f
if m.entry != nil && m.isPlayingVideo == false {
go m.playVideo(context.Background(), m.entry.Link)
} else {
// TODO: we are already playing something, we should not allow this
// return MsgTick(time.Now())
}
return nil
}
func (m *playerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case MsgPlayEntry:
f := feedEntry(msg)
return m, m.playEntry(f)
}
// send message because it is finished
m.Lock()
defer m.Unlock()
if len(m.finishedEntries) > 0 {
watchedEntryID := m.finishedEntries[0]
m.finishedEntries = m.finishedEntries[1:]
return m, func() tea.Msg { return MsgWatchedEntry(watchedEntryID) }
}
return m, nil
}
func (m *playerModel) View() string {
if m.isPlayingVideo && m.entry != nil {
timePos := time.Time{}.Add(m.videoPosition)
timeDur := time.Time{}.Add(m.videoDuration)
// truncate name if needed
name := m.entry.Name
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.videoPosition, m.videoDuration),
)
}
return ""
}
func (m *playerModel) finishedVideo() {
m.Lock()
defer m.Unlock()
m.isPlayingVideo = false
if videoPercentageWatched(m.videoPosition, m.videoDuration) > 90.0 {
m.finishedEntries = append(m.finishedEntries, m.entry.ID)
}
}
func (m *playerModel) playVideo(ctx context.Context, url string) (bool, error) {
m.Lock()
m.isPlayingVideo = true
m.Unlock()
defer m.finishedVideo()
cmd := exec.CommandContext(ctx, "mpv", url)
output, err := cmd.StdoutPipe()
if err != nil {
return false, err
}
err = cmd.Start()
if err != nil {
return false, err
}
s := bufio.NewScanner(output)
go func(s *bufio.Scanner) {
var finishedVideo bool
for s.Scan() {
t := s.Text()
if strings.Contains(t, "End of file") {
finishedVideo = true
} else if strings.HasPrefix(t, "AV: ") {
matches := regexpPosDur.FindStringSubmatch(t)
posh, _ := strconv.Atoi(matches[1])
posm, _ := strconv.Atoi(matches[2])
poss, _ := strconv.Atoi(matches[3])
durh, _ := strconv.Atoi(matches[4])
durm, _ := strconv.Atoi(matches[5])
durs, _ := strconv.Atoi(matches[6])
m.videoPosition = time.Duration((posh*60*60 + posm*60 + poss) * int(time.Second))
m.videoDuration = time.Duration((durh*60*60 + durm*60 + durs) * int(time.Second))
}
}
_ = finishedVideo
}(s)
return false, cmd.Wait()
}