Files
-rpi-sendspin/third_party/sendspin-go/pkg/sendspin/server_dispatch.go

132 lines
3.3 KiB
Go

// ABOUTME: Message dispatch for incoming client control frames
// ABOUTME: Routes client/time, client/state, client/goodbye, client/command to handlers
package sendspin
import (
"encoding/json"
"log"
"github.com/Sendspin/sendspin-go/pkg/protocol"
)
func (s *Server) handleClientMessage(c *ServerClient, data []byte) {
var msg protocol.Message
if err := json.Unmarshal(data, &msg); err != nil {
log.Printf("Error unmarshaling message: %v", err)
return
}
switch msg.Type {
case "client/time":
s.handleTimeSync(c, msg.Payload)
case "client/state":
s.handleClientState(c, msg.Payload)
case "client/goodbye":
s.handleClientGoodbye(c, msg.Payload)
case "client/command":
s.handleClientCommand(c, msg.Payload)
default:
if s.config.Debug {
log.Printf("Unknown message type: %s", msg.Type)
}
}
}
func (s *Server) handleTimeSync(c *ServerClient, payload interface{}) {
serverRecv := s.getClockMicros()
timeData, err := json.Marshal(payload)
if err != nil {
return
}
var clientTime protocol.ClientTime
if err := json.Unmarshal(timeData, &clientTime); err != nil {
return
}
serverSend := s.getClockMicros()
response := protocol.ServerTime{
ClientTransmitted: clientTime.ClientTransmitted,
ServerReceived: serverRecv,
ServerTransmitted: serverSend,
}
if err := c.Send("server/time", response); err != nil {
if s.config.Debug {
log.Printf("Error sending server/time to %s: %v", c.name, err)
}
}
}
// handleClientState applies a client's player state update per spec.
func (s *Server) handleClientState(c *ServerClient, payload interface{}) {
stateData, err := json.Marshal(payload)
if err != nil {
return
}
var stateMsg protocol.ClientStateMessage
if err := json.Unmarshal(stateData, &stateMsg); err != nil {
return
}
if stateMsg.Player != nil {
c.mu.Lock()
c.state = stateMsg.Player.State
c.volume = stateMsg.Player.Volume
c.muted = stateMsg.Player.Muted
c.mu.Unlock()
if s.config.Debug {
log.Printf("Client %s state: %s (vol: %d, muted: %v)", c.name, stateMsg.Player.State, stateMsg.Player.Volume, stateMsg.Player.Muted)
}
s.defaultGroup.publish(ClientStateChangedEvent{
Client: c,
State: stateMsg.Player.State,
Volume: stateMsg.Player.Volume,
Muted: stateMsg.Player.Muted,
})
}
}
func (s *Server) handleClientCommand(c *ServerClient, payload interface{}) {
data, err := json.Marshal(payload)
if err != nil {
log.Printf("Error marshaling client/command payload: %v", err)
return
}
// client/command payloads are role-keyed: {"controller": {...}, "player": {...}}
var rolePayloads map[string]json.RawMessage
if err := json.Unmarshal(data, &rolePayloads); err != nil {
log.Printf("Error unmarshaling client/command: %v", err)
return
}
for roleFamily, roleData := range rolePayloads {
if err := s.defaultGroup.RouteMessage(c, roleFamily, roleData); err != nil {
if s.config.Debug {
log.Printf("client/command routing error for role %s: %v", roleFamily, err)
}
}
}
}
func (s *Server) handleClientGoodbye(c *ServerClient, payload interface{}) {
goodbyeData, err := json.Marshal(payload)
if err != nil {
return
}
var goodbye protocol.ClientGoodbye
if err := json.Unmarshal(goodbyeData, &goodbye); err != nil {
return
}
log.Printf("Client %s goodbye: %s", c.name, goodbye.Reason)
// Connection close happens in handleConnection's read loop once this returns.
}