93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
package mediamtx
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Source struct {
|
|
Path string
|
|
URI string
|
|
Username string
|
|
Password string
|
|
}
|
|
type PathStatus struct {
|
|
Name string `json:"name"`
|
|
Ready bool `json:"ready"`
|
|
Readers int `json:"readers"`
|
|
BytesReceived int64 `json:"bytes_received"`
|
|
}
|
|
type Controller interface {
|
|
Apply(context.Context, Source) error
|
|
Status(context.Context, string) (PathStatus, error)
|
|
}
|
|
type HTTPController struct {
|
|
base string
|
|
client *http.Client
|
|
}
|
|
|
|
func NewHTTPController(base string) *HTTPController {
|
|
return &HTTPController{base: strings.TrimRight(base, "/"), client: &http.Client{Timeout: 5 * time.Second}}
|
|
}
|
|
func (c *HTTPController) Apply(ctx context.Context, source Source) error {
|
|
parsed, err := url.Parse(source.URI)
|
|
if err != nil || parsed.User != nil {
|
|
return fmt.Errorf("invalid credential-free media source")
|
|
}
|
|
if source.Username != "" {
|
|
parsed.User = url.UserPassword(source.Username, source.Password)
|
|
}
|
|
payload := map[string]any{"source": parsed.String(), "sourceOnDemand": true, "rtspTransport": "tcp"}
|
|
data, _ := json.Marshal(payload)
|
|
endpoint := c.base + "/v3/config/paths/replace/" + url.PathEscape(source.Path)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
res, err := c.client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("mediamtx control unavailable: %w", err)
|
|
}
|
|
defer res.Body.Close()
|
|
io.Copy(io.Discard, io.LimitReader(res.Body, 1<<20))
|
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
|
return fmt.Errorf("mediamtx apply returned %d", res.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
func (c *HTTPController) Status(ctx context.Context, path string) (PathStatus, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.base+"/v3/paths/get/"+url.PathEscape(path), nil)
|
|
if err != nil {
|
|
return PathStatus{}, err
|
|
}
|
|
res, err := c.client.Do(req)
|
|
if err != nil {
|
|
return PathStatus{}, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode == http.StatusNotFound {
|
|
return PathStatus{Name: path}, nil
|
|
}
|
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
|
return PathStatus{}, fmt.Errorf("mediamtx status returned %d", res.StatusCode)
|
|
}
|
|
var raw struct {
|
|
Name string `json:"name"`
|
|
Ready bool `json:"ready"`
|
|
BytesReceived int64 `json:"bytesReceived"`
|
|
Readers []any `json:"readers"`
|
|
}
|
|
if err := json.NewDecoder(io.LimitReader(res.Body, 1<<20)).Decode(&raw); err != nil {
|
|
return PathStatus{}, err
|
|
}
|
|
return PathStatus{Name: raw.Name, Ready: raw.Ready, Readers: len(raw.Readers), BytesReceived: raw.BytesReceived}, nil
|
|
}
|