install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
package tuya
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
pionWebrtc "github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
type TuyaAPI interface {
|
||||
GetMqtt() *TuyaMqttClient
|
||||
|
||||
GetStreamType(streamResolution string) int
|
||||
IsHEVC(streamType int) bool
|
||||
|
||||
GetVideoCodecs() []*core.Codec
|
||||
GetAudioCodecs() []*core.Codec
|
||||
|
||||
GetStreamUrl(streamUrl string) (string, error)
|
||||
GetICEServers() []pionWebrtc.ICEServer
|
||||
|
||||
Init() error
|
||||
Close()
|
||||
}
|
||||
|
||||
type TuyaClient struct {
|
||||
TuyaAPI
|
||||
|
||||
httpClient *http.Client
|
||||
mqtt *TuyaMqttClient
|
||||
baseUrl string
|
||||
expireTime int64
|
||||
deviceId string
|
||||
localKey string
|
||||
skill *Skill
|
||||
iceServers []pionWebrtc.ICEServer
|
||||
}
|
||||
|
||||
type AudioAttributes struct {
|
||||
CallMode []int `json:"call_mode"` // 1 = one way, 2 = two way
|
||||
HardwareCapability []int `json:"hardware_capability"` // 1 = mic, 2 = speaker
|
||||
}
|
||||
|
||||
type ICEServer struct {
|
||||
Urls string `json:"urls"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Credential string `json:"credential,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type WebICE struct {
|
||||
Urls string `json:"urls"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Credential string `json:"credential,omitempty"`
|
||||
}
|
||||
|
||||
type P2PConfig struct {
|
||||
Ices []ICEServer `json:"ices"`
|
||||
}
|
||||
|
||||
type AudioSkill struct {
|
||||
Channels int `json:"channels"`
|
||||
DataBit int `json:"dataBit"`
|
||||
CodecType int `json:"codecType"`
|
||||
SampleRate int `json:"sampleRate"`
|
||||
}
|
||||
|
||||
type VideoSkill struct {
|
||||
StreamType int `json:"streamType"` // 2 = main stream (HD), 4 = sub stream (SD)
|
||||
CodecType int `json:"codecType"` // 2 = H264, 4 = H265 (HEVC)
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
SampleRate int `json:"sampleRate"`
|
||||
ProfileId string `json:"profileId,omitempty"`
|
||||
}
|
||||
|
||||
type Skill struct {
|
||||
WebRTC int `json:"webrtc"` // Bit flags: bit 4=speaker, bit 5=clarity, bit 6=record
|
||||
LowPower int `json:"lowPower,omitempty"` // 1 = battery-powered camera
|
||||
Audios []AudioSkill `json:"audios"`
|
||||
Videos []VideoSkill `json:"videos"`
|
||||
}
|
||||
|
||||
type WebRTCConfig struct {
|
||||
AudioAttributes AudioAttributes `json:"audio_attributes"`
|
||||
Auth string `json:"auth"`
|
||||
ID string `json:"id"`
|
||||
LocalKey string `json:"local_key,omitempty"`
|
||||
MotoID string `json:"moto_id"`
|
||||
P2PConfig P2PConfig `json:"p2p_config"`
|
||||
ProtocolVersion string `json:"protocol_version"`
|
||||
Skill string `json:"skill"`
|
||||
SupportsWebRTCRecord bool `json:"supports_webrtc_record"`
|
||||
SupportsWebRTC bool `json:"supports_webrtc"`
|
||||
VedioClaritiy int `json:"vedio_clarity"`
|
||||
VideoClaritiy int `json:"video_clarity"`
|
||||
VideoClarities []int `json:"video_clarities"`
|
||||
}
|
||||
|
||||
type MQTTConfig struct {
|
||||
Url string `json:"url"`
|
||||
PublishTopic string `json:"publish_topic"`
|
||||
SubscribeTopic string `json:"subscribe_topic"`
|
||||
ClientID string `json:"client_id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type Allocate struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type AllocateRequest struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type AllocateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Result Allocate `json:"result"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
}
|
||||
|
||||
func (c *TuyaClient) GetICEServers() []pionWebrtc.ICEServer {
|
||||
return c.iceServers
|
||||
}
|
||||
|
||||
func (c *TuyaClient) GetMqtt() *TuyaMqttClient {
|
||||
return c.mqtt
|
||||
}
|
||||
|
||||
// GetStreamType returns the Skill StreamType for the requested resolution
|
||||
// Returns Skill values (2 or 4), not MQTT values (0 or 1)
|
||||
// - "hd" → highest resolution streamType (usually 2 = mainStream)
|
||||
// - "sd" → lowest resolution streamType (usually 4 = substream)
|
||||
//
|
||||
// These values must be mapped before sending to MQTT:
|
||||
// - streamType 2 → MQTT stream_type 0
|
||||
// - streamType 4 → MQTT stream_type 1
|
||||
func (c *TuyaClient) GetStreamType(streamResolution string) int {
|
||||
// Default streamType if nothing is found
|
||||
defaultStreamType := 1
|
||||
|
||||
if c.skill == nil || len(c.skill.Videos) == 0 {
|
||||
return defaultStreamType
|
||||
}
|
||||
|
||||
// Find the highest and lowest resolution based on pixel count
|
||||
var highestResType = defaultStreamType
|
||||
var highestRes = 0
|
||||
var lowestResType = defaultStreamType
|
||||
var lowestRes = 0
|
||||
|
||||
for _, video := range c.skill.Videos {
|
||||
res := video.Width * video.Height
|
||||
|
||||
// Highest Resolution
|
||||
if res > highestRes {
|
||||
highestRes = res
|
||||
highestResType = video.StreamType
|
||||
}
|
||||
|
||||
// Lower Resolution (or first if not set yet)
|
||||
if lowestRes == 0 || res < lowestRes {
|
||||
lowestRes = res
|
||||
lowestResType = video.StreamType
|
||||
}
|
||||
}
|
||||
|
||||
// Return the streamType based on the selection
|
||||
switch streamResolution {
|
||||
case "hd":
|
||||
return highestResType
|
||||
case "sd":
|
||||
return lowestResType
|
||||
default:
|
||||
return defaultStreamType
|
||||
}
|
||||
}
|
||||
|
||||
// IsHEVC checks if the given streamType uses H265 (HEVC) codec
|
||||
// HEVC cameras use DataChannel, H264 cameras use RTP tracks
|
||||
// - codecType 4 = H265 (HEVC) → DataChannel mode
|
||||
// - codecType 2 = H264 → Normal RTP mode
|
||||
func (c *TuyaClient) IsHEVC(streamType int) bool {
|
||||
for _, video := range c.skill.Videos {
|
||||
if video.StreamType == streamType {
|
||||
return video.CodecType == 4 // 4 = H265/HEVC
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *TuyaClient) GetVideoCodecs() []*core.Codec {
|
||||
if len(c.skill.Videos) > 0 {
|
||||
codecs := make([]*core.Codec, 0)
|
||||
|
||||
for _, video := range c.skill.Videos {
|
||||
name := core.CodecH264
|
||||
if c.IsHEVC(video.StreamType) {
|
||||
name = core.CodecH265
|
||||
}
|
||||
|
||||
codec := &core.Codec{
|
||||
Name: name,
|
||||
ClockRate: uint32(video.SampleRate),
|
||||
}
|
||||
|
||||
codecs = append(codecs, codec)
|
||||
}
|
||||
|
||||
if len(codecs) > 0 {
|
||||
return codecs
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TuyaClient) GetAudioCodecs() []*core.Codec {
|
||||
if len(c.skill.Audios) > 0 {
|
||||
codecs := make([]*core.Codec, 0)
|
||||
|
||||
for _, audio := range c.skill.Audios {
|
||||
name := getAudioCodecName(&audio)
|
||||
|
||||
codec := &core.Codec{
|
||||
Name: name,
|
||||
ClockRate: uint32(audio.SampleRate),
|
||||
Channels: uint8(audio.Channels),
|
||||
}
|
||||
codecs = append(codecs, codec)
|
||||
}
|
||||
|
||||
if len(codecs) > 0 {
|
||||
return codecs
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TuyaClient) Close() {
|
||||
c.mqtt.Stop()
|
||||
c.httpClient.CloseIdleConnections()
|
||||
}
|
||||
|
||||
// https://protect-us.ismartlife.me/
|
||||
func getAudioCodecName(audioSkill *AudioSkill) string {
|
||||
switch audioSkill.CodecType {
|
||||
// case 100:
|
||||
// return "ADPCM"
|
||||
case 101:
|
||||
return core.CodecPCML
|
||||
case 102, 103, 104:
|
||||
return core.CodecAAC
|
||||
case 105:
|
||||
return core.CodecPCMU
|
||||
case 106:
|
||||
return core.CodecPCMA
|
||||
// case 107:
|
||||
// return "G726-32"
|
||||
// case 108:
|
||||
// return "SPEEX"
|
||||
case 109:
|
||||
return core.CodecMP3
|
||||
default:
|
||||
return core.CodecPCML
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user