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) 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 }