Replaces the broken anacrolix WebTorrent path with a custom WebRTC peer that the browser drives directly. Architecture matches plan/clever- weaving-dove.md (Fase 2 + 3 + 6 of the streaming pivot). - engine/wire: shared 12-byte binary frame format (Hello / RangeReq / RangeData / RangeEnd / Cancel / Ping / Pong / SeekHint). Roundtrip + oversized-frame rejection tests. - agent/signal_client: SSE consumer + POST sender for SDP/ICE relay through /api/internal/stream/signal/<id>; auto-reconnects. - engine/webrtc_stream: pion v4 PeerConnection + DataChannel pump. Reads file via os.ReadAt, chunks RangeData at 16 KiB, honours app- level backpressure with SetBufferedAmountLowThreshold. - cmd/daemon dispatcher learns mode webrtc_stream + new webrtcSessionRegistry tracks per-session cancel funcs for clean shutdown. - engine/probe + hwaccel + transcoder: foundation for Fase 2.5 (codec detection, NVENC/QSV/VAAPI/VideoToolbox autodetection, ffmpeg pipe wrapper to fragmented MP4). Integration into webrtc_stream still pending. - pion/webrtc/v4 promoted from indirect to direct dep. End-to-end against unarr-dev confirms a 122 MB 1080p H.264 / AAC MP4 plays in Chrome with the new pipeline.
62 lines
1.8 KiB
Go
62 lines
1.8 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"sync"
|
|
)
|
|
|
|
// webrtcRegistry tracks per-session cancel funcs for active custom WebRTC
|
|
// streams (engine.RunWebRTCStream goroutines). Each session lives only as
|
|
// long as its DataChannel; the registry exists so duplicate sync responses
|
|
// don't double-spawn the same session and so daemon shutdown can drain.
|
|
var webrtcRegistry = &webrtcSessionRegistry{
|
|
cancels: make(map[string]context.CancelFunc),
|
|
}
|
|
|
|
type webrtcSessionRegistry struct {
|
|
mu sync.Mutex
|
|
cancels map[string]context.CancelFunc
|
|
}
|
|
|
|
func (r *webrtcSessionRegistry) has(sessionID string) bool {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
_, ok := r.cancels[sessionID]
|
|
return ok
|
|
}
|
|
|
|
func (r *webrtcSessionRegistry) add(sessionID string, cancel context.CancelFunc) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.cancels[sessionID] = cancel
|
|
}
|
|
|
|
func (r *webrtcSessionRegistry) remove(sessionID string) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
delete(r.cancels, sessionID)
|
|
}
|
|
|
|
// cancelAllWebRTCSessions cancels every running session. Called on daemon
|
|
// shutdown so pion peers and SSE consumers exit cleanly.
|
|
func cancelAllWebRTCSessions() {
|
|
webrtcRegistry.mu.Lock()
|
|
cancels := make([]context.CancelFunc, 0, len(webrtcRegistry.cancels))
|
|
for _, c := range webrtcRegistry.cancels {
|
|
cancels = append(cancels, c)
|
|
}
|
|
webrtcRegistry.cancels = make(map[string]context.CancelFunc)
|
|
webrtcRegistry.mu.Unlock()
|
|
for _, c := range cancels {
|
|
c()
|
|
}
|
|
}
|
|
|
|
// stdLogger is a tiny adapter so engine.RunWebRTCStream can log through the
|
|
// standard library logger without pulling in a logging dependency.
|
|
type stdLogger struct{}
|
|
|
|
func (stdLogger) Infof(format string, args ...any) { log.Printf(format, args...) }
|
|
func (stdLogger) Warnf(format string, args ...any) { log.Printf("WARN: "+format, args...) }
|
|
func (stdLogger) Errorf(format string, args ...any) { log.Printf("ERROR: "+format, args...) }
|