diff options
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 157 |
1 files changed, 157 insertions, 0 deletions
@@ -0,0 +1,157 @@ +package main + +import ( + "context" + "image/color" + "log" + "os" + "os/exec" + "sync" + + "gioui.org/app" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/text" + "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" +) + +func main() { + ui := NewUi() + go func() { + err := ui.Run() + if err != nil { + log.Fatal(err) + } + os.Exit(0) + }() + app.Main() +} + +type State struct { + sync.Mutex + Theme *material.Theme + Title string + UrlEditor widget.Editor + PlayPauseButton widget.Clickable + PlayPauseButtonText string + PlayerContext context.Context + PlayerCancel context.CancelFunc +} + +type Ui struct { + Window *app.Window + State State +} + +func NewUi() *Ui { + ui := Ui{} + + ui.State.Theme = material.NewTheme() + + ui.Window = new(app.Window) + ui.Window.Option(app.Title("soundbox app")) + + ui.State.Title = "soundbox" + ui.State.PlayPauseButtonText = "Play" + + return &ui +} + +func (ui *Ui) Cleanup() { + ui.State.Lock() + defer ui.State.Unlock() + + if ui.State.PlayerCancel != nil { + ui.State.PlayerCancel() + } +} + +func (ui *Ui) Run() error { + defer ui.Cleanup() + var ops op.Ops + for { + switch e := ui.Window.Event().(type) { + case app.DestroyEvent: + return e.Err + case app.FrameEvent: + gtx := app.NewContext(&ops, e) + ui.HandleInputs(gtx) + ui.Layout(gtx) + e.Frame(gtx.Ops) + } + } +} + +func (ui *Ui) HandleInputs(gtx layout.Context) { + ui.State.Lock() + defer ui.State.Unlock() + + if ui.State.PlayPauseButton.Clicked(gtx) { + if ui.State.UrlEditor.ReadOnly { + ui.State.PlayerCancel() + } else { + ui.State.PlayerContext, ui.State.PlayerCancel = context.WithCancel(context.Background()) + go mpv(ui.State.PlayerContext, ui.State.UrlEditor.Text(), ui) + } + } +} + +func (ui *Ui) Layout(gtx layout.Context) layout.Dimensions { + ui.State.Lock() + defer ui.State.Unlock() + + inset := layout.UniformInset(unit.Dp(10)) + + flex := layout.Flex{ + Axis: layout.Vertical, + Spacing: layout.SpaceEnd, + } + + h1 := material.H1(ui.State.Theme, ui.State.Title) + h1.Color = color.NRGBA{R: 88, G: 88, B: 88, A: 255} + h1.Alignment = text.Middle + + editor := material.Editor(ui.State.Theme, &ui.State.UrlEditor, "Audio stream URL") + editor.Editor.Alignment = text.Middle + + button := material.Button(ui.State.Theme, &ui.State.PlayPauseButton, ui.State.PlayPauseButtonText) + + return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return flex.Layout(gtx, + layout.Rigid(h1.Layout), + layout.Rigid(layout.Spacer{Height: unit.Dp(25)}.Layout), + layout.Rigid(editor.Layout), + layout.Rigid(layout.Spacer{Height: unit.Dp(25)}.Layout), + layout.Rigid(button.Layout), + ) + }) +} + +func mpv(ctx context.Context, url string, ui *Ui) { + setPlayingState := func(isPlaying bool) { + ui.State.Lock() + defer ui.Window.Invalidate() + defer ui.State.Unlock() + + ui.State.UrlEditor.ReadOnly = isPlaying + if isPlaying { + ui.State.PlayPauseButtonText = "Stop" + } else { + ui.State.PlayPauseButtonText = "Play" + } + } + + setPlayingState(true) + + cmd := exec.CommandContext( + ctx, + "mpv", + "--no-video", + url, + ) + _ = cmd.Run() + + setPlayingState(false) +} |