Files
yovision/Sense/server/app/sense/liveview/service.go
T

113 lines
3.2 KiB
Go

package liveview
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"net/url"
"strings"
"sync"
"time"
"yovision.local/sense/app/sense/media"
)
type Session struct {
ID string `json:"id"`
OwnerID string `json:"-"`
RouteID string `json:"route_id"`
DeviceID string `json:"device_id"`
ProfileToken string `json:"profile_token"`
PlayerURL string `json:"player_url"`
Status string `json:"status"`
Detail string `json:"detail"`
ExpiresAt time.Time `json:"expires_at"`
}
type Service struct {
base *url.URL
ttl time.Duration
route func(context.Context, string) (media.Route, error)
routes func(context.Context) ([]media.Route, error)
mu sync.RWMutex
sessions map[string]Session
now func() time.Time
}
func NewService(rawBase string, ttl time.Duration) (*Service, error) {
parsed, err := url.Parse(strings.TrimRight(rawBase, "/"))
if err != nil || parsed.Scheme == "" || parsed.Host == "" || parsed.User != nil {
return nil, fmt.Errorf("invalid MediaMTX WebRTC base URL")
}
if parsed.Scheme != "http" && parsed.Scheme != "https" {
return nil, fmt.Errorf("unsupported MediaMTX WebRTC scheme")
}
if ttl <= 0 || ttl > 10*time.Minute {
ttl = 2 * time.Minute
}
return &Service{base: parsed, ttl: ttl, route: media.PlaybackRoute, routes: media.PlaybackRoutes, sessions: map[string]Session{}, now: time.Now}, nil
}
func (s *Service) Routes(ctx context.Context) ([]media.Route, error) {
items, err := s.routes(ctx)
if err != nil {
return nil, err
}
result := items[:0]
for _, item := range items {
if item.Desired == "running" {
result = append(result, item)
}
}
return result, nil
}
func (s *Service) Create(ctx context.Context, owner, routeID string) (Session, error) {
route, err := s.route(ctx, routeID)
if err != nil {
return Session{}, err
}
if route.Desired != "running" {
return Session{}, fmt.Errorf("media route is stopped")
}
id := newID()
session := Session{ID: id, OwnerID: owner, RouteID: route.ID, DeviceID: route.DeviceID, ProfileToken: route.ProfileToken, PlayerURL: "/api/v1/liveview/sessions/" + id + "/player", Status: route.Actual, Detail: route.Detail, ExpiresAt: s.now().UTC().Add(s.ttl)}
s.mu.Lock()
s.sessions[id] = session
s.mu.Unlock()
return session, nil
}
func (s *Service) Get(ctx context.Context, owner, id string) (Session, error) {
s.mu.RLock()
session, ok := s.sessions[id]
s.mu.RUnlock()
if !ok || session.OwnerID != owner || !s.now().Before(session.ExpiresAt) {
return Session{}, fmt.Errorf("playback session expired")
}
route, err := s.route(ctx, session.RouteID)
if err != nil {
return Session{}, err
}
session.Status = route.Actual
session.Detail = route.Detail
return session, nil
}
func (s *Service) EmbedURL(ctx context.Context, owner, id string) (string, error) {
session, err := s.Get(ctx, owner, id)
if err != nil {
return "", err
}
route, err := s.route(ctx, session.RouteID)
if err != nil {
return "", err
}
target := *s.base
target.Path = strings.TrimRight(target.Path, "/") + "/" + route.Path
return target.String(), nil
}
func newID() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return "view_" + hex.EncodeToString(b)
}