package cmd import ( "fmt" "os" "os/exec" "sync" "time" "github.com/dexterlb/mpvipc" ) 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 } func NewMPVPlayer() *MPVPLayer { return &MPVPLayer{} } func (mpv *MPVPLayer) Exit() error { return nil } 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 if err := mpv.c.Set("pause", false); err != nil { return err } return nil } 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() { return false } return mpv.isPlaying } // 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("", "mpvminiflux") 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.Command( "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 } // 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 { data := event.Data.(float64) if data != 0 { mpv.Lock() 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 }