package main import ( "bytes" "context" discordspeaker "dndmusicbot/speaker" "io" "log" "net/http" "os" "os/exec" "path/filepath" "strconv" "syscall" "text/template" "time" "github.com/gopxl/beep" "github.com/kataras/go-events" "github.com/steino/gompd/v2/mpd" ) type MPD struct { file int f beep.Format httpClient *http.Client pcm *io.PipeReader } func init() { log.Println("mpd.go loading..") f, err := os.Create(config.GetString("mpd.config")) if err != nil { log.Fatal(err) } t, err := template.New("mpd.tmpl").ParseFiles("tmpl/mpd.tmpl") if err != nil { log.Fatal(err) } mpd_conf := config.GetStringMapString("mpd") mpd_conf["proxy_port"] = strconv.Itoa(proxy_port) err = t.Execute(f, mpd_conf) if err != nil { log.Fatal(err) } f.Close() pidstr, err := os.ReadFile(config.GetString("mpd.pid")) if err != nil && os.IsNotExist(err) { pid, _ := strconv.Atoi(string(bytes.TrimSpace(pidstr))) proc, err := os.FindProcess(pid) switch err { case nil: log.Println(proc.Kill()) case os.ErrProcessDone: log.Println("Pid alreadt finished, doing nothing.") default: log.Fatal(err) } } else if err != nil { log.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) app.mpdc = cancel cmd := exec.CommandContext( ctx, config.GetString("mpd.cmd"), "--no-daemon", config.GetString("mpd.config"), "-v", ) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr go func() { err := cmd.Run() if err != nil { log.Println(err) } cancel() }() // wait for mpd to start. time.Sleep(2 * time.Second) app.mpd, err = mpd.Dial("unix", config.GetString("mpd.sock")) if err != nil { log.Fatal(err) } err = app.mpd.NewPartition("ambiance") if err != nil { log.Fatal(err) } app.mpd.Repeat(true) app.mpd.Random(true) err = app.mpd.EnableOutput(0) if err != nil { log.Fatal(err) } // To force the httpd stream to start. err = app.mpd.Add(filepath.Join(cwd, "mp3/silence.mp3")) if err != nil { log.Println(err) return } app.mpd.Play(0) time.Sleep(1 * time.Second) app.mpd.Stop() app.mpd.Clear() err = app.mpd.Partition("ambiance") if err != nil { log.Fatal(err) } app.mpd.Repeat(true) err = app.mpd.MoveOutput("ambiance") if err != nil { log.Fatal(err) } // To force the httpd stream to start. err = app.mpd.Add(filepath.Join(cwd, "mp3/silence.mp3")) if err != nil { log.Println(err) return } app.mpd.Play(0) time.Sleep(1 * time.Second) app.mpd.Stop() app.mpd.Clear() err = app.mpd.Partition("default") if err != nil { log.Fatal(err) } app.mpdw, err = mpd.NewWatcher("unix", config.GetString("mpd.sock"), "") if err != nil { log.Fatal(err) } go func() { for { select { case ev := <-app.mpdw.Event: app.events.Emit(events.EventName(ev)) case err := <-app.mpdw.Error: log.Println(err) return } } }() log.Println("mpd.go done.") } func NewMPD(name string) (*MPD, error) { out := new(MPD) out.httpClient = &http.Client{} var pcm *io.PipeWriter out.pcm, pcm = io.Pipe() f, err := syscall.Open(name, syscall.O_CREAT|syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0644) if err != nil { return nil, err } go func() { buf := make([]byte, 2048) for { _, err := syscall.Read(f, buf) if err != nil { pcm.Write(make([]byte, 2048)) continue } pcm.Write(buf) } }() out.f = beep.Format{ SampleRate: beep.SampleRate(sampleRate), NumChannels: channels, Precision: 2, } out.file = f return out, nil } func (m *MPD) Err() error { return nil } func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) { tmp := make([]byte, m.f.Width()) for i := range samples { //dn, err := syscall.Read(m.file, tmp) dn, err := m.pcm.Read(tmp) if dn == len(tmp) { samples[i], _ = m.f.DecodeSigned(tmp) ok = true } if err != nil { samples[i] = discordspeaker.Silence ok = true break } } return len(samples), ok }