package api import ( "fmt" "net/http" "strings" "time" cfgstore "github.com/datarhei/core/v16/config/store" "github.com/datarhei/core/v16/whip" "github.com/labstack/echo/v4" ) // WHIPHandler provides HTTP API access to the WHIP server channel information. type WHIPHandler struct { whip whip.Server config cfgstore.Store } // NewWHIP returns a new WHIPHandler backed by the provided WHIP server. func NewWHIP(whip whip.Server, config cfgstore.Store) *WHIPHandler { return &WHIPHandler{ whip: whip, config: config, } } // WHIPChannel represents a currently active WHIP publish session. type WHIPChannel struct { // Name is the stream identifier used in the WHIP URL path. Name string `json:"name" jsonschema:"minLength=1"` // PublishedAt is the RFC 3339 timestamp when the publisher connected. PublishedAt time.Time `json:"published_at"` } // WHIPURLs holds the publish URL (for OBS) and the internal SDP relay URL (for FFmpeg). type WHIPURLs struct { // PublishURL is the URL to enter in OBS → Settings → Stream → Server. // Format: http://:/whip/ PublishURL string `json:"publish_url"` // SDPURL is the internal FFmpeg-readable relay URL. // Format: http://localhost:/whip//sdp SDPURL string `json:"sdp_url"` // StreamKey is the key component used in both URLs. StreamKey string `json:"stream_key"` } // whipPublicBase returns "http://:" derived from the active config. func (h *WHIPHandler) whipPublicBase() (string, string, error) { if h.config == nil { return "", "", fmt.Errorf("config not available") } cfg := h.config.GetActive() addr := cfg.WHIP.Address // e.g. ":8555" or "0.0.0.0:8555" // Extract port from address. port := addr if idx := strings.LastIndex(addr, ":"); idx >= 0 { port = addr[idx+1:] } // Pick public host. host := "localhost" if len(cfg.Host.Name) > 0 && cfg.Host.Name[0] != "" { host = cfg.Host.Name[0] } return fmt.Sprintf("http://%s:%s", host, port), fmt.Sprintf("http://localhost:%s", port), nil } // GetURL returns the WHIP publish URL for a given stream key. // @Summary Get WHIP publish URL for a stream key // @Description Returns the URL to configure in OBS and the internal SDP relay URL for FFmpeg. // @Tags v16.?.? // @ID whip-3-get-url // @Produce json // @Param name path string true "Stream key" // @Success 200 {object} WHIPURLs // @Security ApiKeyAuth // @Router /api/v3/whip/{name}/url [get] func (h *WHIPHandler) GetURL(c echo.Context) error { name := c.Param("name") if name == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "stream key is required"}) } publicBase, localBase, err := h.whipPublicBase() if err != nil { return c.JSON(http.StatusServiceUnavailable, map[string]string{"error": err.Error()}) } return c.JSON(http.StatusOK, WHIPURLs{ PublishURL: fmt.Sprintf("%s/whip/%s", publicBase, name), SDPURL: fmt.Sprintf("%s/whip/%s/sdp", localBase, name), StreamKey: name, }) } // GetServerURL returns the base WHIP server URL (without a stream key). // @Summary Get WHIP server base URL // @Description Returns the base publish URL and configuration so the UI can display it in a WHIP Server panel. // @Tags v16.?.? // @ID whip-3-get-server-url // @Produce json // @Success 200 {object} WHIPServerInfo // @Security ApiKeyAuth // @Router /api/v3/whip/url [get] func (h *WHIPHandler) GetServerURL(c echo.Context) error { publicBase, localBase, err := h.whipPublicBase() if err != nil { return c.JSON(http.StatusServiceUnavailable, map[string]string{"error": err.Error()}) } var token string if h.config != nil { token = h.config.GetActive().WHIP.Token } return c.JSON(http.StatusOK, WHIPServerInfo{ BasePublishURL: fmt.Sprintf("%s/whip/", publicBase), BaseSDPURL: fmt.Sprintf("%s/whip/", localBase), HasToken: token != "", ExampleOBS: fmt.Sprintf("%s/whip/", publicBase), InputAddressTemplate: "{whip:name=}", }) } // WHIPServerInfo holds the base URLs and metadata shown in the WHIP Server panel. type WHIPServerInfo struct { // BasePublishURL is the base URL for OBS. Append the stream key. BasePublishURL string `json:"base_publish_url"` // BaseSDPURL is the base internal SDP relay URL. BaseSDPURL string `json:"base_sdp_url"` // HasToken indicates whether a bearer token is required. HasToken bool `json:"has_token"` // ExampleOBS shows an example URL with placeholder stream key. ExampleOBS string `json:"example_obs_url"` // InputAddressTemplate is the process input address template for WHIP. InputAddressTemplate string `json:"input_address_template"` } // ListChannels lists all currently active WHIP publishers. // @Summary List all publishing WHIP streams // @Description List all currently active WHIP (WebRTC HTTP Ingestion Protocol) streams. // @Tags v16.?.? // @ID whip-3-list-channels // @Produce json // @Success 200 {array} WHIPChannel // @Security ApiKeyAuth // @Router /api/v3/whip [get] func (h *WHIPHandler) ListChannels(c echo.Context) error { channels := h.whip.Channels() list := make([]WHIPChannel, 0, len(channels.Publisher)) for name, since := range channels.Publisher { list = append(list, WHIPChannel{ Name: name, PublishedAt: since, }) } return c.JSON(http.StatusOK, list) }