🎉 live server seems to be working now
This commit is contained in:
161
third_party/sendspin-go/pkg/sendspin/role_player.go
vendored
Normal file
161
third_party/sendspin-go/pkg/sendspin/role_player.go
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// ABOUTME: PlayerGroupRole handles player stream setup on client join
|
||||
// ABOUTME: Codec negotiation, encoder creation, and stream/start message
|
||||
package sendspin
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/Sendspin/sendspin-go/internal/server"
|
||||
"github.com/Sendspin/sendspin-go/pkg/audio"
|
||||
"github.com/Sendspin/sendspin-go/pkg/protocol"
|
||||
)
|
||||
|
||||
// PlayerRoleConfig configures the PlayerGroupRole.
|
||||
type PlayerRoleConfig struct {
|
||||
SampleRate int
|
||||
Channels int
|
||||
BitDepth int
|
||||
|
||||
// NewEncoder creates an Opus encoder when needed. When nil, Opus
|
||||
// clients fall back to PCM. Typically set to server.NewOpusEncoder.
|
||||
NewEncoder func(sampleRate, channels, chunkSamples int) (*server.OpusEncoder, error)
|
||||
|
||||
// NewFLACEncoder creates a FLAC encoder when needed. When nil, FLAC
|
||||
// clients fall back to PCM.
|
||||
NewFLACEncoder func(sampleRate, channels, bitDepth, blockSize int) (*server.FLACEncoder, error)
|
||||
}
|
||||
|
||||
// PlayerGroupRole handles the "player" role family. On client join it
|
||||
// negotiates a codec, creates any needed encoder/resampler, and sends
|
||||
// stream/start. The audio chunk pipeline (generateAndSendChunk) stays
|
||||
// on Server — this role only handles the per-client setup.
|
||||
type PlayerGroupRole struct {
|
||||
config PlayerRoleConfig
|
||||
}
|
||||
|
||||
// NewPlayerRole creates a PlayerGroupRole with the given config.
|
||||
func NewPlayerRole(config PlayerRoleConfig) *PlayerGroupRole {
|
||||
return &PlayerGroupRole{config: config}
|
||||
}
|
||||
|
||||
func (r *PlayerGroupRole) Role() string { return "player" }
|
||||
|
||||
// OnClientJoin negotiates a codec, creates encoder/resampler if needed,
|
||||
// and sends stream/start to clients advertising the player role.
|
||||
func (r *PlayerGroupRole) OnClientJoin(c *ServerClient) {
|
||||
if !c.HasRole("player") {
|
||||
return
|
||||
}
|
||||
|
||||
if c.capabilities != nil {
|
||||
fmts := make([]string, 0, len(c.capabilities.SupportedFormats))
|
||||
for _, f := range c.capabilities.SupportedFormats {
|
||||
fmts = append(fmts, fmt.Sprintf("%s@%dHz/%dbit/%dch", f.Codec, f.SampleRate, f.BitDepth, f.Channels))
|
||||
}
|
||||
log.Printf("Client %s advertised formats (in order): %v", c.Name(), fmts)
|
||||
} else {
|
||||
log.Printf("Client %s advertised no capabilities", c.Name())
|
||||
}
|
||||
|
||||
codec := negotiateCodec(c, r.config.SampleRate)
|
||||
|
||||
var opusEncoder *server.OpusEncoder
|
||||
var flacEncoder *server.FLACEncoder
|
||||
var resampler *audio.Resampler
|
||||
flacBitDepth := r.config.BitDepth
|
||||
|
||||
switch codec {
|
||||
case "opus":
|
||||
if r.config.SampleRate != 48000 {
|
||||
resampler = audio.NewResampler(r.config.SampleRate, 48000, r.config.Channels)
|
||||
log.Printf("Created resampler: %dHz -> 48kHz for Opus (client: %s)", r.config.SampleRate, c.Name())
|
||||
}
|
||||
|
||||
opusChunkSamples := (48000 * ChunkDurationMs) / 1000
|
||||
if r.config.NewEncoder != nil {
|
||||
encoder, err := r.config.NewEncoder(48000, r.config.Channels, opusChunkSamples)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create Opus encoder for %s, falling back to PCM: %v", c.Name(), err)
|
||||
codec = "pcm"
|
||||
resampler = nil
|
||||
} else {
|
||||
opusEncoder = encoder
|
||||
}
|
||||
} else {
|
||||
log.Printf("No Opus encoder factory for %s, falling back to PCM", c.Name())
|
||||
codec = "pcm"
|
||||
resampler = nil
|
||||
}
|
||||
case "flac":
|
||||
if c.capabilities != nil {
|
||||
for _, format := range c.capabilities.SupportedFormats {
|
||||
if format.Codec == "flac" && format.SampleRate == r.config.SampleRate {
|
||||
flacBitDepth = format.BitDepth
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
chunkSamples := (r.config.SampleRate * ChunkDurationMs) / 1000
|
||||
if r.config.NewFLACEncoder != nil {
|
||||
encoder, err := r.config.NewFLACEncoder(r.config.SampleRate, r.config.Channels, flacBitDepth, chunkSamples)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create FLAC encoder for %s, falling back to PCM: %v", c.Name(), err)
|
||||
codec = "pcm"
|
||||
} else {
|
||||
flacEncoder = encoder
|
||||
log.Printf("FLAC encoder for %s: %dHz/%dbit/%dch", c.Name(), r.config.SampleRate, flacBitDepth, r.config.Channels)
|
||||
}
|
||||
} else {
|
||||
log.Printf("No FLAC encoder factory for %s, falling back to PCM", c.Name())
|
||||
codec = "pcm"
|
||||
}
|
||||
}
|
||||
|
||||
// Create buffer tracker from client's advertised buffer_capacity.
|
||||
bufferCapacity := 1048576 // 1MB default
|
||||
if c.capabilities != nil && c.capabilities.BufferCapacity > 0 {
|
||||
bufferCapacity = c.capabilities.BufferCapacity
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.codec = codec
|
||||
c.opusEncoder = opusEncoder
|
||||
c.flacEncoder = flacEncoder
|
||||
c.resampler = resampler
|
||||
c.bufferTracker = NewBufferTracker(bufferCapacity)
|
||||
c.mu.Unlock()
|
||||
|
||||
log.Printf("Added client %s with codec %s", c.Name(), codec)
|
||||
|
||||
streamSampleRate := r.config.SampleRate
|
||||
streamBitDepth := r.config.BitDepth
|
||||
if codec == "opus" {
|
||||
streamSampleRate = 48000
|
||||
streamBitDepth = 16
|
||||
} else if codec == "flac" {
|
||||
streamBitDepth = flacBitDepth
|
||||
}
|
||||
|
||||
var codecHeaderB64 string
|
||||
if codec == "flac" && flacEncoder != nil {
|
||||
codecHeaderB64 = base64.StdEncoding.EncodeToString(flacEncoder.CodecHeader())
|
||||
}
|
||||
|
||||
streamStart := protocol.StreamStart{
|
||||
Player: &protocol.StreamStartPlayer{
|
||||
Codec: codec,
|
||||
SampleRate: streamSampleRate,
|
||||
Channels: r.config.Channels,
|
||||
BitDepth: streamBitDepth,
|
||||
CodecHeader: codecHeaderB64,
|
||||
},
|
||||
}
|
||||
|
||||
if err := c.Send("stream/start", streamStart); err != nil {
|
||||
log.Printf("Error sending stream/start to %s: %v", c.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PlayerGroupRole) OnClientLeave(id string, name string) {}
|
||||
Reference in New Issue
Block a user