feat: implement M1 teleport protocol sender

Stream a synthetic test pattern and silent PCM audio over the OBS
Teleport protocol:
- protocol: wire format (Header/ImageHeader/WaveHeader), BT.709 full
  range colour matrix, JPEG encode via turbojpeg cgo, WAVE packet builder
- output: TCP sender with per-connection buffered channels and drop-on-overflow
- discovery: multicast announce via peerdiscovery
- cmd: teleportfling CLI with flags, test-pattern frame generator

Verified end-to-end: OBS discovers and renders the stream with correct
colours and motion.
This commit is contained in:
2026-09-18 18:36:25 +01:00
parent b8b2ba8da1
commit 10fa72b228
11 changed files with 1438 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
// Package discovery wraps schollz/peerdiscovery to broadcast the Teleport
// AnnouncePayload over UDP multicast. The payload tells OBS receivers
// where to connect and what the stream carries. Only the announcer
// (sender) side is implemented; receivers are OBS's job.
package discovery
import (
"encoding/json"
"log"
"os"
"sync"
"github.com/schollz/peerdiscovery"
"teleportfling/internal/protocol"
)
// Announcer periodically broadcasts AnnouncePayload on the LAN multicast
// group until Stop is called.
type Announcer struct {
wg sync.WaitGroup
ch chan struct{}
stopOnce sync.Once
}
// Start launches the multicast announcer. It returns immediately; the
// background goroutine keeps broadcasting until Stop is called.
//
// name is the stream display name (empty → hostname). port is the TCP
// listening port advertised to receivers.
func Start(name string, port int) *Announcer {
a := &Announcer{ch: make(chan struct{})}
if name == "" {
var err error
name, err = os.Hostname()
if err != nil {
name = "TeleportFling"
}
}
payload := protocol.AnnouncePayload{
Name: name,
Port: port,
AudioAndVideo: true,
Version: "0.0.0",
}
b, err := json.Marshal(payload)
if err != nil {
log.Printf("discovery: marshal announce payload: %v", err)
return a
}
a.wg.Add(1)
go func() {
defer a.wg.Done()
_, err := peerdiscovery.Discover(peerdiscovery.Settings{
TimeLimit: -1,
StopChan: a.ch,
Payload: b,
})
// A nil error is expected on normal Stop(); only surface real failures.
if err != nil {
log.Printf("discovery: announce stopped: %v", err)
}
}()
log.Printf("discovery: announcing %q on port %d", name, port)
return a
}
// Stop terminates the background announcer and waits for it to exit.
func (a *Announcer) Stop() {
a.stopOnce.Do(func() { close(a.ch) })
a.wg.Wait()
}