Add client for SwitchBot Camera WebRTC (supports special SessionDescription).
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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", &kinesisClientOpts{})
|
||||
} else if format == "openipc" {
|
||||
return openIPCClient(rawURL, query)
|
||||
} else if format == "switchbot" {
|
||||
return switchbotClient(rawURL, query)
|
||||
} else {
|
||||
return go2rtcClient(rawURL)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,12 @@ 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) {
|
||||
type kinesisClientOpts struct {
|
||||
SessionDescriptionModifier func(*pion.SessionDescription) ([]byte, error)
|
||||
MediaModifier func() ([]*core.Media, error)
|
||||
}
|
||||
|
||||
func kinesisClient(rawURL string, query url.Values, format string, opts *kinesisClientOpts) (core.Producer, error) {
|
||||
// 1. Connect to signalign server
|
||||
conn, _, err := websocket.DefaultDialer.Dial(rawURL, nil)
|
||||
if err != nil {
|
||||
@@ -112,6 +117,12 @@ func kinesisClient(rawURL string, query url.Values, format string) (core.Produce
|
||||
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
|
||||
{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
|
||||
}
|
||||
if opts.MediaModifier != nil {
|
||||
medias, err = opts.MediaModifier()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Create offer
|
||||
offer, err := prod.CreateOffer(medias)
|
||||
@@ -121,10 +132,15 @@ func kinesisClient(rawURL string, query url.Values, format string) (core.Produce
|
||||
|
||||
// 5. Send offer
|
||||
req.Action = "SDP_OFFER"
|
||||
req.Payload, _ = json.Marshal(pion.SessionDescription{
|
||||
sessionDescription := pion.SessionDescription{
|
||||
Type: pion.SDPTypeOffer,
|
||||
SDP: offer,
|
||||
})
|
||||
}
|
||||
if opts.SessionDescriptionModifier != nil {
|
||||
req.Payload, _ = opts.SessionDescriptionModifier(&sessionDescription)
|
||||
} else {
|
||||
req.Payload, _ = json.Marshal(sessionDescription)
|
||||
}
|
||||
if err = conn.WriteJSON(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -218,5 +234,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", &kinesisClientOpts{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
pion "github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
// SessionDescription is used to expose local and remote session descriptions.
|
||||
type SwitchBotSessionDescription struct {
|
||||
Type string `json:"type"`
|
||||
SDP string `json:"sdp"`
|
||||
Resolution SwitchBotResolution `json:"resolution"`
|
||||
PlayType int `json:"play_type"`
|
||||
}
|
||||
|
||||
func switchbotClient(rawURL string, query url.Values) (core.Producer, error) {
|
||||
return kinesisClient(rawURL, query, "webrtc/switchbot", &kinesisClientOpts{
|
||||
SessionDescriptionModifier: func(sd *pion.SessionDescription) ([]byte, error) {
|
||||
resolution, ok := parseSwitchBotResolution(query.Get("resolution"))
|
||||
if !ok {
|
||||
resolution = SwitchBotResolutionSD
|
||||
}
|
||||
json, err := json.Marshal(SwitchBotSessionDescription{
|
||||
Type: sd.Type.String(),
|
||||
SDP: sd.SDP,
|
||||
Resolution: resolution,
|
||||
PlayType: 0,
|
||||
})
|
||||
return json, err
|
||||
},
|
||||
MediaModifier: func() ([]*core.Media, error) {
|
||||
return []*core.Media{
|
||||
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
|
||||
//{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
|
||||
//{Kind: core.KindAudio, Direction: core.DirectionSendRecv},
|
||||
//{Kind: "Data", Direction: core.DirectionSendRecv},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type SwitchBotResolution int
|
||||
|
||||
const (
|
||||
SwitchBotResolutionHD SwitchBotResolution = 0
|
||||
SwitchBotResolutionSD = 1
|
||||
)
|
||||
|
||||
func parseSwitchBotResolution(str string) (SwitchBotResolution, bool) {
|
||||
var (
|
||||
resolutionMap = map[string]SwitchBotResolution{
|
||||
"hd": SwitchBotResolutionHD,
|
||||
"sd": SwitchBotResolutionSD,
|
||||
}
|
||||
)
|
||||
c, ok := resolutionMap[strings.ToLower(str)]
|
||||
return c, ok
|
||||
}
|
||||
Reference in New Issue
Block a user