xair-cli/internal/xair/engine.go

98 lines
2.0 KiB
Go

package xair
import (
"fmt"
"net"
"time"
"github.com/charmbracelet/log"
"github.com/hypebeast/go-osc/osc"
)
type engine struct {
Kind MixerKind
conn *net.UDPConn
mixerAddr *net.UDPAddr
parser parser
addressMap map[string]string
done chan bool
respChan chan *osc.Message
}
// receiveLoop handles incoming OSC messages
func (e *engine) receiveLoop() {
buffer := make([]byte, 4096)
for {
select {
case <-e.done:
return
default:
// Set read timeout to avoid blocking forever
e.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
n, _, err := e.conn.ReadFromUDP(buffer)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
// Timeout is expected, continue loop
continue
}
// Check if we're shutting down to avoid logging expected errors
select {
case <-e.done:
return
default:
log.Errorf("Read error: %v", err)
return
}
}
msg, err := e.parseOSCMessage(buffer[:n])
if err != nil {
log.Errorf("Failed to parse OSC message: %v", err)
continue
}
e.respChan <- msg
}
}
}
// parseOSCMessage parses raw bytes into an OSC message with improved error handling
func (e *engine) parseOSCMessage(data []byte) (*osc.Message, error) {
msg, err := e.parser.Parse(data)
if err != nil {
return nil, err
}
return msg, nil
}
// sendToAddress sends an OSC message to a specific address (enables replying to different ports)
func (e *engine) sendToAddress(addr *net.UDPAddr, oscAddress string, args ...any) error {
msg := osc.NewMessage(oscAddress)
for _, arg := range args {
msg.Append(arg)
}
log.Debugf("Sending to %v: %s", addr, msg.String())
if len(args) > 0 {
log.Debug(" - Arguments: ")
for i, arg := range args {
if i > 0 {
log.Debug(", ")
}
log.Debugf("%v", arg)
}
}
log.Debug("")
data, err := msg.MarshalBinary()
if err != nil {
return fmt.Errorf("failed to marshal message: %v", err)
}
_, err = e.conn.WriteToUDP(data, addr)
return err
}