got bulk of player api
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
dev:
|
dev:
|
||||||
wails dev -tags webkit2_41
|
WEBKIT_DISABLE_DMABUF_RENDERER=1 wails dev -tags webkit2_41
|
||||||
|
|
||||||
build:
|
build:
|
||||||
wails build
|
wails build
|
||||||
|
|||||||
+61
-65
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gopxl/beep"
|
"github.com/gopxl/beep"
|
||||||
@@ -15,15 +14,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Player struct {
|
type Player struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
state PlayerState
|
state PlayerState
|
||||||
currentFile os.File
|
currentFile *os.File
|
||||||
format beep.Format
|
format beep.Format
|
||||||
baseStreamer beep.Streamer
|
baseStreamer beep.Streamer
|
||||||
resampled beep.Streamer
|
resampled beep.Streamer
|
||||||
control beep.Ctrl
|
control *beep.Ctrl
|
||||||
volume effects.Volume
|
volume *effects.Volume
|
||||||
queue []string
|
speakerStreamer beep.Streamer
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerState int
|
type PlayerState int
|
||||||
@@ -34,34 +33,47 @@ const (
|
|||||||
Stopped
|
Stopped
|
||||||
)
|
)
|
||||||
|
|
||||||
const testQueue = []string{
|
var speakerSampleRate = beep.SampleRate(44100)
|
||||||
"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) {
|
func NewPlayer() (*Player, error) {
|
||||||
return &Player{
|
return &Player{
|
||||||
state: Stopped,
|
state: Stopped,
|
||||||
baseStreamer: generatorsilence(-1),
|
baseStreamer: generators.Silence(-1),
|
||||||
// Default sample rate
|
format: beep.Format{
|
||||||
// TODO: read this from config
|
SampleRate: speakerSampleRate,
|
||||||
fileFormat: beep.Format{},
|
},
|
||||||
queue: testQueue,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Init(ctx context.Context) error {
|
func (p *Player) Init(ctx context.Context) error {
|
||||||
p.ctx = ctx
|
p.ctx = ctx
|
||||||
p.state = Stopped
|
|
||||||
p.streamer = generators.Silence(-1)
|
|
||||||
|
|
||||||
// Initialize speaker
|
// Initialize speaker
|
||||||
// TODO: allow user to change buffer size and speaker sample rate
|
// TODO: allow user to change buffer size and speaker sample rate
|
||||||
err := speaker.Init(p.speakerSampleRate, p.speakerSampleRate.N(time.Second/10))
|
err := speaker.Init(p.format.SampleRate, p.format.SampleRate.N(time.Second/10))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize speaker %w", err)
|
return fmt.Errorf("failed to initialize speaker %w", err)
|
||||||
}
|
}
|
||||||
|
p.updateStreamers(p.baseStreamer, p.format.SampleRate)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) updateStreamers(newBaseStreamer beep.Streamer, sr beep.SampleRate) error {
|
||||||
|
p.baseStreamer = newBaseStreamer
|
||||||
|
// resample file stream to match speaker
|
||||||
|
// TODO: variable resample quality
|
||||||
|
p.resampled = beep.Resample(4, sr, speakerSampleRate, p.baseStreamer)
|
||||||
|
// wrap in ctrl streamer to allow play/pause
|
||||||
|
p.control = &beep.Ctrl{Streamer: p.resampled}
|
||||||
|
// wrap in volume streamer
|
||||||
|
p.volume = &effects.Volume{
|
||||||
|
Streamer: p.control,
|
||||||
|
Base: 2,
|
||||||
|
Volume: 0,
|
||||||
|
Silent: false,
|
||||||
|
}
|
||||||
|
p.speakerStreamer = p.volume
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -72,40 +84,27 @@ func (p *Player) changeState(desiredState PlayerState) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reads a file and creates a streamer resampled to match the speaker
|
// reads a file and creates a streamer resampled to match the speaker
|
||||||
func (p *Player) CreateStreamerFromFile(filePath string) (beep.Streamer, error) {
|
func (p *Player) LoadFile(filePath string) error {
|
||||||
f, err := os.Open(filePath)
|
f, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open file %w", err)
|
return fmt.Errorf("failed to open file %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
streamer, format, err := mp3.Decode(f)
|
streamer, format, err := mp3.Decode(f)
|
||||||
p.fileFormat = format
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode mp3 %w", err)
|
return fmt.Errorf("failed to decode mp3 %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resample file stream to match speaker
|
p.updateStreamers(streamer, format.SampleRate)
|
||||||
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Play(streamer beep.Streamer) error {
|
func (p *Player) Play() error {
|
||||||
p.streamer = streamer
|
|
||||||
p.state = Playing
|
p.state = Playing
|
||||||
// hangs until song finishes playing
|
// hangs until song finishes playing
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
speaker.Play(beep.Seq(p.streamer, beep.Callback(func() {
|
speaker.Play(beep.Seq(p.speakerStreamer, beep.Callback(func() {
|
||||||
p.state = Paused //called when streamer finishes
|
p.state = Paused //called when streamer finishes
|
||||||
done <- true
|
done <- true
|
||||||
})))
|
})))
|
||||||
@@ -116,26 +115,16 @@ func (p *Player) Play(streamer beep.Streamer) error {
|
|||||||
|
|
||||||
// TODO: reduce dupilcation in pause/resume functions
|
// TODO: reduce dupilcation in pause/resume functions
|
||||||
func (p *Player) Pause() error {
|
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()
|
speaker.Lock()
|
||||||
ctrl.Paused = true
|
p.control.Paused = true
|
||||||
speaker.Unlock()
|
speaker.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: reduce dupilcation in pause/resume functions
|
// TODO: reduce dupilcation in pause/resume functions
|
||||||
func (p *Player) Resume() error {
|
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()
|
speaker.Lock()
|
||||||
ctrl.Paused = false
|
p.control.Paused = false
|
||||||
speaker.Unlock()
|
speaker.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -148,20 +137,27 @@ Positive Volume value means increasing volume
|
|||||||
Negative Volume value means decreasing volume
|
Negative Volume value means decreasing volume
|
||||||
*/
|
*/
|
||||||
// TODO: implement max/ min volume values
|
// TODO: implement max/ min volume values
|
||||||
func (p *Player) SetVolume() error {
|
func (p *Player) SetVolume(desiredVolume UserVolume) error {
|
||||||
volume := &effects.Volume{
|
if desiredVolume > MaxUserVol {
|
||||||
Streamer: p.streamer,
|
p.volume.Silent = false
|
||||||
Base: 2,
|
p.volume.Volume = float64(MaxUserVol.ToPlayerVolume())
|
||||||
Volume: 0,
|
} else if desiredVolume < MinUserVol {
|
||||||
Silent: false,
|
p.volume.Volume = float64(MinUserVol.ToPlayerVolume())
|
||||||
|
p.volume.Silent = true
|
||||||
|
} else {
|
||||||
|
p.volume.Silent = false
|
||||||
|
p.volume.Volume = float64(desiredVolume.ToPlayerVolume())
|
||||||
}
|
}
|
||||||
p.Play(volume)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (p *Player) ChangeVolume(deltaVolume int) error {
|
||||||
|
return p.SetVolume(p.getUserVolume() + UserVolume(deltaVolume))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player) getUserVolume() UserVolume {
|
||||||
|
return PlayerVolume(p.volume.Volume).ToUserVolume()
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Player) MuteToggle() error {
|
func (p *Player) MuteToggle() error {
|
||||||
|
|
||||||
p.streamer.Silent = !p.Streamer.
|
|
||||||
p.Play(volume)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testQueue = []string{
|
||||||
|
"./test_data/music_library_test/other_music/03 PONPONPON.mp3",
|
||||||
|
"./test_data/music_library_test/01 Some Chords.mp3",
|
||||||
|
"./test_data/music_library_test/03 anything.mp3",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlayer(t *testing.T) {
|
||||||
|
t.Logf("Starting test")
|
||||||
|
p, err := NewPlayer()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not create player\n%s", err.Error())
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
t.Logf("initializing player")
|
||||||
|
err = p.Init(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not initialize player\n%s", err.Error())
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, track := range testQueue {
|
||||||
|
t.Logf("loading file: %s", track)
|
||||||
|
err = p.LoadFile(track)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not load file\n%s\n%s", track, err.Error())
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
err = p.Play()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not play file\n%s\n%s", track, err.Error())
|
||||||
|
t.Failed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package player
|
||||||
|
|
||||||
|
type UserVolume int
|
||||||
|
type PlayerVolume float64
|
||||||
|
|
||||||
|
const MinUserVol UserVolume = 0
|
||||||
|
const MaxUserVol UserVolume = 100
|
||||||
|
|
||||||
|
const MinPlayerVol PlayerVolume = -10
|
||||||
|
const MaxPlayerVol PlayerVolume = 10
|
||||||
|
|
||||||
|
func (oldVol UserVolume) ToPlayerVolume() PlayerVolume {
|
||||||
|
var newVol PlayerVolume
|
||||||
|
|
||||||
|
if oldVol >= MinUserVol && oldVol <= MaxUserVol {
|
||||||
|
ratio := PlayerVolume(oldVol-MinUserVol) / PlayerVolume(MaxUserVol-MinUserVol)
|
||||||
|
newVol = ratio*(MaxPlayerVol-MinPlayerVol) + MinPlayerVol
|
||||||
|
}
|
||||||
|
return newVol
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oldVolFloat PlayerVolume) ToUserVolume() UserVolume {
|
||||||
|
var newVol UserVolume
|
||||||
|
|
||||||
|
if oldVolFloat >= MinPlayerVol && oldVolFloat <= MaxPlayerVol {
|
||||||
|
ratio := (oldVolFloat - MinPlayerVol) / (MaxPlayerVol - MinPlayerVol)
|
||||||
|
newVol = UserVolume(ratio*PlayerVolume(MaxUserVol-MinUserVol)) + MinUserVol
|
||||||
|
}
|
||||||
|
return newVol
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user