package server import ( "bytes" "embed" "io/fs" "net" "net/http" "time" "google.golang.org/grpc" "gospeak-live-sfu-demo/gen" "gospeak-live-sfu-demo/internal/config" "gospeak-live-sfu-demo/internal/sfu/cloudflare" "gospeak-live-sfu-demo/internal/sfu/srs" ) //go:embed static var staticFS embed.FS // Server 聚合控制面(gRPC + JSON 网关)、媒体面反向代理与静态 UI。 type Server struct { cfg *config.Config svc *Service hub *roomHub srsProxy http.Handler cfProxy http.Handler } func New(cfg *config.Config) *Server { hub := newRoomHub() cf := cloudflare.NewProvider(cfg.CFAppID, cfg.CFAppSecret, cfg.CFBaseURL, cfg.CFStunURL) srsP := srs.NewProvider(cfg.SRSBaseURL, cfg.SRSApp, cfg.SRSSecret, cfg.SRSCandidate) svc := NewService(cfg, cf, srsP, hub) s := &Server{cfg: cfg, svc: svc, hub: hub} s.srsProxy = s.srsProxyHandler() s.cfProxy = s.cfProxyHandler() return s } // StartGRPC 启动 gRPC 控制面(protobuf 契约的服务端实现)。 func (s *Server) StartGRPC() error { lis, err := net.Listen("tcp", ":"+s.cfg.GRPCPort) if err != nil { return err } gs := grpc.NewServer() gen.RegisterLiveSFUServer(gs, NewGRPCServer(s.svc)) go func() { _ = gs.Serve(lis) }() return nil } // Handler 返回 HTTP 路由:静态 UI、JSON 网关、SSE、SRS/Cloudflare 媒体反代。 func (s *Server) Handler() http.Handler { mux := http.NewServeMux() sub, _ := fs.Sub(staticFS, "static") mux.Handle("GET /", s.fileServer("static/index.html")) mux.Handle("GET /publish", s.fileServer("static/publish.html")) mux.Handle("GET /watch", s.fileServer("static/watch.html")) mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(sub)))) mux.HandleFunc("GET /api/config", s.handleConfig) mux.HandleFunc("GET /api/rooms", s.handleRooms) mux.HandleFunc("POST /api/publish", s.handlePublish) mux.HandleFunc("POST /api/subscribe", s.handleSubscribe) mux.HandleFunc("POST /api/stop", s.handleStop) mux.HandleFunc("GET /api/srs/streams", s.handleSRSStreams) mux.Handle("GET /api/room/{room}/events", http.HandlerFunc(s.handleRoomEvents)) mux.Handle("GET /rtc/v1/", s.srsProxy) mux.Handle("POST /rtc/v1/", s.srsProxy) mux.Handle("PUT /rtc/v1/", s.srsProxy) mux.Handle("DELETE /rtc/v1/", s.srsProxy) mux.Handle("GET /api/cf/", s.cfProxy) mux.Handle("POST /api/cf/", s.cfProxy) mux.Handle("PUT /api/cf/", s.cfProxy) mux.Handle("DELETE /api/cf/", s.cfProxy) return mux } func (s *Server) fileServer(name string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { data, err := staticFS.ReadFile(name) if err != nil { http.Error(w, "not found", http.StatusNotFound) return } http.ServeContent(w, r, name, time.Time{}, bytes.NewReader(data)) } }