package soundbox import ( "context" "errors" "io" "net" "os/exec" "time" ) // streamingPort is the default network port a soundbox is listening to for // incoming audio stream data. const streamingPort = 5316 const bufferSize = 20 const writeTimeout = 1 * time.Second // StreamURLContext streams audio from a given URL to one or multiple soundbox // devices. The devices are referenced via their MAC addresses given by the // targets argument. The ctx argument is passed to cancel the streaming. func StreamURLContext(ctx context.Context, url string, targets []net.HardwareAddr) error { conns := make([]net.Conn, 0) for _, target := range targets { conn, err := dialContext(ctx, target) if err != nil { return err } conns = append(conns, conn) } defer func() { for _, conn := range conns { conn.Close() } }() cmd := exec.CommandContext( ctx, "ffmpeg", "-re", "-i", url, "-acodec", "flac", "-f", "ogg", "-", ) stdout, err := cmd.StdoutPipe() if err != nil { return err } err = cmd.Start() if err != nil { return err } for { buffer := make([]byte, bufferSize) i, err := stdout.Read(buffer) if err != nil { if errors.Is(err, io.EOF) { break } else { return err } } for _, conn := range conns { conn.SetDeadline(time.Now().Add(writeTimeout)) _, err = conn.Write(buffer[:i]) if err != nil { return err } } } return cmd.Wait() }