package cmd import ( "context" "fmt" "os" "os/exec" "sync" "time" "github.com/dexterlb/mpvipc" ) type MPVPLayer struct { entries []*feedEntry finishedEntries []*feedEntry c *mpvipc.Connection f *os.File videoPosition time.Duration videoDuration time.Duration isPlaying bool sync.RWMutex } func NewMPVPlayer() *MPVPLayer { return &MPVPLayer{} } 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 { if err := mpv.ensurePlayer(); err != nil { return err } _, err := mpv.c.Call("loadfile", url) if err != nil { return err } mpv.isPlaying = true return err } func (mpv *MPVPLayer) Stop() error { mpv.Lock() defer mpv.Unlock() if mpv.c == nil { return nil } err := mpv.c.Set("pause", true) return err } func (mpv *MPVPLayer) VideoDuration() time.Duration { mpv.RLock() defer mpv.RUnlock() return mpv.videoDuration } func (mpv *MPVPLayer) VideoPosition() time.Duration { mpv.RLock() defer mpv.RUnlock() return mpv.videoPosition } func (mpv *MPVPLayer) IsPlaying() bool { mpv.Lock() defer mpv.Unlock() if mpv.c == nil || mpv.c.IsClosed() { if mpv.isPlaying == true { mpv.finishedVideo() } 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 err := mpv.play(mpv.entries[0].Link); err != nil { return false, err } } return false, nil } func (mpv *MPVPLayer) ensurePlayer() error { if mpv.c != nil && !mpv.c.IsClosed() { return nil } var err error mpv.f, err = os.CreateTemp("", "example") if err != nil { return err } // close and delete file defer func() { if mpv.f == nil { return } filename := mpv.f.Name() mpv.f.Close() os.Remove(filename) }() // defer mpv.finishedVideo() ctx := context.TODO() cmd := exec.CommandContext(ctx, "mpv", "--keep-open=yes", "--idle", fmt.Sprintf("--input-ipc-server=%s", mpv.f.Name()), ) // start err = cmd.Start() if err != nil { return err } time.Sleep(100 * time.Millisecond) mpv.c = mpvipc.NewConnection(mpv.f.Name()) if err := mpv.c.Open(); err != nil { return err } // setup observe properties events, _ := mpv.c.NewEventListener() if _, err = mpv.c.Call("observe_property", 1, "duration"); err != nil { return err } if _, err = mpv.c.Call("observe_property", 2, "time-pos"); err != nil { return err } if _, err = mpv.c.Call("observe_property", 3, "eof-reached"); err != nil { return err } 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 { if event.Data != nil { if event.Data.(bool) == true { mpv.Lock() mpv.finishedVideo() mpv.Unlock() } } } } }() return nil // return cmd.Wait() }