summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go157
1 files changed, 157 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..5701c72
--- /dev/null
+++ b/main.go
@@ -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)
+}