add HLS support and fix skill response
This commit is contained in:
+58
-20
@@ -21,6 +21,7 @@ type TuyaClient struct {
|
|||||||
mqtt *TuyaMQTT
|
mqtt *TuyaMQTT
|
||||||
apiURL string
|
apiURL string
|
||||||
rtspURL string
|
rtspURL string
|
||||||
|
hlsURL string
|
||||||
sessionID string
|
sessionID string
|
||||||
clientID string
|
clientID string
|
||||||
deviceID string
|
deviceID string
|
||||||
@@ -42,11 +43,11 @@ type Token struct {
|
|||||||
ExpireTime int64 `json:"expire_time"`
|
ExpireTime int64 `json:"expire_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RTSPRequest struct {
|
type AllocateRequest struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RTSPResponse struct {
|
type AllocateResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Result struct {
|
Result struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
@@ -145,7 +146,7 @@ const (
|
|||||||
defaultTimeout = 5 * time.Second
|
defaultTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID string, secret string, useRTSP bool) (*TuyaClient, error) {
|
func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID string, secret string, useRTSP bool, useHLS bool) (*TuyaClient, error) {
|
||||||
client := &TuyaClient{
|
client := &TuyaClient{
|
||||||
httpClient: &http.Client{Timeout: defaultTimeout},
|
httpClient: &http.Client{Timeout: defaultTimeout},
|
||||||
mqtt: &TuyaMQTT{waiter: core.Waiter{}},
|
mqtt: &TuyaMQTT{waiter: core.Waiter{}},
|
||||||
@@ -162,9 +163,13 @@ func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if useRTSP {
|
if useRTSP {
|
||||||
if err := client.GetRTSP(); err != nil {
|
if err := client.GetStreamUrl("rtsp"); err != nil {
|
||||||
return nil, fmt.Errorf("failed to get RTSP URL: %w", err)
|
return nil, fmt.Errorf("failed to get RTSP URL: %w", err)
|
||||||
}
|
}
|
||||||
|
} else if useHLS {
|
||||||
|
if err := client.GetStreamUrl("hls"); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get HLS URL: %w", err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := client.InitDevice(); err != nil {
|
if err := client.InitDevice(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize device: %w", err)
|
return nil, fmt.Errorf("failed to initialize device: %w", err)
|
||||||
@@ -285,8 +290,9 @@ func(c *TuyaClient) InitDevice() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.medias = make([]*core.Media, 0)
|
c.medias = make([]*core.Media, 0)
|
||||||
|
if len(skill.Audios) > 0 {
|
||||||
for _, audio := range skill.Audios {
|
for _, audio := range skill.Audios {
|
||||||
media := &core.Media{
|
c.medias = append(c.medias, &core.Media{
|
||||||
Kind: core.KindAudio,
|
Kind: core.KindAudio,
|
||||||
Direction: audioDirection,
|
Direction: audioDirection,
|
||||||
Codecs: []*core.Codec{
|
Codecs: []*core.Codec{
|
||||||
@@ -296,11 +302,23 @@ func(c *TuyaClient) InitDevice() (err error) {
|
|||||||
Channels: uint8(audio.Channels),
|
Channels: uint8(audio.Channels),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.medias = append(c.medias, &core.Media{
|
||||||
|
Kind: core.KindAudio,
|
||||||
|
Direction: core.DirectionRecvonly,
|
||||||
|
Codecs: []*core.Codec{
|
||||||
|
{
|
||||||
|
Name: "PCMU",
|
||||||
|
ClockRate: uint32(8000),
|
||||||
|
Channels: uint8(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.medias = append(c.medias, media)
|
if len(skill.Videos) > 0 {
|
||||||
}
|
|
||||||
|
|
||||||
// take only the first video codec
|
// take only the first video codec
|
||||||
video := skill.Videos[0]
|
video := skill.Videos[0]
|
||||||
|
|
||||||
@@ -314,7 +332,7 @@ func(c *TuyaClient) InitDevice() (err error) {
|
|||||||
name = core.CodecH264
|
name = core.CodecH264
|
||||||
}
|
}
|
||||||
|
|
||||||
media := &core.Media{
|
c.medias = append(c.medias, &core.Media{
|
||||||
Kind: core.KindVideo,
|
Kind: core.KindVideo,
|
||||||
Direction: core.DirectionRecvonly,
|
Direction: core.DirectionRecvonly,
|
||||||
Codecs: []*core.Codec{
|
Codecs: []*core.Codec{
|
||||||
@@ -324,10 +342,21 @@ func(c *TuyaClient) InitDevice() (err error) {
|
|||||||
PayloadType: 96,
|
PayloadType: 96,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.medias = append(c.medias, &core.Media{
|
||||||
|
Kind: core.KindVideo,
|
||||||
|
Direction: core.DirectionRecvonly,
|
||||||
|
Codecs: []*core.Codec{
|
||||||
|
{
|
||||||
|
Name: core.CodecH264,
|
||||||
|
ClockRate: uint32(90000),
|
||||||
|
PayloadType: 96,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.medias = append(c.medias, media)
|
|
||||||
|
|
||||||
iceServersBytes, err := json.Marshal(&webRTCConfigResponse.Result.P2PConfig.Ices)
|
iceServersBytes, err := json.Marshal(&webRTCConfigResponse.Result.P2PConfig.Ices)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal ICE servers: %w", err)
|
return fmt.Errorf("failed to marshal ICE servers: %w", err)
|
||||||
@@ -342,11 +371,11 @@ func(c *TuyaClient) InitDevice() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func(c *TuyaClient) GetRTSP() (err error) {
|
func(c *TuyaClient) GetStreamUrl(streamType string) (err error) {
|
||||||
url := fmt.Sprintf("https://%s/v1.0/devices/%s/stream/actions/allocate", c.apiURL, c.deviceID)
|
url := fmt.Sprintf("https://%s/v1.0/devices/%s/stream/actions/allocate", c.apiURL, c.deviceID)
|
||||||
|
|
||||||
request := &RTSPRequest{
|
request := &AllocateRequest{
|
||||||
Type: "rtsp",
|
Type: streamType,
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := c.Request("POST", url, request)
|
body, err := c.Request("POST", url, request)
|
||||||
@@ -354,17 +383,26 @@ func(c *TuyaClient) GetRTSP() (err error) {
|
|||||||
return fmt.Errorf("failed to get rtsp url: %w", err)
|
return fmt.Errorf("failed to get rtsp url: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rtspResponse RTSPResponse
|
var allosResponse AllocateResponse
|
||||||
err = json.Unmarshal(body, &rtspResponse)
|
err = json.Unmarshal(body, &allosResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal rtsp response: %w", err)
|
return fmt.Errorf("failed to unmarshal stream response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rtspResponse.Success {
|
if !allosResponse.Success {
|
||||||
return fmt.Errorf("failed to get rtsp url: %s", string(body))
|
return fmt.Errorf("failed to get stream url: %s", string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.rtspURL = rtspResponse.Result.URL
|
switch streamType {
|
||||||
|
case "rtsp":
|
||||||
|
c.rtspURL = allosResponse.Result.URL
|
||||||
|
fmt.Printf("RTSP URL: %s\n", c.rtspURL)
|
||||||
|
case "hls":
|
||||||
|
c.hlsURL = "ffmpeg:" + allosResponse.Result.URL + "#video=copy"
|
||||||
|
fmt.Printf("HLS URL: %s\n", c.hlsURL)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported stream type: %s", streamType)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -41,14 +41,15 @@ func Dial(rawURL string) (core.Producer, error) {
|
|||||||
clientID := query.Get("client_id")
|
clientID := query.Get("client_id")
|
||||||
secret := query.Get("secret")
|
secret := query.Get("secret")
|
||||||
resolution := query.Get("resolution")
|
resolution := query.Get("resolution")
|
||||||
useRTSP := query.Get("use_rtsp") == "1"
|
useRTSP := query.Get("rtsp") == "1"
|
||||||
|
useHLS := query.Get("hls") == "1"
|
||||||
|
|
||||||
if deviceID == "" || uid == "" || clientID == "" || secret == "" {
|
if deviceID == "" || uid == "" || clientID == "" || secret == "" {
|
||||||
return nil, errors.New("tuya: wrong query")
|
return nil, errors.New("tuya: wrong query")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Tuya API client
|
// Initialize Tuya API client
|
||||||
tuyaAPI, err := NewTuyaClient(u.Hostname(), deviceID, uid, clientID, secret, useRTSP)
|
tuyaAPI, err := NewTuyaClient(u.Hostname(), deviceID, uid, clientID, secret, useRTSP, useHLS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -58,6 +59,7 @@ func Dial(rawURL string) (core.Producer, error) {
|
|||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RTSP
|
||||||
if useRTSP {
|
if useRTSP {
|
||||||
if client.api.rtspURL == "" {
|
if client.api.rtspURL == "" {
|
||||||
return nil, errors.New("tuya: no rtsp url")
|
return nil, errors.New("tuya: no rtsp url")
|
||||||
@@ -66,6 +68,15 @@ func Dial(rawURL string) (core.Producer, error) {
|
|||||||
return streams.GetProducer(client.api.rtspURL)
|
return streams.GetProducer(client.api.rtspURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HLS
|
||||||
|
if useHLS {
|
||||||
|
if client.api.hlsURL == "" {
|
||||||
|
return nil, errors.New("tuya: no hls url")
|
||||||
|
}
|
||||||
|
return streams.GetProducer(client.api.hlsURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to WebRTC
|
||||||
conf := pion.Configuration{
|
conf := pion.Configuration{
|
||||||
ICEServers: client.api.iceServers,
|
ICEServers: client.api.iceServers,
|
||||||
ICETransportPolicy: pion.ICETransportPolicyAll,
|
ICETransportPolicy: pion.ICETransportPolicyAll,
|
||||||
|
|||||||
Reference in New Issue
Block a user