switch libmpv binding
This commit is contained in:
@@ -5,6 +5,7 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.3.3
|
fyne.io/fyne/v2 v2.3.3
|
||||||
github.com/20after4/configdir v0.1.1
|
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/dweymouth/go-subsonic v0.0.0-20230210044542-537b9238299b
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/pelletier/go-toml v1.9.3
|
github.com/pelletier/go-toml v1.9.3
|
||||||
|
|||||||
@@ -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/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 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/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 h1:8JbTKYDdQg6JKu7ZDbEaVbXxutc3pRF58yNvTVMBHec=
|
||||||
github.com/dweymouth/go-subsonic v0.0.0-20230210044542-537b9238299b/go.mod h1:fUez6NFiEJiQTZizZ1BThZr5GJXAbigzGYjEPNm4tdI=
|
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=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
package player
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <mpv/client.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#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
|
|
||||||
}
|
|
||||||
+18
-18
@@ -5,6 +5,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/dweymouth/go-mpv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error returned by many Player functions if called before the player has not been initialized.
|
// 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
|
// Player encapsulates the mpv instance and provides functions
|
||||||
// to control it and to check its status.
|
// to control it and to check its status.
|
||||||
type Player struct {
|
type Player struct {
|
||||||
mpv libmpv
|
mpv *mpv.Mpv
|
||||||
initialized bool
|
initialized bool
|
||||||
vol int
|
vol int
|
||||||
replayGainOpts ReplayGainOptions
|
replayGainOpts ReplayGainOptions
|
||||||
@@ -99,10 +101,8 @@ func NewWithClientName(c string) *Player {
|
|||||||
// Most Player functions will return ErrUnitialized if called before Init.
|
// Most Player functions will return ErrUnitialized if called before Init.
|
||||||
func (p *Player) Init(maxCacheMB int) error {
|
func (p *Player) Init(maxCacheMB int) error {
|
||||||
if !p.initialized {
|
if !p.initialized {
|
||||||
m, err := CreateMPV()
|
m := mpv.Create()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.SetOptionString("idle", "yes")
|
m.SetOptionString("idle", "yes")
|
||||||
m.SetOptionString("video", "no")
|
m.SetOptionString("video", "no")
|
||||||
m.SetOptionString("audio-display", "no")
|
m.SetOptionString("audio-display", "no")
|
||||||
@@ -117,7 +117,7 @@ func (p *Player) Init(maxCacheMB int) error {
|
|||||||
if p.vol < 0 {
|
if p.vol < 0 {
|
||||||
p.vol = 100
|
p.vol = 100
|
||||||
}
|
}
|
||||||
m.SetOption("volume", MPVFormatInt64, p.vol)
|
m.SetOption("volume", mpv.FORMAT_INT64, p.vol)
|
||||||
|
|
||||||
p.SetAudioExclusive(p.audioExclusive)
|
p.SetAudioExclusive(p.audioExclusive)
|
||||||
if p.haveRGainOpts {
|
if p.haveRGainOpts {
|
||||||
@@ -246,7 +246,7 @@ func (p *Player) SetVolume(vol int) error {
|
|||||||
vol = 0
|
vol = 0
|
||||||
}
|
}
|
||||||
if p.initialized {
|
if p.initialized {
|
||||||
err := p.mpv.SetProperty("volume", MPVFormatInt64, vol)
|
err := p.mpv.SetProperty("volume", mpv.FORMAT_INT64, vol)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
p.vol = vol
|
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 {
|
if err := p.mpv.SetPropertyString("replaygain", string(options.Mode)); err != nil {
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
clip := "no"
|
clip := "no"
|
||||||
@@ -300,7 +300,7 @@ func (p *Player) GetVolume() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) setPaused(paused bool) error {
|
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.
|
// Start playback from the first track in the play queue.
|
||||||
@@ -362,8 +362,8 @@ func (p *Player) GetStatus() Status {
|
|||||||
return p.status
|
return p.status
|
||||||
}
|
}
|
||||||
|
|
||||||
pos, _ := p.mpv.GetProperty("playback-time", MPVFormatDouble)
|
pos, _ := p.mpv.GetProperty("playback-time", mpv.FORMAT_DOUBLE)
|
||||||
dur, _ := p.mpv.GetProperty("duration", MPVFormatDouble)
|
dur, _ := p.mpv.GetProperty("duration", mpv.FORMAT_DOUBLE)
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
p.status.TimePos = pos.(float64)
|
p.status.TimePos = pos.(float64)
|
||||||
}
|
}
|
||||||
@@ -377,7 +377,7 @@ func (p *Player) GetStatus() Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) getInt64Property(propName string) (int64, error) {
|
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 {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
@@ -463,19 +463,19 @@ func (p *Player) eventHandler(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
e := p.mpv.WaitEvent(1 /*timeout seconds*/)
|
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)
|
//log.Printf("mpv event: %+v\n", e)
|
||||||
}
|
}
|
||||||
switch e.ID {
|
switch e.Event_Id {
|
||||||
case MPVEventPlaybackRestart:
|
case mpv.EVENT_PLAYBACK_RESTART:
|
||||||
if p.seeking {
|
if p.seeking {
|
||||||
p.seeking = false
|
p.seeking = false
|
||||||
}
|
}
|
||||||
case MPVEventSeek:
|
case mpv.EVENT_SEEK:
|
||||||
for _, cb := range p.onSeek {
|
for _, cb := range p.onSeek {
|
||||||
cb()
|
cb()
|
||||||
}
|
}
|
||||||
case MPVEventFileLoaded:
|
case mpv.EVENT_FILE_LOADED:
|
||||||
if p.status.State == Paused {
|
if p.status.State == Paused {
|
||||||
// seek while paused switches to a new file
|
// seek while paused switches to a new file
|
||||||
// mpv does not fire seek event in this case
|
// mpv does not fire seek event in this case
|
||||||
@@ -489,7 +489,7 @@ func (p *Player) eventHandler(ctx context.Context) {
|
|||||||
cb(pos)
|
cb(pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case MPVEventIdle:
|
case mpv.EVENT_IDLE:
|
||||||
p.status.Duration = 0
|
p.status.Duration = 0
|
||||||
p.status.TimePos = 0
|
p.status.TimePos = 0
|
||||||
p.setState(Stopped)
|
p.setState(Stopped)
|
||||||
|
|||||||
Reference in New Issue
Block a user