From 6e35f1a389c9c2f91a1d2a4a7daa9adeee7168d1 Mon Sep 17 00:00:00 2001 From: seydx Date: Sun, 11 May 2025 01:21:20 +0200 Subject: [PATCH] add rtsp support --- pkg/tuya/api.go | 58 ++++++++++++++++++++++++++++++++++++++++------ pkg/tuya/client.go | 14 +++++++++-- pkg/tuya/mqtt.go | 3 +-- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/pkg/tuya/api.go b/pkg/tuya/api.go index a6290494..e3c9d5b0 100644 --- a/pkg/tuya/api.go +++ b/pkg/tuya/api.go @@ -20,6 +20,7 @@ type TuyaClient struct { httpClient *http.Client mqtt *TuyaMQTT apiURL string + rtspURL string sessionID string clientID string deviceID string @@ -41,6 +42,17 @@ type Token struct { ExpireTime int64 `json:"expire_time"` } +type RTSPRequest struct { + Type string `json:"type"` +} + +type RTSPResponse struct { + Success bool `json:"success"` + Result struct { + URL string `json:"url"` + } `json:"result"` +} + type AudioAttributes struct { CallMode []int `json:"call_mode"` // 1 = one way, 2 = two way HardwareCapability []int `json:"hardware_capability"` // 1 = mic, 2 = speaker @@ -133,7 +145,7 @@ const ( defaultTimeout = 5 * time.Second ) -func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID string, secret string) (*TuyaClient, error) { +func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID string, secret string, useRTSP bool) (*TuyaClient, error) { client := &TuyaClient{ httpClient: &http.Client{Timeout: defaultTimeout}, mqtt: &TuyaMQTT{waiter: core.Waiter{}}, @@ -149,12 +161,18 @@ func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID stri return nil, fmt.Errorf("failed to initialize token: %w", err) } - if err := client.InitDevice(); err != nil { - return nil, fmt.Errorf("failed to initialize device: %w", err) - } + if useRTSP { + if err := client.GetRTSP(); err != nil { + return nil, fmt.Errorf("failed to get RTSP URL: %w", err) + } + } else { + if err := client.InitDevice(); err != nil { + return nil, fmt.Errorf("failed to initialize device: %w", err) + } - if err := client.StartMQTT(); err != nil { - return nil, fmt.Errorf("failed to start MQTT: %w", err) + if err := client.StartMQTT(); err != nil { + return nil, fmt.Errorf("failed to start MQTT: %w", err) + } } return client, nil @@ -324,6 +342,32 @@ func(c *TuyaClient) InitDevice() (err error) { return nil } +func(c *TuyaClient) GetRTSP() (err error) { + url := fmt.Sprintf("https://%s/v1.0/devices/%s/stream/actions/allocate", c.apiURL, c.deviceID) + + request := &RTSPRequest{ + Type: "rtsp", + } + + body, err := c.Request("POST", url, request) + if err != nil { + return fmt.Errorf("failed to get rtsp url: %w", err) + } + + var rtspResponse RTSPResponse + err = json.Unmarshal(body, &rtspResponse) + if err != nil { + return fmt.Errorf("failed to unmarshal rtsp response: %w", err) + } + + if !rtspResponse.Success { + return fmt.Errorf("failed to get rtsp url: %s", string(body)) + } + + c.rtspURL = rtspResponse.Result.URL + + return nil +} func(c *TuyaClient) LoadHubConfig() (config *OpenIoTHubConfig, err error) { url := fmt.Sprintf("https://%s/v2.0/open-iot-hub/access/config", c.apiURL) @@ -335,12 +379,12 @@ func(c *TuyaClient) LoadHubConfig() (config *OpenIoTHubConfig, err error) { Topics: "ipc", } - var openIoTHubConfigResponse OpenIoTHubConfigResponse body, err := c.Request("POST", url, request) if err != nil { return nil, fmt.Errorf("failed to get OpenIoTHub config: %w", err) } + var openIoTHubConfigResponse OpenIoTHubConfigResponse err = json.Unmarshal(body, &openIoTHubConfigResponse) if err != nil { return nil, fmt.Errorf("failed to unmarshal OpenIoTHub config response: %w", err) diff --git a/pkg/tuya/client.go b/pkg/tuya/client.go index e0cbce4c..1f1bc827 100644 --- a/pkg/tuya/client.go +++ b/pkg/tuya/client.go @@ -7,6 +7,7 @@ import ( "net/url" "strconv" + "github.com/AlexxIT/go2rtc/internal/streams" "github.com/AlexxIT/go2rtc/pkg/core" "github.com/AlexxIT/go2rtc/pkg/webrtc" pion "github.com/pion/webrtc/v4" @@ -27,7 +28,7 @@ const ( DefaultInURL = "openapi.tuyain.com" ) -func Dial(rawURL string) (*Client, error) { +func Dial(rawURL string) (core.Producer, error) { // Parse URL and validate basic params u, err := url.Parse(rawURL) if err != nil { @@ -40,13 +41,14 @@ func Dial(rawURL string) (*Client, error) { clientID := query.Get("client_id") secret := query.Get("secret") resolution := query.Get("resolution") + useRTSP := query.Get("use_rtsp") == "1" if deviceID == "" || uid == "" || clientID == "" || secret == "" { return nil, errors.New("tuya: wrong query") } // Initialize Tuya API client - tuyaAPI, err := NewTuyaClient(u.Hostname(), deviceID, uid, clientID, secret) + tuyaAPI, err := NewTuyaClient(u.Hostname(), deviceID, uid, clientID, secret, useRTSP) if err != nil { return nil, err } @@ -56,6 +58,14 @@ func Dial(rawURL string) (*Client, error) { done: make(chan struct{}), } + if useRTSP { + if client.api.rtspURL == "" { + return nil, errors.New("tuya: no rtsp url") + } + + return streams.GetProducer(client.api.rtspURL) + } + conf := pion.Configuration{ ICEServers: client.api.iceServers, ICETransportPolicy: pion.ICETransportPolicyAll, diff --git a/pkg/tuya/mqtt.go b/pkg/tuya/mqtt.go index eff9d8e7..3da27c08 100644 --- a/pkg/tuya/mqtt.go +++ b/pkg/tuya/mqtt.go @@ -107,9 +107,8 @@ func(c *TuyaClient) StartMQTT() error { } func(c *TuyaClient) StopMQTT() { - c.sendDisconnect() - if c.mqtt.client != nil { + c.sendDisconnect() c.mqtt.client.Disconnect(1000) } }