web rtc for webcam, audio, etc.

This commit is contained in:
KingKDot
2026-05-13 20:19:21 -05:00
parent 05fe31f04e
commit bf093f5a7d
20 changed files with 986 additions and 199 deletions
+81 -13
View File
@@ -6,37 +6,85 @@ import (
"time"
)
type FrameWriter interface {
type Kind string
const (
KindDesktop Kind = "desktop"
KindWebcam Kind = "webcam"
KindAudio Kind = "audio"
)
type VideoWriter interface {
WriteH264(nalu []byte, dur time.Duration) error
}
type AudioWriter interface {
WriteAudio(pcm []int16) error
}
type writerEntry struct {
video VideoWriter
audio AudioWriter
}
var (
writersMu sync.RWMutex
writers = map[string]FrameWriter{}
writers = map[string]map[string]writerEntry{} // kind → id → entry
)
func registerWriter(id string, w FrameWriter) {
func registerVideoWriter(kind Kind, id string, w VideoWriter) {
if id == "" || w == nil {
return
}
writersMu.Lock()
writers[id] = w
bucket := writers[string(kind)]
if bucket == nil {
bucket = map[string]writerEntry{}
writers[string(kind)] = bucket
}
entry := bucket[id]
entry.video = w
bucket[id] = entry
writersMu.Unlock()
}
func unregisterWriter(id string) {
func registerAudioWriter(kind Kind, id string, w AudioWriter) {
if id == "" || w == nil {
return
}
writersMu.Lock()
bucket := writers[string(kind)]
if bucket == nil {
bucket = map[string]writerEntry{}
writers[string(kind)] = bucket
}
entry := bucket[id]
entry.audio = w
bucket[id] = entry
writersMu.Unlock()
}
func unregisterWriter(kind Kind, id string) {
if id == "" {
return
}
writersMu.Lock()
delete(writers, id)
if bucket, ok := writers[string(kind)]; ok {
delete(bucket, id)
if len(bucket) == 0 {
delete(writers, string(kind))
}
}
writersMu.Unlock()
}
func IsActive() bool {
// IsActive reports whether any writer of the given kind is registered.
// Callers in capture loops use this as a cheap "should I divert this frame
// to WebRTC?" check before doing more expensive work.
func IsActive(kind Kind) bool {
writersMu.RLock()
defer writersMu.RUnlock()
return len(writers) > 0
return len(writers[string(kind)]) > 0
}
var keyframeWanted atomic.Bool
@@ -49,21 +97,38 @@ func ConsumeKeyframeRequest() bool {
return keyframeWanted.Swap(false)
}
func WriteH264(nalu []byte, dur time.Duration) error {
func WriteH264(kind Kind, nalu []byte, dur time.Duration) error {
if len(nalu) == 0 {
return nil
}
writersMu.RLock()
defer writersMu.RUnlock()
for _, w := range writers {
_ = w.WriteH264(nalu, dur)
bucket := writers[string(kind)]
for _, w := range bucket {
if w.video != nil {
_ = w.video.WriteH264(nalu, dur)
}
}
return nil
}
func WriteAudio(kind Kind, pcm []int16) error {
if len(pcm) == 0 {
return nil
}
writersMu.RLock()
defer writersMu.RUnlock()
bucket := writers[string(kind)]
for _, w := range bucket {
if w.audio != nil {
_ = w.audio.WriteAudio(pcm)
}
}
return nil
}
// Deadass fuck WebRTC bro ts is so confusing
type Options struct {
// (e.g. https://server:5173/api/webrtc/agents/abc/whip).
// (e.g. https://server:5173/api/webrtc/agents/abc/desktop/whip).
WhipURL string
// PublishToken is the bearer token issued by the server.
PublishToken string
@@ -71,6 +136,9 @@ type Options struct {
TLSInsecureSkipVerify bool
// TLSCAPath is an optional custom CA bundle.
TLSCAPath string
// HasVideo / HasAudio select which tracks to add to the peer connection.
HasVideo bool
HasAudio bool
}
type ICECandidate struct {