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:
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user