diff --git a/cmd/hass/api.go b/cmd/hass/api.go new file mode 100644 index 00000000..592838ef --- /dev/null +++ b/cmd/hass/api.go @@ -0,0 +1,94 @@ +package hass + +import ( + "encoding/base64" + "encoding/json" + "github.com/AlexxIT/go2rtc/cmd/api" + "github.com/AlexxIT/go2rtc/cmd/streams" + "github.com/AlexxIT/go2rtc/cmd/webrtc" + "net/http" + "strings" +) + +func initAPI() { + ok := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"status":1,"payload":{}}`)) + } + + // support https://www.home-assistant.io/integrations/rtsp_to_webrtc/ + api.HandleFunc("/static", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + api.HandleFunc("/streams", ok) + + api.HandleFunc("/stream/", func(w http.ResponseWriter, r *http.Request) { + switch { + // /stream/{id}/add + case strings.HasSuffix(r.RequestURI, "/add"): + var v addJSON + if err := json.NewDecoder(r.Body).Decode(&v); err != nil { + return + } + + if streams.Has(v.Name) { + stream := streams.Get(v.Name) + stream.SetSource(v.Channels.First.Url) + } else { + streams.New(v.Name, v.Channels.First.Url) + } + + ok(w, r) + + // /stream/{id}/channel/0/webrtc + default: + i := strings.IndexByte(r.RequestURI[8:], '/') + src := r.RequestURI[8 : 8+i] + if !streams.Has(src) { + w.WriteHeader(http.StatusNotFound) + return + } + + if err := r.ParseForm(); err != nil { + log.Error().Err(err).Msg("[api.hass] parse form") + return + } + + s := r.FormValue("data") + offer, err := base64.StdEncoding.DecodeString(s) + if err != nil { + log.Error().Err(err).Msg("[api.hass] sdp64 decode") + return + } + + // check if stream links to our rtsp server + //if strings.HasPrefix(src, "rtsp://") { + // i := strings.IndexByte(src[7:], '/') + // if i > 0 && streams.Has(src[8+i:]) { + // src = src[8+i:] + // } + //} + + stream := streams.Get(src) + s, err = webrtc.ExchangeSDP(stream, string(offer), r.UserAgent()) + if err != nil { + log.Error().Err(err).Msg("[api.hass] exchange SDP") + return + } + + s = base64.StdEncoding.EncodeToString([]byte(s)) + _, _ = w.Write([]byte(s)) + } + }) +} + +type addJSON struct { + Name string `json:"name"` + Channels struct { + First struct { + //Name string `json:"name"` + Url string `json:"url"` + } `json:"0"` + } `json:"channels"` +} diff --git a/cmd/hass/hass.go b/cmd/hass/hass.go index a9b233b0..d3b98a40 100644 --- a/cmd/hass/hass.go +++ b/cmd/hass/hass.go @@ -1,20 +1,14 @@ package hass import ( - "encoding/base64" "encoding/json" "fmt" - "github.com/AlexxIT/go2rtc/cmd/api" "github.com/AlexxIT/go2rtc/cmd/app" "github.com/AlexxIT/go2rtc/cmd/streams" - "github.com/AlexxIT/go2rtc/cmd/webrtc" "github.com/AlexxIT/go2rtc/pkg/streamer" "github.com/rs/zerolog" - "net/http" - "net/url" "os" "path" - "strings" ) func Init() { @@ -28,11 +22,7 @@ func Init() { log = app.GetLogger("hass") - // support https://www.home-assistant.io/integrations/rtsp_to_webrtc/ - api.HandleFunc("/static", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - api.HandleFunc("/stream", handler) + initAPI() // support load cameras from Hass config file filename := path.Join(conf.Mod.Config, ".storage/core.config_entries") @@ -78,73 +68,13 @@ func Init() { continue } - log.Info().Str("url", "hass:" + entrie.Title).Msg("[hass] load stream") + log.Info().Str("url", "hass:"+entrie.Title).Msg("[hass] load stream") //streams.Get("hass:" + entrie.Title) } } var log zerolog.Logger -func handler(w http.ResponseWriter, r *http.Request) { - if err := r.ParseForm(); err != nil { - log.Error().Err(err).Msg("[api.hass] parse form") - return - } - - src := r.FormValue("url") - src, err := url.QueryUnescape(src) - if err != nil { - log.Error().Err(err).Msg("[api.hass] query unescape") - return - } - - str := r.FormValue("sdp64") - - offer, err := base64.StdEncoding.DecodeString(str) - if err != nil { - log.Error().Err(err).Msg("[api.hass] sdp64 decode") - return - } - - // check if stream links to our rtsp server - if strings.HasPrefix(src, "rtsp://") { - i := strings.IndexByte(src[7:], '/') - if i > 0 && streams.Has(src[8+i:]) { - src = src[8+i:] - } - } - - stream := streams.Get(src) - if stream == nil { - log.Error().Str("url", src).Msg("[api.hass] unsupported source") - return - } - - str, err = webrtc.ExchangeSDP(stream, string(offer), r.UserAgent()) - if err != nil { - log.Error().Err(err).Msg("[api.hass] exchange SDP") - return - } - - resp := struct { - Answer string `json:"sdp64"` - }{ - Answer: base64.StdEncoding.EncodeToString([]byte(str)), - } - - data, err := json.Marshal(resp) - if err != nil { - log.Error().Err(err).Msg("[api.hass] marshal JSON") - return - } - - w.Header().Set("Content-Type", "application/json") - if _, err = w.Write(data); err != nil { - log.Error().Err(err).Msg("[api.hass] write") - return - } -} - type entries struct { Data struct { Entries []struct { diff --git a/cmd/streams/stream.go b/cmd/streams/stream.go index 1aaa31ed..f636c590 100644 --- a/cmd/streams/stream.go +++ b/cmd/streams/stream.go @@ -38,6 +38,12 @@ func NewStream(source interface{}) *Stream { return s } +func (s *Stream) SetSource(source string) { + if len(s.producers) > 0 { + s.producers[0].url = source + } +} + func (s *Stream) AddConsumer(cons streamer.Consumer) (err error) { ic := len(s.consumers) diff --git a/cmd/streams/streams.go b/cmd/streams/streams.go index 65f39455..4b629bab 100644 --- a/cmd/streams/streams.go +++ b/cmd/streams/streams.go @@ -4,6 +4,7 @@ import ( "github.com/AlexxIT/go2rtc/cmd/app" "github.com/AlexxIT/go2rtc/cmd/app/store" "github.com/rs/zerolog" + "strings" ) func Init() { @@ -27,7 +28,6 @@ func Init() { func Get(src string) *Stream { if stream, ok := streams[src]; ok { return stream - } if !HasProducer(src) { @@ -45,6 +45,19 @@ func Has(src string) bool { } func New(name string, source interface{}) { + switch source := source.(type) { + case string: + // check if new stream already link on our other stream + if strings.HasPrefix(source, "rtsp://") { + if i := strings.IndexByte(source[7:], '/'); i > 0 { + if stream, ok := streams[source[8+i:]]; ok { + streams[name] = stream + return + } + } + } + } + streams[name] = NewStream(source) }