diff --git a/.claude/memory b/.claude/memory index 1c128f07d..c45bdff2b 120000 --- a/.claude/memory +++ b/.claude/memory @@ -1 +1 @@ -../.openclaw/memory \ No newline at end of file +../memory \ No newline at end of file diff --git a/.claude/skills b/.claude/skills index a8b71e9a5..42c5394a1 120000 --- a/.claude/skills +++ b/.claude/skills @@ -1 +1 @@ -../.openclaw/skills \ No newline at end of file +../skills \ No newline at end of file diff --git a/.codex/memory b/.codex/memory index 1c128f07d..c45bdff2b 120000 --- a/.codex/memory +++ b/.codex/memory @@ -1 +1 @@ -../.openclaw/memory \ No newline at end of file +../memory \ No newline at end of file diff --git a/.codex/skills b/.codex/skills index a8b71e9a5..42c5394a1 120000 --- a/.codex/skills +++ b/.codex/skills @@ -1 +1 @@ -../.openclaw/skills \ No newline at end of file +../skills \ No newline at end of file diff --git a/.kiro/memory b/.kiro/memory index 1c128f07d..c45bdff2b 120000 --- a/.kiro/memory +++ b/.kiro/memory @@ -1 +1 @@ -../.openclaw/memory \ No newline at end of file +../memory \ No newline at end of file diff --git a/.kiro/skills b/.kiro/skills index a8b71e9a5..42c5394a1 120000 --- a/.kiro/skills +++ b/.kiro/skills @@ -1 +1 @@ -../.openclaw/skills \ No newline at end of file +../skills \ No newline at end of file diff --git a/.openclaw/memory b/.openclaw/memory new file mode 120000 index 000000000..c45bdff2b --- /dev/null +++ b/.openclaw/memory @@ -0,0 +1 @@ +../memory \ No newline at end of file diff --git a/.openclaw/skills b/.openclaw/skills new file mode 120000 index 000000000..42c5394a1 --- /dev/null +++ b/.openclaw/skills @@ -0,0 +1 @@ +../skills \ No newline at end of file diff --git a/docs/proxy/proxy-load-balancer.md b/docs/proxy/proxy-load-balancer.md index 4ec3c9d66..a0266af78 100644 --- a/docs/proxy/proxy-load-balancer.md +++ b/docs/proxy/proxy-load-balancer.md @@ -53,14 +53,14 @@ Both implementations maintain stream-to-server mappings to ensure stream consist The load balancer uses a clean interface-based architecture: -**Core Interface**: `SRSLoadBalancer` +**Core Interface**: `OriginLoadBalancer` - Initialization and lifecycle management - Server registration and updates - Stream routing (Pick operation) - Protocol-specific state management (HLS, WebRTC) **Data Models**: -- `SRSServer`: Backend origin server representation +- `OriginServer`: Backend origin server representation - `HLSPlayStream`: Interface for HLS streaming sessions - `RTCConnection`: Interface for WebRTC connections diff --git a/internal/lb/debug.go b/internal/lb/debug.go index 9dab03d82..50cf8fd80 100644 --- a/internal/lb/debug.go +++ b/internal/lb/debug.go @@ -13,7 +13,7 @@ import ( ) // NewDefaultSRSForDebugging initialize the default SRS media server, for debugging only. -func NewDefaultSRSForDebugging(environment env.ProxyEnvironment) (*SRSServer, error) { +func NewDefaultSRSForDebugging(environment env.ProxyEnvironment) (*OriginServer, error) { if environment.DefaultBackendEnabled() != "on" { return nil, nil } @@ -25,7 +25,7 @@ func NewDefaultSRSForDebugging(environment env.ProxyEnvironment) (*SRSServer, er return nil, fmt.Errorf("empty default backend rtmp") } - server := NewSRSServer(func(srs *SRSServer) { + server := NewOriginServer(func(srs *OriginServer) { srs.IP = environment.DefaultBackendIP() srs.RTMP = []string{environment.DefaultBackendRTMP()} srs.ServerID = fmt.Sprintf("default-%v", logger.GenerateContextID()) diff --git a/internal/lb/lb.go b/internal/lb/lb.go index 3c097c7f1..cb841fbc1 100644 --- a/internal/lb/lb.go +++ b/internal/lb/lb.go @@ -19,8 +19,8 @@ const HLSAliveDuration = 120 * time.Second // If WebRTC streaming update in this duration, it's alive. const RTCAliveDuration = 120 * time.Second -// SRSServer represents a backend origin server. -type SRSServer struct { +// OriginServer represents a backend origin server. +type OriginServer struct { // The server IP. IP string `json:"ip,omitempty"` // The server device ID, configured by user. @@ -45,15 +45,15 @@ type SRSServer struct { UpdatedAt time.Time `json:"update_at,omitempty"` } -func (v *SRSServer) ID() string { +func (v *OriginServer) ID() string { return fmt.Sprintf("%v-%v-%v", v.ServerID, v.ServiceID, v.PID) } -func (v *SRSServer) String() string { +func (v *OriginServer) String() string { return fmt.Sprintf("%v", v) } -func (v *SRSServer) Format(f fmt.State, c rune) { +func (v *OriginServer) Format(f fmt.State, c rune) { switch c { case 'v', 's': if f.Flag('+') { @@ -87,8 +87,8 @@ func (v *SRSServer) Format(f fmt.State, c rune) { } } -func NewSRSServer(opts ...func(*SRSServer)) *SRSServer { - v := &SRSServer{} +func NewOriginServer(opts ...func(*OriginServer)) *OriginServer { + v := &OriginServer{} for _, opt := range opts { opt(v) } @@ -109,14 +109,14 @@ type RTCConnection interface { GetUfrag() string } -// SRSLoadBalancer is the interface to load balance the SRS servers. -type SRSLoadBalancer interface { +// OriginLoadBalancer is the interface to load balance the SRS servers. +type OriginLoadBalancer interface { // Initialize the load balancer. Initialize(ctx context.Context) error - // Update the backend server. - Update(ctx context.Context, server *SRSServer) error + // Update records the latest registration or heartbeat for an origin server. + Update(ctx context.Context, server *OriginServer) error // Pick a backend server for the specified stream URL. - Pick(ctx context.Context, streamURL string) (*SRSServer, error) + Pick(ctx context.Context, streamURL string) (*OriginServer, error) // Load or store the HLS streaming for the specified stream URL. LoadOrStoreHLS(ctx context.Context, streamURL string, value HLSPlayStream) (HLSPlayStream, error) // Load the HLS streaming by SPBHID, the SRS Proxy Backend HLS ID. @@ -128,4 +128,4 @@ type SRSLoadBalancer interface { } // SrsLoadBalancer is the global SRS load balancer instance. -var SrsLoadBalancer SRSLoadBalancer +var SrsLoadBalancer OriginLoadBalancer diff --git a/internal/lb/mem.go b/internal/lb/mem.go index 57b4c88b4..0f1702a34 100644 --- a/internal/lb/mem.go +++ b/internal/lb/mem.go @@ -20,9 +20,9 @@ type MemoryLoadBalancer struct { // The environment interface. environment env.ProxyEnvironment // All available SRS servers, key is server ID. - servers sync.Map[string, *SRSServer] + servers sync.Map[string, *OriginServer] // The picked server to service client by specified stream URL, key is stream url. - picked sync.Map[string, *SRSServer] + picked sync.Map[string, *OriginServer] // The HLS streaming, key is stream URL. hlsStreamURL sync.Map[string, HLSPlayStream] // The HLS streaming, key is SPBHID. @@ -34,11 +34,11 @@ type MemoryLoadBalancer struct { } // NewMemoryLoadBalancer creates a new memory-based load balancer. -func NewMemoryLoadBalancer(environment env.ProxyEnvironment) SRSLoadBalancer { +func NewMemoryLoadBalancer(environment env.ProxyEnvironment) OriginLoadBalancer { return &MemoryLoadBalancer{ environment: environment, - servers: sync.NewMap[string, *SRSServer](), - picked: sync.NewMap[string, *SRSServer](), + servers: sync.NewMap[string, *OriginServer](), + picked: sync.NewMap[string, *OriginServer](), hlsStreamURL: sync.NewMap[string, HLSPlayStream](), hlsSPBHID: sync.NewMap[string, HLSPlayStream](), rtcStreamURL: sync.NewMap[string, RTCConnection](), @@ -75,20 +75,20 @@ func (v *MemoryLoadBalancer) Initialize(ctx context.Context) error { return nil } -func (v *MemoryLoadBalancer) Update(ctx context.Context, server *SRSServer) error { +func (v *MemoryLoadBalancer) Update(ctx context.Context, server *OriginServer) error { v.servers.Store(server.ID(), server) return nil } -func (v *MemoryLoadBalancer) Pick(ctx context.Context, streamURL string) (*SRSServer, error) { +func (v *MemoryLoadBalancer) Pick(ctx context.Context, streamURL string) (*OriginServer, error) { // Always proxy to the same server for the same stream URL. if server, ok := v.picked.Load(streamURL); ok { return server, nil } // Gather all servers that were alive within the last few seconds. - var servers []*SRSServer - v.servers.Range(func(key string, server *SRSServer) bool { + var servers []*OriginServer + v.servers.Range(func(key string, server *OriginServer) bool { if time.Since(server.UpdatedAt) < ServerAliveDuration { servers = append(servers, server) } @@ -97,7 +97,7 @@ func (v *MemoryLoadBalancer) Pick(ctx context.Context, streamURL string) (*SRSSe // If no servers available, use all possible servers. if len(servers) == 0 { - v.servers.Range(func(key string, server *SRSServer) bool { + v.servers.Range(func(key string, server *OriginServer) bool { servers = append(servers, server) return true }) diff --git a/internal/lb/redis.go b/internal/lb/redis.go index d47bf8982..a3be9b961 100644 --- a/internal/lb/redis.go +++ b/internal/lb/redis.go @@ -28,7 +28,7 @@ type RedisLoadBalancer struct { } // NewRedisLoadBalancer creates a new Redis-based load balancer. -func NewRedisLoadBalancer(environment env.ProxyEnvironment) SRSLoadBalancer { +func NewRedisLoadBalancer(environment env.ProxyEnvironment) OriginLoadBalancer { return &RedisLoadBalancer{ environment: environment, } @@ -80,7 +80,7 @@ func (v *RedisLoadBalancer) Initialize(ctx context.Context) error { return nil } -func (v *RedisLoadBalancer) Update(ctx context.Context, server *SRSServer) error { +func (v *RedisLoadBalancer) Update(ctx context.Context, server *OriginServer) error { b, err := json.Marshal(server) if err != nil { return errors.Wrapf(err, "marshal server %+v", server) @@ -130,14 +130,14 @@ func (v *RedisLoadBalancer) Update(ctx context.Context, server *SRSServer) error return nil } -func (v *RedisLoadBalancer) Pick(ctx context.Context, streamURL string) (*SRSServer, error) { +func (v *RedisLoadBalancer) Pick(ctx context.Context, streamURL string) (*OriginServer, error) { key := fmt.Sprintf("srs-proxy-url:%v", streamURL) // Always proxy to the same server for the same stream URL. if serverKey, err := v.rdb.Get(ctx, key).Result(); err == nil { // If server not exists, ignore and pick another server for the stream URL. if b, err := v.rdb.Get(ctx, serverKey).Bytes(); err == nil && len(b) > 0 { - var server SRSServer + var server OriginServer if err := json.Unmarshal(b, &server); err != nil { return nil, errors.Wrapf(err, "unmarshal key=%v server %v", key, string(b)) } @@ -163,7 +163,7 @@ func (v *RedisLoadBalancer) Pick(ctx context.Context, streamURL string) (*SRSSer // All server should be alive, if not, should have been removed by redis. So we only // random pick one that is always available. Use global rand which is thread-safe since Go 1.20. var serverKey string - var server SRSServer + var server OriginServer for i := 0; i < 3; i++ { tryServerKey := serverKeys[rand.Intn(len(serverKeys))] b, err := v.rdb.Get(ctx, tryServerKey).Bytes() diff --git a/internal/proxy/api.go b/internal/proxy/api.go index ed7bd6020..c3365eec7 100644 --- a/internal/proxy/api.go +++ b/internal/proxy/api.go @@ -255,7 +255,7 @@ func (v *systemAPI) Run(ctx context.Context) error { return errors.Errorf("empty rtmp") } - server := lb.NewSRSServer(func(srs *lb.SRSServer) { + server := lb.NewOriginServer(func(srs *lb.OriginServer) { srs.IP, srs.DeviceID = ip, deviceID srs.ServerID, srs.ServiceID, srs.PID = serverID, serviceID, pid srs.RTMP, srs.HTTP, srs.API = rtmp, stream, api diff --git a/internal/proxy/http.go b/internal/proxy/http.go index 1d82ce72c..9ce915e1d 100644 --- a/internal/proxy/http.go +++ b/internal/proxy/http.go @@ -245,7 +245,7 @@ func (v *httpFlvTsConnection) serve(ctx context.Context, w http.ResponseWriter, return nil } -func (v *httpFlvTsConnection) serveByBackend(ctx context.Context, w http.ResponseWriter, r *http.Request, backend *lb.SRSServer) error { +func (v *httpFlvTsConnection) serveByBackend(ctx context.Context, w http.ResponseWriter, r *http.Request, backend *lb.OriginServer) error { // Parse HTTP port from backend. if len(backend.HTTP) == 0 { return errors.Errorf("no http stream server") @@ -363,7 +363,7 @@ func (v *hlsPlayStream) serve(ctx context.Context, w http.ResponseWriter, r *htt return nil } -func (v *hlsPlayStream) serveByBackend(ctx context.Context, w http.ResponseWriter, r *http.Request, backend *lb.SRSServer) error { +func (v *hlsPlayStream) serveByBackend(ctx context.Context, w http.ResponseWriter, r *http.Request, backend *lb.OriginServer) error { // Parse HTTP port from backend. if len(backend.HTTP) == 0 { return errors.Errorf("no rtmp server") diff --git a/internal/proxy/rtc.go b/internal/proxy/rtc.go index 3711e3d37..71628602e 100644 --- a/internal/proxy/rtc.go +++ b/internal/proxy/rtc.go @@ -147,7 +147,7 @@ func (v *webRTCProxyServer) HandleApiForWHEP(ctx context.Context, w http.Respons } func (v *webRTCProxyServer) proxyApiToBackend( - ctx context.Context, w http.ResponseWriter, r *http.Request, backend *lb.SRSServer, + ctx context.Context, w http.ResponseWriter, r *http.Request, backend *lb.OriginServer, remoteSDPOffer string, streamURL string, ) error { // Parse HTTP port from backend. diff --git a/memory b/memory deleted file mode 120000 index 20495cb7b..000000000 --- a/memory +++ /dev/null @@ -1 +0,0 @@ -.openclaw/memory \ No newline at end of file diff --git a/.openclaw/memory/2026-02-06.md b/memory/2026-02-06.md similarity index 100% rename from .openclaw/memory/2026-02-06.md rename to memory/2026-02-06.md diff --git a/.openclaw/memory/srs-codebase-map.md b/memory/srs-codebase-map.md similarity index 97% rename from .openclaw/memory/srs-codebase-map.md rename to memory/srs-codebase-map.md index 585dacbc8..039598ade 100644 --- a/.openclaw/memory/srs-codebase-map.md +++ b/memory/srs-codebase-map.md @@ -219,7 +219,7 @@ The next-generation server (`cmd/` + `internal/`) is written in Go and maintaine `internal/rtmp` — RTMP protocol implementation (parsing, not proxying). Full RTMP chunk stream and message protocol: simple handshake (C0/C1/C2), chunk stream reader/writer with all four format types, extended timestamp, message reassembly from chunks. Defines all RTMP message types, chunk stream IDs, and command names. Packet types include ConnectApp, CreateStream, Publish, Play, Call, SetChunkSize, WindowAcknowledgementSize, SetPeerBandwidth, UserControl. Uses Go generics (`ExpectPacket[T]`) to read until a specific packet type arrives. Also includes full AMF0 encoder/decoder supporting Number, Boolean, String, Object, Null, Undefined, EcmaArray, StrictArray, Date, LongString — with ordered key-value maps, auto-type-discovery, and safe type converters. -`internal/lb` — Load balancer abstraction and two implementations. Defines `SRSLoadBalancer` interface and core types in `lb.go` (Initialize, Update, Pick, HLS/WebRTC state management) and `SRSServer` struct representing a backend origin (IP, listen endpoints for RTMP/HTTP/API/SRT/RTC, heartbeat tracking). **Memory LB** (`mem.go`) — in-memory using `sync.Map`, sticky random pick per stream URL, single-proxy deployment. **Redis LB** (`redis.go`) — Redis-backed shared state with TTL-based expiration, enables multi-proxy horizontal scaling behind a network load balancer. Also includes a debug helper (`debug.go`) that creates a fake backend from env vars when `PROXY_DEFAULT_BACKEND_ENABLED=on` for development without real SRS registration. +`internal/lb` — Load balancer abstraction and two implementations. Defines `OriginLoadBalancer` interface and core types in `lb.go` (Initialize, Update, Pick, HLS/WebRTC state management) and `OriginServer` struct representing a backend origin (IP, listen endpoints for RTMP/HTTP/API/SRT/RTC, heartbeat tracking). **Memory LB** (`mem.go`) — in-memory using `sync.Map`, sticky random pick per stream URL, single-proxy deployment. **Redis LB** (`redis.go`) — Redis-backed shared state with TTL-based expiration, enables multi-proxy horizontal scaling behind a network load balancer. Also includes a debug helper (`debug.go`) that creates a fake backend from env vars when `PROXY_DEFAULT_BACKEND_ENABLED=on` for development without real SRS registration. `internal/logger` — Structured logging with context IDs. Four log levels: Debug/Info (stdout), Warn/Error (stderr). Emits JSON via `log/slog` with `pid` and `cid` attributes. Each connection/request gets a unique 7-char hex context ID for log correlation, stored in `context.Context`. diff --git a/.openclaw/memory/srs-coroutines.md b/memory/srs-coroutines.md similarity index 100% rename from .openclaw/memory/srs-coroutines.md rename to memory/srs-coroutines.md diff --git a/.openclaw/memory/srs-overview.md b/memory/srs-overview.md similarity index 100% rename from .openclaw/memory/srs-overview.md rename to memory/srs-overview.md diff --git a/.openclaw/skills/srs-develop/SKILL.md b/skills/srs-develop/SKILL.md similarity index 100% rename from .openclaw/skills/srs-develop/SKILL.md rename to skills/srs-develop/SKILL.md diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-cluster-test.sh b/skills/srs-develop/scripts/proxy-e2e-cluster-test.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-e2e-cluster-test.sh rename to skills/srs-develop/scripts/proxy-e2e-cluster-test.sh diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-redis-test.sh b/skills/srs-develop/scripts/proxy-e2e-redis-test.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-e2e-redis-test.sh rename to skills/srs-develop/scripts/proxy-e2e-redis-test.sh diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-srt-test.sh b/skills/srs-develop/scripts/proxy-e2e-srt-test.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-e2e-srt-test.sh rename to skills/srs-develop/scripts/proxy-e2e-srt-test.sh diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-test.sh b/skills/srs-develop/scripts/proxy-e2e-test.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-e2e-test.sh rename to skills/srs-develop/scripts/proxy-e2e-test.sh diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-transmux-test.sh b/skills/srs-develop/scripts/proxy-e2e-transmux-test.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-e2e-transmux-test.sh rename to skills/srs-develop/scripts/proxy-e2e-transmux-test.sh diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-whip-test.sh b/skills/srs-develop/scripts/proxy-e2e-whip-test.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-e2e-whip-test.sh rename to skills/srs-develop/scripts/proxy-e2e-whip-test.sh diff --git a/.openclaw/skills/srs-develop/scripts/proxy-utest.sh b/skills/srs-develop/scripts/proxy-utest.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/proxy-utest.sh rename to skills/srs-develop/scripts/proxy-utest.sh diff --git a/.openclaw/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh b/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh similarity index 100% rename from .openclaw/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh rename to skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh diff --git a/.openclaw/skills/srs-support/.gitignore b/skills/srs-support/.gitignore similarity index 100% rename from .openclaw/skills/srs-support/.gitignore rename to skills/srs-support/.gitignore diff --git a/.openclaw/skills/srs-support/SKILL.md b/skills/srs-support/SKILL.md similarity index 100% rename from .openclaw/skills/srs-support/SKILL.md rename to skills/srs-support/SKILL.md diff --git a/.openclaw/skills/srs-support/evals/evals.json b/skills/srs-support/evals/evals.json similarity index 100% rename from .openclaw/skills/srs-support/evals/evals.json rename to skills/srs-support/evals/evals.json