core/http/handler/api/whip.go

177 lines
5.8 KiB
Go

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"`
// Subscribers is the number of active WHEP subscribers watching this stream.
Subscribers int `json:"subscribers"`
}
// 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://<public_host>:<port>/whip/<stream_key>
PublishURL string `json:"publish_url"`
// SDPURL is the internal FFmpeg-readable relay URL.
// Format: http://localhost:<port>/whip/<stream_key>/sdp
SDPURL string `json:"sdp_url"`
// StreamKey is the key component used in both URLs.
StreamKey string `json:"stream_key"`
// WHEPURL is the WebRTC egress URL for browsers (WHEP).
// Format: http://<public_host>:<port>/whip/<stream_key>/whep
WHEPURL string `json:"whep_url,omitempty"`
}
// whipPublicBase returns "http://<host>:<port>" 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,
WHEPURL: fmt.Sprintf("%s/whip/%s/whep", publicBase, 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),
BaseWHEPURL: fmt.Sprintf("%s/whip/", publicBase),
HasToken: token != "",
ExampleOBS: fmt.Sprintf("%s/whip/<stream-key>", publicBase),
InputAddressTemplate: "{whip:name=<stream-key>}",
})
}
// 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"`
// BaseWHEPURL is the base URL for WHEP browser subscribers. Append the stream key and /whep.
BaseWHEPURL string `json:"base_whep_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,
Subscribers: channels.Subscribers[name],
})
}
return c.JSON(http.StatusOK, list)
}