Fix GTK/mpv player build issues (imports, window ID, polling)
This commit is contained in:
parent
26c48ab981
commit
ba1db9e16f
|
|
@ -1,23 +1,25 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"git.leaktechnologies.dev/stu/VT_Player/player/mpvembed"
|
||||
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
// simple GTK + mpv embedded player demo (single pane).
|
||||
type pane struct {
|
||||
area *gtk.DrawingArea
|
||||
mpv *mpvembed.Client
|
||||
path string
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
gtk.Init(nil)
|
||||
|
||||
win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
|
||||
|
|
@ -25,88 +27,30 @@ func main() {
|
|||
log.Fatalf("window: %v", err)
|
||||
}
|
||||
win.SetTitle("VT Player (GTK/mpv)")
|
||||
win.SetDefaultSize(1280, 720)
|
||||
win.SetDefaultSize(1400, 800)
|
||||
|
||||
grid, _ := gtk.GridNew()
|
||||
grid.SetColumnHomogeneous(true)
|
||||
grid.SetRowHomogeneous(false)
|
||||
win.Add(grid)
|
||||
|
||||
// Drawing area for mpv
|
||||
da, _ := gtk.DrawingAreaNew()
|
||||
da.SetHexpand(true)
|
||||
da.SetVexpand(true)
|
||||
grid.Attach(da, 0, 1, 4, 1)
|
||||
// Two panes for compare; left/right
|
||||
left := newPane()
|
||||
right := newPane()
|
||||
|
||||
// Controls
|
||||
openBtn, _ := gtk.ButtonNewWithLabel("Open...")
|
||||
playBtn, _ := gtk.ButtonNewWithLabel("Play")
|
||||
pauseBtn, _ := gtk.ButtonNewWithLabel("Pause")
|
||||
seekStartBtn, _ := gtk.ButtonNewWithLabel("<< Start")
|
||||
grid.Attach(openBtn, 0, 0, 1, 1)
|
||||
grid.Attach(playBtn, 1, 0, 1, 1)
|
||||
grid.Attach(pauseBtn, 2, 0, 1, 1)
|
||||
grid.Attach(seekStartBtn, 3, 0, 1, 1)
|
||||
controls := buildControls(win, left, right)
|
||||
grid.Attach(controls, 0, 0, 2, 1)
|
||||
|
||||
mpv, err := mpvembed.New()
|
||||
if err != nil {
|
||||
log.Fatalf("mpv: %v", err)
|
||||
}
|
||||
defer mpv.Destroy()
|
||||
|
||||
var duration float64
|
||||
|
||||
da.Connect("realize", func() {
|
||||
w := da.GetWindow()
|
||||
if w == nil {
|
||||
log.Println("no window yet")
|
||||
return
|
||||
}
|
||||
// Bind native window ID
|
||||
if xid := getWindowID(w); xid != 0 {
|
||||
_ = mpv.SetWID(xid)
|
||||
}
|
||||
// Set options and init
|
||||
_ = mpv.SetOptionString("pause", "yes")
|
||||
if err := mpv.Initialize(); err != nil {
|
||||
log.Fatalf("mpv init: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
openBtn.Connect("clicked", func() {
|
||||
dlg, _ := gtk.FileChooserDialogNewWith1Button("Open Video", win, gtk.FILE_CHOOSER_ACTION_OPEN, "Open", gtk.RESPONSE_ACCEPT)
|
||||
if resp := dlg.Run(); resp == int(gtk.RESPONSE_ACCEPT) {
|
||||
filename := dlg.GetFilename()
|
||||
if filename != "" {
|
||||
loadFile(mpv, filename, &duration)
|
||||
}
|
||||
}
|
||||
dlg.Destroy()
|
||||
})
|
||||
|
||||
playBtn.Connect("clicked", func() {
|
||||
_ = mpv.SetPropertyBool("pause", false)
|
||||
})
|
||||
pauseBtn.Connect("clicked", func() {
|
||||
_ = mpv.SetPropertyBool("pause", true)
|
||||
})
|
||||
seekStartBtn.Connect("clicked", func() {
|
||||
_ = mpv.Command("seek", "0", "absolute", "exact")
|
||||
})
|
||||
|
||||
// Progress poll
|
||||
go func() {
|
||||
t := time.NewTicker(250 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
for range t.C {
|
||||
pos, err := mpv.GetPropertyDouble("time-pos")
|
||||
if err == nil && duration > 0 {
|
||||
fmt.Printf("\r%.1f / %.1f", pos, duration)
|
||||
}
|
||||
}
|
||||
}()
|
||||
grid.Attach(left.area, 0, 1, 1, 1)
|
||||
grid.Attach(right.area, 1, 1, 1, 1)
|
||||
|
||||
win.Connect("destroy", func() {
|
||||
if left.mpv != nil {
|
||||
left.mpv.Destroy()
|
||||
}
|
||||
if right.mpv != nil {
|
||||
right.mpv.Destroy()
|
||||
}
|
||||
gtk.MainQuit()
|
||||
})
|
||||
|
||||
|
|
@ -114,36 +58,172 @@ func main() {
|
|||
gtk.Main()
|
||||
}
|
||||
|
||||
func loadFile(mpv *mpvembed.Client, path string, duration *float64) {
|
||||
base := filepath.Base(path)
|
||||
fmt.Printf("\nLoading: %s\n", base)
|
||||
if err := mpv.Command("loadfile", path, "replace"); err != nil {
|
||||
log.Printf("loadfile: %v", err)
|
||||
func newPane() *pane {
|
||||
da, _ := gtk.DrawingAreaNew()
|
||||
da.SetHExpand(true)
|
||||
da.SetVExpand(true)
|
||||
p := &pane{area: da}
|
||||
da.Connect("realize", func() {
|
||||
if p.mpv != nil {
|
||||
return
|
||||
}
|
||||
mpv, err := mpvembed.New()
|
||||
if err != nil {
|
||||
log.Printf("mpv create: %v", err)
|
||||
return
|
||||
}
|
||||
p.mpv = mpv
|
||||
if w, ok := da.GetWindow(); ok && w != nil {
|
||||
if xid := getWindowID(w); xid != 0 {
|
||||
_ = mpv.SetWID(xid)
|
||||
}
|
||||
}
|
||||
_ = mpv.SetOptionString("pause", "yes")
|
||||
if err := mpv.Initialize(); err != nil {
|
||||
log.Printf("mpv init: %v", err)
|
||||
}
|
||||
})
|
||||
return p
|
||||
}
|
||||
|
||||
func buildControls(win *gtk.Window, left, right *pane) *gtk.Box {
|
||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 6)
|
||||
row1, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4)
|
||||
row2, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4)
|
||||
row3, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4)
|
||||
|
||||
openL, _ := gtk.ButtonNewWithLabel("Open Left")
|
||||
openR, _ := gtk.ButtonNewWithLabel("Open Right")
|
||||
play, _ := gtk.ButtonNewWithLabel("Play Both")
|
||||
pause, _ := gtk.ButtonNewWithLabel("Pause Both")
|
||||
seek0, _ := gtk.ButtonNewWithLabel("Seek 0")
|
||||
stepF, _ := gtk.ButtonNewWithLabel("Step +1f")
|
||||
stepB, _ := gtk.ButtonNewWithLabel("Step -1f")
|
||||
info, _ := gtk.LabelNew("Meta: -")
|
||||
|
||||
openL.Connect("clicked", func() { chooseAndLoad(win, left) })
|
||||
openR.Connect("clicked", func() { chooseAndLoad(win, right) })
|
||||
|
||||
play.Connect("clicked", func() {
|
||||
if left.mpv != nil {
|
||||
_ = left.mpv.SetPropertyBool("pause", false)
|
||||
}
|
||||
if right.mpv != nil {
|
||||
_ = right.mpv.SetPropertyBool("pause", false)
|
||||
}
|
||||
})
|
||||
pause.Connect("clicked", func() {
|
||||
if left.mpv != nil {
|
||||
_ = left.mpv.SetPropertyBool("pause", true)
|
||||
}
|
||||
if right.mpv != nil {
|
||||
_ = right.mpv.SetPropertyBool("pause", true)
|
||||
}
|
||||
})
|
||||
seek0.Connect("clicked", func() {
|
||||
if left.mpv != nil {
|
||||
_ = left.mpv.Command("seek", "0", "absolute", "exact")
|
||||
}
|
||||
if right.mpv != nil {
|
||||
_ = right.mpv.Command("seek", "0", "absolute", "exact")
|
||||
}
|
||||
})
|
||||
stepF.Connect("clicked", func() {
|
||||
if left.mpv != nil {
|
||||
_ = left.mpv.Command("frame-step")
|
||||
}
|
||||
if right.mpv != nil {
|
||||
_ = right.mpv.Command("frame-step")
|
||||
}
|
||||
})
|
||||
stepB.Connect("clicked", func() {
|
||||
if left.mpv != nil {
|
||||
_ = left.mpv.Command("frame-back-step")
|
||||
}
|
||||
if right.mpv != nil {
|
||||
_ = right.mpv.Command("frame-back-step")
|
||||
}
|
||||
})
|
||||
|
||||
// Poll meta/progress
|
||||
go func() {
|
||||
t := time.NewTicker(500 * time.Millisecond)
|
||||
defer t.Stop()
|
||||
for range t.C {
|
||||
text := metaSummary(left, right)
|
||||
glib.IdleAdd(func() { info.SetText(text) })
|
||||
}
|
||||
}()
|
||||
|
||||
row1.PackStart(openL, false, false, 0)
|
||||
row1.PackStart(openR, false, false, 0)
|
||||
row1.PackStart(play, false, false, 0)
|
||||
row1.PackStart(pause, false, false, 0)
|
||||
row1.PackStart(seek0, false, false, 0)
|
||||
row1.PackStart(stepB, false, false, 0)
|
||||
row1.PackStart(stepF, false, false, 0)
|
||||
|
||||
row2.PackStart(info, false, false, 0)
|
||||
|
||||
box.PackStart(row1, false, false, 0)
|
||||
box.PackStart(row2, false, false, 0)
|
||||
box.PackStart(row3, false, false, 0)
|
||||
return box
|
||||
}
|
||||
|
||||
func chooseAndLoad(win *gtk.Window, p *pane) {
|
||||
dlg, _ := gtk.FileChooserDialogNewWith1Button("Open Video", win, gtk.FILE_CHOOSER_ACTION_OPEN, "Open", gtk.RESPONSE_ACCEPT)
|
||||
if resp := dlg.Run(); resp == gtk.RESPONSE_ACCEPT {
|
||||
filename := dlg.GetFilename()
|
||||
if filename != "" {
|
||||
loadIntoPane(p, filename)
|
||||
}
|
||||
}
|
||||
if d, err := mpv.GetPropertyDouble("duration"); err == nil {
|
||||
*duration = d
|
||||
dlg.Destroy()
|
||||
}
|
||||
|
||||
func loadIntoPane(p *pane, filename string) {
|
||||
if p.mpv == nil {
|
||||
return
|
||||
}
|
||||
_ = mpv.SetPropertyBool("pause", false)
|
||||
p.path = filename
|
||||
if err := p.mpv.Command("loadfile", filename, "replace"); err != nil {
|
||||
log.Printf("loadfile %s: %v", filename, err)
|
||||
}
|
||||
_ = p.mpv.SetPropertyBool("pause", false)
|
||||
}
|
||||
|
||||
func metaSummary(a, b *pane) string {
|
||||
parts := func(p *pane) string {
|
||||
if p == nil || p.mpv == nil || p.path == "" {
|
||||
return "[empty]"
|
||||
}
|
||||
dur, _ := p.mpv.GetPropertyDouble("duration")
|
||||
pos, _ := p.mpv.GetPropertyDouble("time-pos")
|
||||
w, _ := p.mpv.GetPropertyInt64("width")
|
||||
h, _ := p.mpv.GetPropertyInt64("height")
|
||||
return fmt.Sprintf("%s | %dx%d | %.1f/%.1fs", filepath.Base(p.path), w, h, pos, dur)
|
||||
}
|
||||
return fmt.Sprintf("L: %s | R: %s", parts(a), parts(b))
|
||||
}
|
||||
|
||||
// getWindowID returns the native window handle (XID on X11, HWND on Windows).
|
||||
func getWindowID(w *gdk.Window) uint64 {
|
||||
// X11
|
||||
if xid := gdkWindowGetXID(w); xid != 0 {
|
||||
return xid
|
||||
return uint64(xid)
|
||||
}
|
||||
// TODO: add Windows handle if needed.
|
||||
return 0
|
||||
}
|
||||
|
||||
// gdkWindowGetXID extracts the XID from a GDK window when running on X11.
|
||||
func gdkWindowGetXID(w *gdk.Window) uint64 {
|
||||
func gdkWindowGetXID(w *gdk.Window) uint {
|
||||
type xidGetter interface {
|
||||
GetXID() uint
|
||||
}
|
||||
if xw, ok := w.(xidGetter); ok {
|
||||
return uint64(xw.GetXID())
|
||||
return xw.GetXID()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,3 +144,34 @@ func (c *Client) GetPropertyDouble(name string) (float64, error) {
|
|||
}
|
||||
return float64(out), nil
|
||||
}
|
||||
|
||||
// GetPropertyInt64 gets an int64 property.
|
||||
func (c *Client) GetPropertyInt64(name string) (int64, error) {
|
||||
if c.handle == nil {
|
||||
return 0, errors.New("mpv handle is nil")
|
||||
}
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
var out C.longlong
|
||||
if res := C.mpv_get_property(c.handle, cname, C.mpv_format(C.MPV_FORMAT_INT64), unsafe.Pointer(&out)); res < 0 {
|
||||
return 0, fmt.Errorf("mpv_get_property %s failed: %s", name, C.GoString(C.mpv_errstr(res)))
|
||||
}
|
||||
return int64(out), nil
|
||||
}
|
||||
|
||||
// GetPropertyString gets a string property.
|
||||
func (c *Client) GetPropertyString(name string) (string, error) {
|
||||
if c.handle == nil {
|
||||
return "", errors.New("mpv handle is nil")
|
||||
}
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
var out *C.char
|
||||
if res := C.mpv_get_property(c.handle, cname, C.mpv_format(C.MPV_FORMAT_STRING), unsafe.Pointer(&out)); res < 0 {
|
||||
return "", fmt.Errorf("mpv_get_property %s failed: %s", name, C.GoString(C.mpv_errstr(res)))
|
||||
}
|
||||
if out == nil {
|
||||
return "", nil
|
||||
}
|
||||
return C.GoString(out), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user