diff --git a/internal/echo/echo.go b/internal/echo/echo.go index 6d7644f7..fb105cec 100644 --- a/internal/echo/echo.go +++ b/internal/echo/echo.go @@ -2,28 +2,28 @@ package echo import ( "bytes" + "os/exec" + "github.com/AlexxIT/go2rtc/internal/app" "github.com/AlexxIT/go2rtc/internal/streams" - "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/shell" - "os/exec" ) func Init() { log := app.GetLogger("echo") - streams.HandleFunc("echo", func(url string) (core.Producer, error) { + streams.RedirectFunc("echo", func(url string) (string, error) { args := shell.QuoteSplit(url[5:]) b, err := exec.Command(args[0], args[1:]...).Output() if err != nil { - return nil, err + return "", err } b = bytes.TrimSpace(b) log.Debug().Str("url", url).Msgf("[echo] %s", b) - return streams.GetProducer(string(b)) + return string(b), nil }) } diff --git a/internal/ffmpeg/ffmpeg.go b/internal/ffmpeg/ffmpeg.go index c6607e85..4f4ec4ee 100644 --- a/internal/ffmpeg/ffmpeg.go +++ b/internal/ffmpeg/ffmpeg.go @@ -1,7 +1,6 @@ package ffmpeg import ( - "errors" "net/url" "strings" @@ -10,7 +9,6 @@ import ( "github.com/AlexxIT/go2rtc/internal/ffmpeg/hardware" "github.com/AlexxIT/go2rtc/internal/rtsp" "github.com/AlexxIT/go2rtc/internal/streams" - "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/ffmpeg" ) @@ -27,12 +25,9 @@ func Init() { defaults["global"] += " -v error" } - streams.HandleFunc("ffmpeg", func(url string) (core.Producer, error) { - args := parseArgs(url[7:]) // remove `ffmpeg:` - if args == nil { - return nil, errors.New("can't generate ffmpeg command") - } - return streams.GetProducer("exec:" + args.String()) + streams.RedirectFunc("ffmpeg", func(url string) (string, error) { + args := parseArgs(url[7:]) + return "exec:" + args.String(), nil }) device.Init(defaults["bin"]) diff --git a/internal/hass/hass.go b/internal/hass/hass.go index ccf38939..8b64a737 100644 --- a/internal/hass/hass.go +++ b/internal/hass/hass.go @@ -37,12 +37,15 @@ func Init() { api.HandleFunc("/streams", apiOK) api.HandleFunc("/stream/", apiStream) - streams.HandleFunc("hass", func(url string) (core.Producer, error) { - // check entity by name - if url2 := entities[url[5:]]; url2 != "" { - return streams.GetProducer(url2) + streams.RedirectFunc("hass", func(url string) (string, error) { + if location := entities[url[5:]]; location != "" { + return location, nil } + return "", nil + }) + + streams.HandleFunc("hass", func(url string) (core.Producer, error) { // support hass://supervisor?entity_id=camera.driveway_doorbell client, err := hass.NewClient(url) if err != nil { diff --git a/internal/streams/handlers.go b/internal/streams/handlers.go index 62c9129f..ecb76d7c 100644 --- a/internal/streams/handlers.go +++ b/internal/streams/handlers.go @@ -1,41 +1,75 @@ package streams import ( - "fmt" - "github.com/AlexxIT/go2rtc/pkg/core" + "errors" "strings" - "sync" + + "github.com/AlexxIT/go2rtc/pkg/core" ) type Handler func(url string) (core.Producer, error) var handlers = map[string]Handler{} -var handlersMu sync.Mutex func HandleFunc(scheme string, handler Handler) { - handlersMu.Lock() handlers[scheme] = handler - handlersMu.Unlock() -} - -func getHandler(url string) Handler { - i := strings.IndexByte(url, ':') - if i <= 0 { // TODO: i < 4 ? - return nil - } - handlersMu.Lock() - defer handlersMu.Unlock() - return handlers[url[:i]] } func HasProducer(url string) bool { - return getHandler(url) != nil + if i := strings.IndexByte(url, ':'); i > 0 { + scheme := url[:i] + + if _, ok := handlers[scheme]; ok { + return true + } + + if _, ok := redirects[scheme]; ok { + return true + } + } + + return false } func GetProducer(url string) (core.Producer, error) { - handler := getHandler(url) - if handler == nil { - return nil, fmt.Errorf("unsupported scheme: %s", url) + if i := strings.IndexByte(url, ':'); i > 0 { + scheme := url[:i] + + if redirect, ok := redirects[scheme]; ok { + location, err := redirect(url) + if err != nil { + return nil, err + } + if location != "" { + return GetProducer(location) + } + } + + if handler, ok := handlers[scheme]; ok { + return handler(url) + } } - return handler(url) + + return nil, errors.New("streams: unsupported scheme: " + url) +} + +// Redirect can return: location URL or error or empty URL and error +type Redirect func(url string) (string, error) + +var redirects = map[string]Redirect{} + +func RedirectFunc(scheme string, redirect Redirect) { + redirects[scheme] = redirect +} + +func Location(url string) (string, error) { + if i := strings.IndexByte(url, ':'); i > 0 { + scheme := url[:i] + + if redirect, ok := redirects[scheme]; ok { + return redirect(url) + } + } + + return "", nil }