Files
yovision/Sense/server/app/sense/adapters/mediamtx/client.go
T

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
}