wip player

This commit is contained in:
2025-03-26 14:38:58 -04:00
parent 53c6bce3dd
commit d47301dbaf
+123 -30
View File
@@ -4,19 +4,26 @@ 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
streamer beep.Streamer
format beep.Format
sampleRate beep.SampleRate
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
@@ -27,48 +34,134 @@ const (
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{
// TODO: variable speaker sample rates
sampleRate: beep.SampleRate(44100),
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) {
func (p *Player) Init(ctx context.Context) error {
p.ctx = ctx
p.state = Stopped
p.streamer = generators.Silence(-1)
speaker.Init(p.sampleRate, p.sampleRate.N(time.Second/10))
}
func (p *Player) ChangeState(desiredState PlayerState) error {
return nil
}
func (p *Player) Play(filePath string) error {
f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file %w", err)
}
streamer, format, err := mp3.Decode(f)
if err != nil {
return fmt.Errorf("failed to decode mp3 %w", err)
}
defer streamer.Close()
// initialize the speaker with the sample rate from the file
err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
// 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(streamer, beep.Callback(func() {
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
}