168 lines
3.8 KiB
Go
168 lines
3.8 KiB
Go
package player
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/gopxl/beep"
|
|
"github.com/gopxl/beep/effects"
|
|
"github.com/gopxl/beep/generators"
|
|
"github.com/gopxl/beep/mp3"
|
|
"github.com/gopxl/beep/speaker"
|
|
)
|
|
|
|
type Player struct {
|
|
ctx context.Context
|
|
state PlayerState
|
|
currentFile os.File
|
|
format beep.Format
|
|
baseStreamer beep.Streamer
|
|
resampled beep.Streamer
|
|
control beep.Ctrl
|
|
volume effects.Volume
|
|
queue []string
|
|
}
|
|
|
|
type PlayerState int
|
|
|
|
const (
|
|
Playing PlayerState = iota
|
|
Paused
|
|
Stopped
|
|
)
|
|
|
|
const testQueue = []string{
|
|
"test_data/music_library_test/gnomed.mp3",
|
|
"test_data/music_library_test/01 Some Chords.mp3",
|
|
"test_data/music_library_test/03 anything.mp3",
|
|
}
|
|
|
|
func NewPlayer() (*Player, error) {
|
|
return &Player{
|
|
state: Stopped,
|
|
baseStreamer: generatorsilence(-1),
|
|
// Default sample rate
|
|
// TODO: read this from config
|
|
fileFormat: beep.Format{},
|
|
queue: testQueue,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Player) Init(ctx context.Context) error {
|
|
p.ctx = ctx
|
|
p.state = Stopped
|
|
p.streamer = generators.Silence(-1)
|
|
|
|
// Initialize speaker
|
|
// TODO: allow user to change buffer size and speaker sample rate
|
|
err := speaker.Init(p.speakerSampleRate, p.speakerSampleRate.N(time.Second/10))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize speaker %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: proper state management extracted to function
|
|
func (p *Player) changeState(desiredState PlayerState) error {
|
|
return nil
|
|
}
|
|
|
|
// reads a file and creates a streamer resampled to match the speaker
|
|
func (p *Player) CreateStreamerFromFile(filePath string) (beep.Streamer, error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file %w", err)
|
|
}
|
|
|
|
streamer, format, err := mp3.Decode(f)
|
|
p.fileFormat = format
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode mp3 %w", err)
|
|
}
|
|
|
|
// resample file stream to match speaker
|
|
// TODO: variable resample quality
|
|
resampled := beep.Resample(4, p.fileFormat.SampleRate, p.speakerSampleRate, streamer)
|
|
// wrap in ctrl streamer to allow play/pause
|
|
ctrl := &beep.Ctrl{Streamer: resampled}
|
|
// wrap in volume streamer
|
|
volume := &effects.Volume{
|
|
Streamer: ctrl,
|
|
Base: 2,
|
|
Volume: 0,
|
|
Silent: false,
|
|
}
|
|
|
|
return volume, nil
|
|
}
|
|
|
|
func (p *Player) Play(streamer beep.Streamer) error {
|
|
p.streamer = streamer
|
|
p.state = Playing
|
|
// hangs until song finishes playing
|
|
done := make(chan bool)
|
|
speaker.Play(beep.Seq(p.streamer, beep.Callback(func() {
|
|
p.state = Paused //called when streamer finishes
|
|
done <- true
|
|
})))
|
|
|
|
<-done
|
|
return nil
|
|
}
|
|
|
|
// TODO: reduce dupilcation in pause/resume functions
|
|
func (p *Player) Pause() error {
|
|
ctrl := &beep.Ctrl{}
|
|
// checking if current streamer is a ctrl type, if not, wrap it in one
|
|
if (reflect.TypeOf(p.streamer) != reflect.TypeOf(beep.Ctrl{})) {
|
|
ctrl = &beep.Ctrl{Streamer: p.streamer}
|
|
}
|
|
speaker.Lock()
|
|
ctrl.Paused = true
|
|
speaker.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// TODO: reduce dupilcation in pause/resume functions
|
|
func (p *Player) Resume() error {
|
|
ctrl := &beep.Ctrl{}
|
|
// checking if current streamer is a ctrl type, if not, wrap it in one
|
|
if (reflect.TypeOf(p.streamer) != reflect.TypeOf(beep.Ctrl{})) {
|
|
ctrl = &beep.Ctrl{Streamer: p.streamer}
|
|
}
|
|
speaker.Lock()
|
|
ctrl.Paused = false
|
|
speaker.Unlock()
|
|
return nil
|
|
}
|
|
|
|
//Paraprasing info from the beep docs here:
|
|
/*
|
|
To INCREASE volume by 1 means to multiply the signal by Base.
|
|
Volume = 0 means unchanged volume.
|
|
Positive Volume value means increasing volume
|
|
Negative Volume value means decreasing volume
|
|
*/
|
|
// TODO: implement max/ min volume values
|
|
func (p *Player) SetVolume() error {
|
|
volume := &effects.Volume{
|
|
Streamer: p.streamer,
|
|
Base: 2,
|
|
Volume: 0,
|
|
Silent: false,
|
|
}
|
|
p.Play(volume)
|
|
return nil
|
|
}
|
|
|
|
func (p *Player) MuteToggle() error {
|
|
|
|
p.streamer.Silent = !p.Streamer.
|
|
p.Play(volume)
|
|
return nil
|
|
}
|