add stop button: package main

import (
    "io"
    "os"
    "sync"
    "gioui.org/app"
    "gioui.org/io/system"
    "gioui.org/layout"
    "gioui.org/op"
    "gioui.org/unit"
    "gioui.org/widget"
    "gioui.org/widget/material"
    "gioui.org/font/gofont"

    "github.com/tosone/minimp3"
    "github.com/hajimehoshi/oto"
)

func main() {
    go func() {
        w := app.NewWindow(
            app.Title("mp3 reader"),
            app.Size(unit.Dp(350), unit.Dp(500)),
        )

        var ops op.Ops
        for event := range w.Events() {
            switch event := event.(type) {
            case system.DestroyEvent:
                os.Exit(0)
            case system.FrameEvent:
                event.Frame(frame(layout.NewContext(&ops, event)))
            }
        }
    }()
    app.Main()
}

type (
    // C quick alias for Context.
    C = layout.Context
    // D quick alias for Dimensions.
    D = layout.Dimensions
)

var (
    th      = material.NewTheme(gofont.Collection())
    topLabel = "mp3 reader"
    playBtn  = widget.Clickable{}
    pauseBtn  = widget.Clickable{}
    playing  bool
    mu       sync.Mutex
    player   *oto.Player
    dec      *minimp3.Decoder
)

// frame lays out the entire frame and returns the resultant ops buffer.
func frame(gtx C) *op.Ops {

    layout.Center.Layout(gtx, func(gtx C) D {

        gtx.Constraints.Max.X = gtx.Dp(unit.Dp(300))
        return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
            layout.Rigid(func(gtx C) D {
                label := material.H5(th, topLabel)
                return label.Layout(gtx)
            }),
            layout.Rigid(func(gtx C) D {
                return material.Button(th, &playBtn, "Play").Layout(gtx)
            }),
            layout.Rigid(func(gtx C) D {
                return material.Button(th, &pauseBtn, "Pause").Layout(gtx)
            }),
        )
    })

    if playBtn.Clicked() {
        go func() {
            mu.Lock()
            if playing {
                mu.Unlock()
                return
            }
            playing = true
            mu.Unlock()

            var err error

            var file *os.File
            if file, err = os.Open("demo.mp3"); err != nil {
                return
            }

            if dec == nil {
                if dec, err = minimp3.NewDecoder(file); err != nil {
                    return
                }
                started := dec.Started()
                <-started

                var context *oto.Context
                if context, err = oto.NewContext(dec.SampleRate, dec.Channels, 2, 1024); err != nil {
                    return
                }
                player = context.NewPlayer()
            }

            for {
                var data = make([]byte, 1024)
                _, err := dec.Read(data)
                if err == io.EOF {
                    break
                }
                if err != nil {
                    break
                }
                player.Write(data)

                mu.Lock()
                if !playing {
                    mu.Unlock()
                    break
                }
                mu.Unlock()
            }

            mu.Lock()
            playing = false
            mu.Unlock()
        }()
    }

    if pauseBtn.Clicked() {
        mu.Lock()
        playing = false
        mu.Unlock()
    }

    return gtx.Ops
}