202 lines
3.9 KiB
Go
202 lines
3.9 KiB
Go
|
|
//go:build linux
|
||
|
|
|
||
|
|
package audio
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/gordonklaus/portaudio"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Player handles audio playback using PortAudio
|
||
|
|
type Player struct {
|
||
|
|
stream *portaudio.Stream
|
||
|
|
volume float32
|
||
|
|
muted bool
|
||
|
|
mu sync.Mutex
|
||
|
|
running bool
|
||
|
|
stopChan chan struct{}
|
||
|
|
|
||
|
|
// User buffers for mixing
|
||
|
|
userBuffers map[uint16][]int16
|
||
|
|
// User settings
|
||
|
|
userSettings map[uint16]*UserSettings
|
||
|
|
bufferMu sync.Mutex
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewPlayer() (*Player, error) {
|
||
|
|
if err := initPortAudio(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
p := &Player{
|
||
|
|
volume: 1.0,
|
||
|
|
muted: false,
|
||
|
|
stopChan: make(chan struct{}),
|
||
|
|
userBuffers: make(map[uint16][]int16),
|
||
|
|
userSettings: make(map[uint16]*UserSettings),
|
||
|
|
}
|
||
|
|
|
||
|
|
return p, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) Start() error {
|
||
|
|
p.mu.Lock()
|
||
|
|
if p.running {
|
||
|
|
p.mu.Unlock()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create stream (Mono, 48kHz, 16-bit)
|
||
|
|
// We'll use a callback-based stream for lower latency
|
||
|
|
var err error
|
||
|
|
p.stream, err = portaudio.OpenDefaultStream(0, 1, 48000, frameSamples, p.processAudio)
|
||
|
|
if err != nil {
|
||
|
|
p.mu.Unlock()
|
||
|
|
return fmt.Errorf("failed to open portaudio stream: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := p.stream.Start(); err != nil {
|
||
|
|
p.stream.Close()
|
||
|
|
p.mu.Unlock()
|
||
|
|
return fmt.Errorf("failed to start portaudio stream: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
p.running = true
|
||
|
|
p.mu.Unlock()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) Stop() {
|
||
|
|
p.mu.Lock()
|
||
|
|
defer p.mu.Unlock()
|
||
|
|
if !p.running {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
p.running = false
|
||
|
|
if p.stream != nil {
|
||
|
|
p.stream.Abort()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) Close() {
|
||
|
|
p.Stop()
|
||
|
|
p.mu.Lock()
|
||
|
|
if p.stream != nil {
|
||
|
|
p.stream.Close()
|
||
|
|
}
|
||
|
|
p.mu.Unlock()
|
||
|
|
terminatePortAudio()
|
||
|
|
}
|
||
|
|
|
||
|
|
// processAudio is the PortAudio callback
|
||
|
|
func (p *Player) processAudio(out []int16) {
|
||
|
|
p.bufferMu.Lock()
|
||
|
|
defer p.bufferMu.Unlock()
|
||
|
|
|
||
|
|
// Initial silence
|
||
|
|
for i := range out {
|
||
|
|
out[i] = 0
|
||
|
|
}
|
||
|
|
|
||
|
|
if p.muted {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
p.mu.Lock()
|
||
|
|
vol := p.volume
|
||
|
|
p.mu.Unlock()
|
||
|
|
|
||
|
|
mixed := make([]int32, len(out))
|
||
|
|
|
||
|
|
for id, buf := range p.userBuffers {
|
||
|
|
if len(buf) > 0 {
|
||
|
|
toTake := len(out)
|
||
|
|
if len(buf) < toTake {
|
||
|
|
toTake = len(buf)
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i < toTake; i++ {
|
||
|
|
sample := int32(buf[i])
|
||
|
|
if settings, ok := p.userSettings[id]; ok {
|
||
|
|
sample = int32(float32(sample) * settings.Volume)
|
||
|
|
}
|
||
|
|
mixed[i] += sample
|
||
|
|
}
|
||
|
|
|
||
|
|
// Advance buffer
|
||
|
|
if len(buf) <= len(out) {
|
||
|
|
delete(p.userBuffers, id)
|
||
|
|
} else {
|
||
|
|
p.userBuffers[id] = buf[len(out):]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply master volume and clip
|
||
|
|
for i := 0; i < len(out); i++ {
|
||
|
|
val := int32(float32(mixed[i]) * vol)
|
||
|
|
if val > 32767 {
|
||
|
|
val = 32767
|
||
|
|
} else if val < -32768 {
|
||
|
|
val = -32768
|
||
|
|
}
|
||
|
|
out[i] = int16(val)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) PlayPCM(senderID uint16, samples []int16) {
|
||
|
|
p.bufferMu.Lock()
|
||
|
|
defer p.bufferMu.Unlock()
|
||
|
|
|
||
|
|
if settings, ok := p.userSettings[senderID]; ok && settings.Muted {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
p.userBuffers[senderID] = append(p.userBuffers[senderID], samples...)
|
||
|
|
if len(p.userBuffers[senderID]) > 48000*2 {
|
||
|
|
drop := len(p.userBuffers[senderID]) - 48000
|
||
|
|
p.userBuffers[senderID] = p.userBuffers[senderID][drop:]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) SetVolume(vol float32) {
|
||
|
|
p.mu.Lock()
|
||
|
|
defer p.mu.Unlock()
|
||
|
|
p.volume = vol
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) SetMuted(muted bool) {
|
||
|
|
p.mu.Lock()
|
||
|
|
defer p.mu.Unlock()
|
||
|
|
p.muted = muted
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) SetUserVolume(clientID uint16, vol float32) {
|
||
|
|
p.bufferMu.Lock()
|
||
|
|
defer p.bufferMu.Unlock()
|
||
|
|
if _, ok := p.userSettings[clientID]; !ok {
|
||
|
|
p.userSettings[clientID] = &UserSettings{Volume: 1.0, Muted: false}
|
||
|
|
}
|
||
|
|
p.userSettings[clientID].Volume = vol
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) SetUserMuted(clientID uint16, muted bool) {
|
||
|
|
p.bufferMu.Lock()
|
||
|
|
defer p.bufferMu.Unlock()
|
||
|
|
if _, ok := p.userSettings[clientID]; !ok {
|
||
|
|
p.userSettings[clientID] = &UserSettings{Volume: 1.0, Muted: false}
|
||
|
|
}
|
||
|
|
p.userSettings[clientID].Muted = muted
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *Player) GetUserSettings(clientID uint16) (float32, bool) {
|
||
|
|
p.bufferMu.Lock()
|
||
|
|
defer p.bufferMu.Unlock()
|
||
|
|
if settings, ok := p.userSettings[clientID]; ok {
|
||
|
|
return settings.Volume, settings.Muted
|
||
|
|
}
|
||
|
|
return 1.0, false
|
||
|
|
}
|