From 34f65e45ca588ef0e018665a9fc2048ac811e885 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 5 Apr 2023 17:35:26 -0700 Subject: [PATCH] switch libmpv binding --- go.mod | 1 + go.sum | 2 + player/libmpv.go | 195 ----------------------------------------------- player/player.go | 36 ++++----- 4 files changed, 21 insertions(+), 213 deletions(-) delete mode 100644 player/libmpv.go diff --git a/go.mod b/go.mod index 966cbd5..b5bdea1 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( fyne.io/fyne/v2 v2.3.3 github.com/20after4/configdir v0.1.1 + github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee github.com/dweymouth/go-subsonic v0.0.0-20230210044542-537b9238299b github.com/google/uuid v1.3.0 github.com/pelletier/go-toml v1.9.3 diff --git a/go.sum b/go.sum index 49d4d78..bff4200 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230331041414-b548301c117c h1:w3Q+TShgmyoTBeWoYEZC5yflP15bkLxAzHWSmxjHQXs= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230331041414-b548301c117c/go.mod h1:MABZ23XXF2K24hl3rva+Di/UbMpibBMAO+qDtDCSe8k= +github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= +github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= github.com/dweymouth/go-subsonic v0.0.0-20230210044542-537b9238299b h1:8JbTKYDdQg6JKu7ZDbEaVbXxutc3pRF58yNvTVMBHec= github.com/dweymouth/go-subsonic v0.0.0-20230210044542-537b9238299b/go.mod h1:fUez6NFiEJiQTZizZ1BThZr5GJXAbigzGYjEPNm4tdI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/player/libmpv.go b/player/libmpv.go deleted file mode 100644 index 6ccb973..0000000 --- a/player/libmpv.go +++ /dev/null @@ -1,195 +0,0 @@ -package player - -/* -#include -#include -#cgo LDFLAGS: -lmpv - -char** newCharArray(int size) { - return calloc(sizeof(char*), size); -} -void setCharArrayIdx(char** a, int i, char* s) { - a[i] = s; -} - -*/ -import "C" -import ( - "errors" - "fmt" - "unsafe" -) - -type MPVFormat int - -const ( - MPVFormatDouble MPVFormat = C.MPV_FORMAT_DOUBLE - MPVFormatFlag MPVFormat = C.MPV_FORMAT_FLAG - MPVFormatString MPVFormat = C.MPV_FORMAT_STRING - MPVFormatInt64 MPVFormat = C.MPV_FORMAT_INT64 -) - -type MPVEventID int - -const ( - MPVEventNone MPVEventID = C.MPV_EVENT_NONE - MPVEventStartFile MPVEventID = C.MPV_EVENT_START_FILE - MPVEventEndFile MPVEventID = C.MPV_EVENT_END_FILE - MPVEventFileLoaded MPVEventID = C.MPV_EVENT_FILE_LOADED - MPVEventIdle MPVEventID = C.MPV_EVENT_IDLE - MPVEventAudioReconfig MPVEventID = C.MPV_EVENT_AUDIO_RECONFIG - MPVEventSeek MPVEventID = C.MPV_EVENT_SEEK - MPVEventPlaybackRestart MPVEventID = C.MPV_EVENT_PLAYBACK_RESTART - MPVEventPropertyChange MPVEventID = C.MPV_EVENT_PROPERTY_CHANGE -) - -type MPVEvent struct { - ID MPVEventID - ErrCode int - Data unsafe.Pointer - UserData uint64 -} - -type libmpv struct { - handle *C.mpv_handle -} - -func CreateMPV() (libmpv, error) { - handle := C.mpv_create() - if handle == nil { - return libmpv{}, errors.New("failed to create mpv instance") - } - return libmpv{handle}, nil -} - -func (m libmpv) Initialize() error { - return toMPVError(C.mpv_initialize(m.handle)) -} - -func (m libmpv) Command(cmd []string) error { - cArray := C.newCharArray(C.int(len(cmd) + 1)) - if cArray == nil { - return errors.New("calloc failed") - } - defer C.free(unsafe.Pointer(cArray)) - - for i, s := range cmd { - cStr := C.CString(s) - C.setCharArrayIdx(cArray, C.int(i), cStr) - defer C.free(unsafe.Pointer(cStr)) - } - - return toMPVError(C.mpv_command(m.handle, cArray)) -} - -func (m libmpv) SetOption(name string, format MPVFormat, value any) error { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - p := toPointer(format, value) - return toMPVError(C.mpv_set_option(m.handle, cname, C.mpv_format(format), p)) -} - -func (m libmpv) SetOptionString(name, value string) error { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - cvalue := C.CString(value) - defer C.free(unsafe.Pointer(cvalue)) - return toMPVError(C.mpv_set_option_string(m.handle, cname, cvalue)) -} - -func (m libmpv) SetProperty(name string, format MPVFormat, value any) error { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - p := toPointer(format, value) - return toMPVError(C.mpv_set_property(m.handle, cname, C.mpv_format(format), p)) -} - -func (m libmpv) SetPropertyString(name, value string) error { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - cvalue := C.CString(value) - defer C.free(unsafe.Pointer(cvalue)) - return toMPVError(C.mpv_set_property_string(m.handle, cname, cvalue)) -} - -func (m libmpv) GetProperty(name string, format MPVFormat) (any, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - switch format { - case MPVFormatDouble: - var cdbl C.double - err := toMPVError(C.mpv_get_property(m.handle, cname, C.mpv_format(format), unsafe.Pointer(&cdbl))) - if err != nil { - return nil, err - } - return float64(cdbl), nil - case MPVFormatFlag: - var cint C.int - err := toMPVError(C.mpv_get_property(m.handle, cname, C.mpv_format(format), unsafe.Pointer(&cint))) - if err != nil { - return nil, err - } - return cint == 1, nil - case MPVFormatInt64: - var cint64 C.int64_t - err := toMPVError(C.mpv_get_property(m.handle, cname, C.mpv_format(format), unsafe.Pointer(&cint64))) - if err != nil { - return nil, err - } - return int64(cint64), nil - default: - return nil, errors.New("unsupported mpv format") - } -} - -func (m libmpv) WaitEvent(timeout float64) MPVEvent { - var cevent *C.mpv_event - cevent = C.mpv_wait_event(m.handle, C.double(timeout)) - if cevent == nil { - return MPVEvent{ID: MPVEventNone} - } - - e := MPVEvent{ - ID: MPVEventID(cevent.event_id), - UserData: uint64(cevent.reply_userdata), - ErrCode: int(cevent.error), - Data: cevent.data, - } - return e -} - -func (m libmpv) TerminateDestroy() { - C.mpv_terminate_destroy(m.handle) -} - -func toMPVError(errcode C.int) error { - if errcode == C.MPV_ERROR_SUCCESS { - return nil - } - return fmt.Errorf("mpv error %d: %s", int(errcode), C.GoString(C.mpv_error_string(C.int(errcode)))) -} - -func toPointer(format MPVFormat, value any) unsafe.Pointer { - var ptr unsafe.Pointer = nil - switch format { - case MPVFormatDouble: - v := C.double(value.(float64)) - ptr = unsafe.Pointer(&v) - case MPVFormatInt64: - i, ok := value.(int64) - if !ok { - i = int64(value.(int)) - } - v := C.int64_t(i) - ptr = unsafe.Pointer(&v) - case MPVFormatFlag: - v := C.int(0) - if value.(bool) { - v = C.int(1) - } - ptr = unsafe.Pointer(&v) - case MPVFormatString: - ptr = unsafe.Pointer(&[]byte(value.(string))[0]) - } - return ptr -} diff --git a/player/player.go b/player/player.go index 87f6bf8..0035b59 100644 --- a/player/player.go +++ b/player/player.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "strconv" + + "github.com/dweymouth/go-mpv" ) // Error returned by many Player functions if called before the player has not been initialized. @@ -58,7 +60,7 @@ type ReplayGainOptions struct { // Player encapsulates the mpv instance and provides functions // to control it and to check its status. type Player struct { - mpv libmpv + mpv *mpv.Mpv initialized bool vol int replayGainOpts ReplayGainOptions @@ -99,10 +101,8 @@ func NewWithClientName(c string) *Player { // Most Player functions will return ErrUnitialized if called before Init. func (p *Player) Init(maxCacheMB int) error { if !p.initialized { - m, err := CreateMPV() - if err != nil { - return err - } + m := mpv.Create() + m.SetOptionString("idle", "yes") m.SetOptionString("video", "no") m.SetOptionString("audio-display", "no") @@ -117,7 +117,7 @@ func (p *Player) Init(maxCacheMB int) error { if p.vol < 0 { p.vol = 100 } - m.SetOption("volume", MPVFormatInt64, p.vol) + m.SetOption("volume", mpv.FORMAT_INT64, p.vol) p.SetAudioExclusive(p.audioExclusive) if p.haveRGainOpts { @@ -246,7 +246,7 @@ func (p *Player) SetVolume(vol int) error { vol = 0 } if p.initialized { - err := p.mpv.SetProperty("volume", MPVFormatInt64, vol) + err := p.mpv.SetProperty("volume", mpv.FORMAT_INT64, vol) if err == nil { p.vol = vol } @@ -266,7 +266,7 @@ func (p *Player) SetReplayGainOptions(options ReplayGainOptions) error { if err := p.mpv.SetPropertyString("replaygain", string(options.Mode)); err != nil { return err } - if err := p.mpv.SetProperty("replaygain-preamp", MPVFormatDouble, options.PreampGain); err != nil { + if err := p.mpv.SetProperty("replaygain-preamp", mpv.FORMAT_DOUBLE, options.PreampGain); err != nil { return err } clip := "no" @@ -300,7 +300,7 @@ func (p *Player) GetVolume() int { } func (p *Player) setPaused(paused bool) error { - return p.mpv.SetProperty("pause", MPVFormatFlag, paused) + return p.mpv.SetProperty("pause", mpv.FORMAT_FLAG, paused) } // Start playback from the first track in the play queue. @@ -362,8 +362,8 @@ func (p *Player) GetStatus() Status { return p.status } - pos, _ := p.mpv.GetProperty("playback-time", MPVFormatDouble) - dur, _ := p.mpv.GetProperty("duration", MPVFormatDouble) + pos, _ := p.mpv.GetProperty("playback-time", mpv.FORMAT_DOUBLE) + dur, _ := p.mpv.GetProperty("duration", mpv.FORMAT_DOUBLE) if pos != nil { p.status.TimePos = pos.(float64) } @@ -377,7 +377,7 @@ func (p *Player) GetStatus() Status { } func (p *Player) getInt64Property(propName string) (int64, error) { - playpos, err := p.mpv.GetProperty(propName, MPVFormatInt64) + playpos, err := p.mpv.GetProperty(propName, mpv.FORMAT_INT64) if err != nil { return -1, err } @@ -463,19 +463,19 @@ func (p *Player) eventHandler(ctx context.Context) { return default: e := p.mpv.WaitEvent(1 /*timeout seconds*/) - if e.ID != MPVEventNone { + if e.Event_Id != mpv.EVENT_NONE { //log.Printf("mpv event: %+v\n", e) } - switch e.ID { - case MPVEventPlaybackRestart: + switch e.Event_Id { + case mpv.EVENT_PLAYBACK_RESTART: if p.seeking { p.seeking = false } - case MPVEventSeek: + case mpv.EVENT_SEEK: for _, cb := range p.onSeek { cb() } - case MPVEventFileLoaded: + case mpv.EVENT_FILE_LOADED: if p.status.State == Paused { // seek while paused switches to a new file // mpv does not fire seek event in this case @@ -489,7 +489,7 @@ func (p *Player) eventHandler(ctx context.Context) { cb(pos) } } - case MPVEventIdle: + case mpv.EVENT_IDLE: p.status.Duration = 0 p.status.TimePos = 0 p.setState(Stopped)