2024-08-04 23:05:23 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-08-05 14:42:38 -04:00
|
|
|
"log"
|
2024-08-04 23:05:23 -04:00
|
|
|
"time"
|
|
|
|
|
2024-08-05 13:18:09 -04:00
|
|
|
"dynatron.me/x/go-minimp3"
|
2024-08-04 23:32:46 -04:00
|
|
|
"github.com/hajimehoshi/oto"
|
2024-08-04 23:05:23 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type Player struct {
|
2024-08-05 14:42:38 -04:00
|
|
|
c chan playReq
|
2024-08-04 23:50:14 -04:00
|
|
|
ctx *oto.Context
|
|
|
|
sampleRate int
|
|
|
|
channels int
|
2024-08-04 23:05:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewPlayer() *Player {
|
2024-08-05 14:42:38 -04:00
|
|
|
p := &Player{
|
|
|
|
c: make(chan playReq, 256),
|
|
|
|
}
|
2024-08-04 23:05:23 -04:00
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2024-08-05 14:42:38 -04:00
|
|
|
func (p *Player) Queue() int {
|
|
|
|
return len(p.c)
|
|
|
|
}
|
|
|
|
|
2024-08-04 23:32:46 -04:00
|
|
|
func (p *Player) initOto(samp, channels int) error {
|
2024-08-04 23:50:14 -04:00
|
|
|
if samp != p.sampleRate || channels != p.channels {
|
|
|
|
if p.ctx != nil {
|
|
|
|
err := p.ctx.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
if p.ctx, err = oto.NewContext(samp, channels, 2, 1024); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-08-08 19:24:33 -04:00
|
|
|
|
|
|
|
p.sampleRate = samp
|
|
|
|
p.channels = channels
|
2024-08-04 23:32:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2024-08-04 23:05:23 -04:00
|
|
|
}
|
|
|
|
|
2024-08-04 23:32:46 -04:00
|
|
|
func (p *Player) playMP3(audio []byte) error {
|
2024-08-11 13:46:43 -04:00
|
|
|
dec, data, err := minimp3.DecodeFull(audio)
|
|
|
|
if err != nil {
|
2024-08-04 23:32:46 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = p.initOto(dec.SampleRate, dec.Channels)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var player = p.ctx.NewPlayer()
|
2024-08-11 14:48:17 -04:00
|
|
|
_, err = player.Write(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-08-04 23:32:46 -04:00
|
|
|
|
|
|
|
<-time.After(time.Second)
|
|
|
|
|
|
|
|
dec.Close()
|
|
|
|
if err = player.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-05 14:42:38 -04:00
|
|
|
type playReq struct {
|
|
|
|
audio []byte
|
|
|
|
mimeType string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Player) Play(audio []byte, mimeType string) {
|
|
|
|
p.c <- playReq{audio, mimeType}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Player) play(req playReq) error {
|
|
|
|
switch req.mimeType {
|
2024-08-04 23:05:23 -04:00
|
|
|
case "audio/mpeg":
|
2024-08-05 14:42:38 -04:00
|
|
|
return p.playMP3(req.audio)
|
2024-08-04 23:05:23 -04:00
|
|
|
case "audio/wav":
|
2024-08-04 23:50:14 -04:00
|
|
|
panic("wav not implemented yet")
|
2024-08-04 23:05:23 -04:00
|
|
|
default:
|
2024-08-05 14:42:38 -04:00
|
|
|
return fmt.Errorf("unknown format %s", req.mimeType)
|
2024-08-04 23:05:23 -04:00
|
|
|
}
|
2024-08-05 14:42:38 -04:00
|
|
|
}
|
2024-08-04 23:05:23 -04:00
|
|
|
|
2024-08-05 14:42:38 -04:00
|
|
|
func (p *Player) Go(done <-chan struct{}) {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
close(p.c)
|
|
|
|
p.ctx.Close()
|
|
|
|
return
|
|
|
|
case r, ok := <-p.c:
|
|
|
|
if !ok {
|
|
|
|
p.ctx.Close()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("> [Q: %d]\r", p.Queue())
|
|
|
|
err := p.play(r)
|
|
|
|
fmt.Printf("\033[2K")
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-04 23:05:23 -04:00
|
|
|
}
|