Merge pull request #1629 from hsakoh/feature/addSwitchBotSupport

Add client for SwitchBot Camera WebRTC
This commit is contained in:
Alex X
2025-03-09 18:48:40 +03:00
committed by GitHub
4 changed files with 82 additions and 22 deletions

View File

@@ -682,13 +682,18 @@ Supports connection to [Wyze](https://www.wyze.com/) cameras, using WebRTC proto
Supports [Amazon Kinesis Video Streams](https://aws.amazon.com/kinesis/video-streams/), using WebRTC protocol. You need to specify signalling WebSocket URL with all credentials in query params, `client_id` and `ice_servers` list in [JSON format](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer).
**switchbot**
Support connection to [SwitchBot](https://us.switch-bot.com/) cameras that are based on Kinesis Video Streams. Specifically, this includes [Pan/Tilt Cam Plus 2K](https://us.switch-bot.com/pages/switchbot-pan-tilt-cam-plus-2k) and [Pan/Tilt Cam Plus 3K](https://us.switch-bot.com/pages/switchbot-pan-tilt-cam-plus-3k). `Outdoor Spotlight Cam 1080P`, `Outdoor Spotlight Cam 2K`, `Pan/Tilt Cam`, `Pan/Tilt Cam 2K`, `Indoor Cam` are based on Tuya, so this feature is not available.
```yaml
streams:
webrtc-whep: webrtc:http://192.168.1.123:1984/api/webrtc?src=camera1
webrtc-go2rtc: webrtc:ws://192.168.1.123:1984/api/ws?src=camera1
webrtc-openipc: webrtc:ws://192.168.1.123/webrtc_ws#format=openipc#ice_servers=[{"urls":"stun:stun.kinesisvideo.eu-north-1.amazonaws.com:443"}]
webrtc-wyze: webrtc:http://192.168.1.123:5000/signaling/camera1?kvs#format=wyze
webrtc-kinesis: webrtc:wss://...amazonaws.com/?...#format=kinesis#client_id=...#ice_servers=[{...},{...}]
webrtc-whep: webrtc:http://192.168.1.123:1984/api/webrtc?src=camera1
webrtc-go2rtc: webrtc:ws://192.168.1.123:1984/api/ws?src=camera1
webrtc-openipc: webrtc:ws://192.168.1.123/webrtc_ws#format=openipc#ice_servers=[{"urls":"stun:stun.kinesisvideo.eu-north-1.amazonaws.com:443"}]
webrtc-wyze: webrtc:http://192.168.1.123:5000/signaling/camera1?kvs#format=wyze
webrtc-kinesis: webrtc:wss://...amazonaws.com/?...#format=kinesis#client_id=...#ice_servers=[{...},{...}]
webrtc-switchbot: webrtc:wss://...amazonaws.com/?...#format=switchbot#resolution=hd#client_id=...#ice_servers=[{...},{...}]
```
**PS.** For `kinesis` sources you can use [echo](#source-echo) to get connection params using `bash`/`python` or any other script language.

View File

@@ -41,9 +41,11 @@ func streamsHandler(rawURL string) (core.Producer, error) {
// https://aws.amazon.com/kinesis/video-streams/
// https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/what-is-kvswebrtc.html
// https://github.com/orgs/awslabs/repositories?q=kinesis+webrtc
return kinesisClient(rawURL, query, "webrtc/kinesis")
return kinesisClient(rawURL, query, "webrtc/kinesis", nil)
} else if format == "openipc" {
return openIPCClient(rawURL, query)
} else if format == "switchbot" {
return switchbotClient(rawURL, query)
} else {
return go2rtcClient(rawURL)
}

View File

@@ -34,7 +34,10 @@ func (k kinesisResponse) String() string {
return fmt.Sprintf("type=%s, payload=%s", k.Type, k.Payload)
}
func kinesisClient(rawURL string, query url.Values, format string) (core.Producer, error) {
func kinesisClient(
rawURL string, query url.Values, format string,
sdpOffer func(prod *webrtc.Conn, query url.Values) (any, error),
) (core.Producer, error) {
// 1. Connect to signalign server
conn, _, err := websocket.DefaultDialer.Dial(rawURL, nil)
if err != nil {
@@ -108,23 +111,33 @@ func kinesisClient(rawURL string, query url.Values, format string) (core.Produce
}
})
medias := []*core.Media{
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
var payload any
if sdpOffer == nil {
medias := []*core.Media{
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
}
// 4. Create offer
var offer string
if offer, err = prod.CreateOffer(medias); err != nil {
return nil, err
}
// 5. Send offer
payload = pion.SessionDescription{
Type: pion.SDPTypeOffer,
SDP: offer,
}
} else {
if payload, err = sdpOffer(prod, query); err != nil {
return nil, err
}
}
// 4. Create offer
offer, err := prod.CreateOffer(medias)
if err != nil {
return nil, err
}
// 5. Send offer
req.Action = "SDP_OFFER"
req.Payload, _ = json.Marshal(pion.SessionDescription{
Type: pion.SDPTypeOffer,
SDP: offer,
})
req.Payload, _ = json.Marshal(payload)
if err = conn.WriteJSON(req); err != nil {
return nil, err
}
@@ -218,5 +231,5 @@ func wyzeClient(rawURL string) (core.Producer, error) {
"ice_servers": []string{string(kvs.Servers)},
}
return kinesisClient(kvs.URL, query, "webrtc/wyze")
return kinesisClient(kvs.URL, query, "webrtc/wyze", nil)
}

View File

@@ -0,0 +1,40 @@
package webrtc
import (
"net/url"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/webrtc"
)
func switchbotClient(rawURL string, query url.Values) (core.Producer, error) {
return kinesisClient(rawURL, query, "webrtc/switchbot", func(prod *webrtc.Conn, query url.Values) (any, error) {
medias := []*core.Media{
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
}
offer, err := prod.CreateOffer(medias)
if err != nil {
return nil, err
}
v := struct {
Type string `json:"type"`
SDP string `json:"sdp"`
Resolution int `json:"resolution"`
PlayType int `json:"play_type"`
}{
Type: "offer",
SDP: offer,
}
switch query.Get("resolution") {
case "hd":
v.Resolution = 0
case "sd":
v.Resolution = 1
}
return v, nil
})
}