mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
175 lines
4.1 KiB
Go
175 lines
4.1 KiB
Go
package nest
|
|
|
|
import (
|
|
"errors"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/AlexxIT/go2rtc/pkg/rtsp"
|
|
"github.com/AlexxIT/go2rtc/pkg/webrtc"
|
|
pion "github.com/pion/webrtc/v3"
|
|
)
|
|
|
|
type WebRTCClient struct {
|
|
conn *webrtc.Conn
|
|
api *API
|
|
}
|
|
|
|
type RTSPClient struct {
|
|
conn *rtsp.Conn
|
|
api *API
|
|
}
|
|
|
|
func Dial(rawURL string) (core.Producer, error) {
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
query := u.Query()
|
|
cliendID := query.Get("client_id")
|
|
cliendSecret := query.Get("client_secret")
|
|
refreshToken := query.Get("refresh_token")
|
|
projectID := query.Get("project_id")
|
|
deviceID := query.Get("device_id")
|
|
protocols := strings.Split(query.Get("protocols"), ",")
|
|
|
|
// Default to WEB_RTC for backwards compataiility
|
|
if len(protocols) == 0 {
|
|
protocols = append(protocols, "WEB_RTC")
|
|
}
|
|
|
|
if cliendID == "" || cliendSecret == "" || refreshToken == "" || projectID == "" || deviceID == "" {
|
|
return nil, errors.New("nest: wrong query")
|
|
}
|
|
|
|
nestAPI, err := NewAPI(cliendID, cliendSecret, refreshToken)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Pick the first supported protocol in order of priority (WEB_RTC, RTSP)
|
|
for _, proto := range protocols {
|
|
if proto == "WEB_RTC" {
|
|
return rtcConn(nestAPI, rawURL, projectID, deviceID)
|
|
} else if proto == "RTSP" {
|
|
return rtspConn(nestAPI, rawURL, projectID, deviceID)
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("nest: unsupported camera")
|
|
}
|
|
|
|
func (c *WebRTCClient) GetMedias() []*core.Media {
|
|
return c.conn.GetMedias()
|
|
}
|
|
|
|
func (c *WebRTCClient) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
|
|
return c.conn.GetTrack(media, codec)
|
|
}
|
|
|
|
func (c *WebRTCClient) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
|
|
return c.conn.AddTrack(media, codec, track)
|
|
}
|
|
|
|
func (c *WebRTCClient) Start() error {
|
|
c.api.StartExtendStreamTimer()
|
|
return c.conn.Start()
|
|
}
|
|
|
|
func (c *WebRTCClient) Stop() error {
|
|
c.api.StopExtendStreamTimer()
|
|
return c.conn.Stop()
|
|
}
|
|
|
|
func (c *WebRTCClient) MarshalJSON() ([]byte, error) {
|
|
return c.conn.MarshalJSON()
|
|
}
|
|
|
|
func rtcConn(nestAPI *API, rawURL, projectID, deviceID string) (*WebRTCClient, error) {
|
|
rtcAPI, err := webrtc.NewAPI()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conf := pion.Configuration{}
|
|
pc, err := rtcAPI.NewPeerConnection(conf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn := webrtc.NewConn(pc)
|
|
conn.FormatName = "nest/webrtc"
|
|
conn.Mode = core.ModeActiveProducer
|
|
conn.Protocol = "http"
|
|
conn.URL = rawURL
|
|
|
|
// https://developers.google.com/nest/device-access/traits/device/camera-live-stream#generatewebrtcstream-request-fields
|
|
medias := []*core.Media{
|
|
{Kind: core.KindAudio, Direction: core.DirectionRecvonly},
|
|
{Kind: core.KindVideo, Direction: core.DirectionRecvonly},
|
|
{Kind: "app"}, // important for Nest
|
|
}
|
|
|
|
// 3. Create offer with candidates
|
|
offer, err := conn.CreateCompleteOffer(medias)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 4. Exchange SDP via Hass
|
|
answer, err := nestAPI.ExchangeSDP(projectID, deviceID, offer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 5. Set answer with remote medias
|
|
if err = conn.SetAnswer(answer); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &WebRTCClient{conn: conn, api: nestAPI}, nil
|
|
}
|
|
|
|
func rtspConn(nestAPI *API, rawURL, projectID, deviceID string) (*RTSPClient, error) {
|
|
rtspURL, err := nestAPI.GenerateRtspStream(projectID, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rtspClient := rtsp.NewClient(rtspURL)
|
|
if err := rtspClient.Dial(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rtspClient.Describe(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &RTSPClient{conn: rtspClient, api: nestAPI}, nil
|
|
}
|
|
|
|
func (c *RTSPClient) GetMedias() []*core.Media {
|
|
result := c.conn.GetMedias()
|
|
return result
|
|
}
|
|
|
|
func (c *RTSPClient) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
|
|
return c.conn.GetTrack(media, codec)
|
|
}
|
|
|
|
func (c *RTSPClient) Start() error {
|
|
c.api.StartExtendStreamTimer()
|
|
return c.conn.Start()
|
|
}
|
|
|
|
func (c *RTSPClient) Stop() error {
|
|
c.api.StopRTSPStream()
|
|
c.api.StopExtendStreamTimer()
|
|
return c.conn.Stop()
|
|
}
|
|
|
|
func (c *RTSPClient) MarshalJSON() ([]byte, error) {
|
|
return c.conn.MarshalJSON()
|
|
}
|