summaryrefslogtreecommitdiff
path: root/soundbox
diff options
context:
space:
mode:
authorxegineering <me@xegineering.eu>2024-11-08 20:56:56 +0100
committerxegineering <me@xegineering.eu>2024-11-10 15:53:44 +0100
commit36a2fda37e837bc39c88a2fa38678b0f3bded041 (patch)
tree892a68802b2df5ab474417964d1512027a5eb9df /soundbox
parente30a1b0768d35ec70e5d2c00e6ae87c0f267b881 (diff)
downloadsoundbox-go-36a2fda37e837bc39c88a2fa38678b0f3bded041.tar
soundbox-go-36a2fda37e837bc39c88a2fa38678b0f3bded041.tar.zst
soundbox-go-36a2fda37e837bc39c88a2fa38678b0f3bded041.zip
Use Go code for output network stream
Calling the external program `ffmpeg` should be avoided completely in the future to make soundbox-go a pure Go code base. `ffmpeg` provides the following functionality to soundbox-go: - web radio input stream transport - re-encoding of the audio stream - output stream transport to soundbox devices The last part should be replaced with this commit as a first step.
Diffstat (limited to 'soundbox')
-rw-r--r--soundbox/interfaces.go2
-rw-r--r--soundbox/stream.go84
2 files changed, 69 insertions, 17 deletions
diff --git a/soundbox/interfaces.go b/soundbox/interfaces.go
index 2854b1a..04839c7 100644
--- a/soundbox/interfaces.go
+++ b/soundbox/interfaces.go
@@ -17,7 +17,7 @@ func getInterface() (net.Interface, error) {
}
for _, iface := range all {
- if iface.Flags & net.FlagUp == 0 {
+ if iface.Flags&net.FlagUp == 0 {
continue
}
addresses, err := iface.Addrs()
diff --git a/soundbox/stream.go b/soundbox/stream.go
index 4b08840..0d6fe5f 100644
--- a/soundbox/stream.go
+++ b/soundbox/stream.go
@@ -2,15 +2,22 @@ package soundbox
import (
"context"
+ "errors"
"fmt"
- "os/exec"
+ "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.
@@ -20,29 +27,74 @@ func StreamURLContext(ctx context.Context, url string, targets []net.HardwareAdd
return err
}
+ ips := make([]net.IP, 0)
+ for _, target := range targets {
+ ip, err := toLinkLocal(target)
+ if err != nil {
+ return err
+ }
+ ips = append(ips, ip)
+ }
+
+ conns := make([]net.Conn, 0)
+ for _, ip := range ips {
+ var d net.Dialer
+ conn, err := d.DialContext(
+ ctx,
+ "tcp6",
+ fmt.Sprintf("[%s%%%s]:%d", ip, iface.Name, streamingPort),
+ )
+ if err != nil {
+ return err
+ }
+ conns = append(conns, conn)
+ }
+ defer func() {
+ for _, conn := range conns {
+ conn.Close()
+ }
+ }()
+
args := []string{
"-re",
"-i",
url,
+ "-acodec",
+ "flac",
+ "-f",
+ "ogg",
+ "-",
}
- for _, target := range targets {
- ip, err := toLinkLocal(target)
+ cmd := exec.CommandContext(ctx, "ffmpeg", args...)
+ 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 {
- return err
+ 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
+ }
}
-
- args = append(args, "-acodec")
- args = append(args, "flac")
- args = append(args, "-f")
- args = append(args, "ogg")
- args = append(args, fmt.Sprintf(
- "tcp://[%s%%%s]:%d",
- ip,
- iface.Name,
- streamingPort,
- ))
}
- return exec.CommandContext(ctx, "ffmpeg", args...).Run()
+ return cmd.Wait()
}