add HLS support and fix skill response

This commit is contained in:
seydx
2025-05-12 05:23:14 +02:00
parent 6e35f1a389
commit b797a2fcd1
2 changed files with 97 additions and 48 deletions
+84 -46
View File
@@ -21,6 +21,7 @@ type TuyaClient struct {
mqtt *TuyaMQTT
apiURL string
rtspURL string
hlsURL string
sessionID string
clientID string
deviceID string
@@ -42,11 +43,11 @@ type Token struct {
ExpireTime int64 `json:"expire_time"`
}
type RTSPRequest struct {
type AllocateRequest struct {
Type string `json:"type"`
}
type RTSPResponse struct {
type AllocateResponse struct {
Success bool `json:"success"`
Result struct {
URL string `json:"url"`
@@ -145,7 +146,7 @@ const (
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{
httpClient: &http.Client{Timeout: defaultTimeout},
mqtt: &TuyaMQTT{waiter: core.Waiter{}},
@@ -162,9 +163,13 @@ func NewTuyaClient(openAPIURL string, deviceID string, uid string, clientID stri
}
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)
}
} else if useHLS {
if err := client.GetStreamUrl("hls"); err != nil {
return nil, fmt.Errorf("failed to get HLS URL: %w", err)
}
} else {
if err := client.InitDevice(); err != nil {
return nil, fmt.Errorf("failed to initialize device: %w", err)
@@ -285,48 +290,72 @@ func(c *TuyaClient) InitDevice() (err error) {
}
c.medias = make([]*core.Media, 0)
for _, audio := range skill.Audios {
media := &core.Media{
if len(skill.Audios) > 0 {
for _, audio := range skill.Audios {
c.medias = append(c.medias, &core.Media{
Kind: core.KindAudio,
Direction: audioDirection,
Codecs: []*core.Codec{
{
Name: "PCMU",
ClockRate: uint32(audio.SampleRate),
Channels: uint8(audio.Channels),
},
},
})
}
} else {
c.medias = append(c.medias, &core.Media{
Kind: core.KindAudio,
Direction: audioDirection,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{
Name: "PCMU",
ClockRate: uint32(audio.SampleRate),
Channels: uint8(audio.Channels),
ClockRate: uint32(8000),
Channels: uint8(1),
},
},
})
}
if len(skill.Videos) > 0 {
// take only the first video codec
video := skill.Videos[0]
var name string
switch video.CodecType {
case 4:
name = core.CodecH265
case 2:
name = core.CodecH264
default:
name = core.CodecH264
}
c.medias = append(c.medias, media)
}
// take only the first video codec
video := skill.Videos[0]
var name string
switch video.CodecType {
case 4:
name = core.CodecH265
case 2:
name = core.CodecH264
default:
name = core.CodecH264
}
media := &core.Media{
Kind: core.KindVideo,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{
Name: name,
ClockRate: uint32(video.SampleRate),
PayloadType: 96,
c.medias = append(c.medias, &core.Media{
Kind: core.KindVideo,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{
Name: name,
ClockRate: uint32(video.SampleRate),
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)
if err != nil {
@@ -342,11 +371,11 @@ func(c *TuyaClient) InitDevice() (err error) {
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)
request := &RTSPRequest{
Type: "rtsp",
request := &AllocateRequest{
Type: streamType,
}
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)
}
var rtspResponse RTSPResponse
err = json.Unmarshal(body, &rtspResponse)
var allosResponse AllocateResponse
err = json.Unmarshal(body, &allosResponse)
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 {
return fmt.Errorf("failed to get rtsp url: %s", string(body))
if !allosResponse.Success {
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
}
+13 -2
View File
@@ -41,14 +41,15 @@ func Dial(rawURL string) (core.Producer, error) {
clientID := query.Get("client_id")
secret := query.Get("secret")
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 == "" {
return nil, errors.New("tuya: wrong query")
}
// 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 {
return nil, err
}
@@ -58,6 +59,7 @@ func Dial(rawURL string) (core.Producer, error) {
done: make(chan struct{}),
}
// RTSP
if useRTSP {
if client.api.rtspURL == "" {
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)
}
// 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{
ICEServers: client.api.iceServers,
ICETransportPolicy: pion.ICETransportPolicyAll,