Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nest extend stream #855

Merged
merged 3 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ streams:

Any cameras in WebRTC format are supported. But at the moment Home Assistant only supports some [Nest](https://www.home-assistant.io/integrations/nest/) cameras in this fomat.

The Nest API only allows you to get a link to a stream for 5 minutes. So every 5 minutes the stream will be reconnected.
**Important.** The Nest API only allows you to get a link to a stream for 5 minutes. Do not use this with Frigate! If the stream expires, Frigate will consume all available ram on your machine within seconds. It's recommended to use [Nest source](#source-nest) - it supports extending the stream.

```yaml
streams:
Expand Down Expand Up @@ -610,7 +610,7 @@ streams:

*[New in v1.6.0](https://github.com/AlexxIT/go2rtc/releases/tag/v1.6.0)*

Currently only WebRTC cameras are supported. Stream reconnects every 5 minutes.
Currently only WebRTC cameras are supported.

For simplicity, it is recommended to connect the Nest/WebRTC camera to the [Home Assistant](#source-hass). But if you can somehow get the below parameters - Nest/WebRTC source will work without Hass.

Expand Down
82 changes: 81 additions & 1 deletion pkg/nest/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import (
type API struct {
Token string
ExpiresAt time.Time

StreamProjectID string
StreamDeviceID string
StreamSessionID string
StreamExpiresAt time.Time

extendTimer *time.Timer
}

type Auth struct {
Expand Down Expand Up @@ -159,17 +166,73 @@ func (a *API) ExchangeSDP(projectID, deviceID, offer string) (string, error) {
Results struct {
Answer string `json:"answerSdp"`
ExpiresAt time.Time `json:"expiresAt"`
MediaSessionId string `json:"mediaSessionId"`
MediaSessionID string `json:"mediaSessionId"`
} `json:"results"`
}

if err = json.NewDecoder(res.Body).Decode(&resv); err != nil {
return "", err
}

a.StreamProjectID = projectID
a.StreamDeviceID = deviceID
a.StreamSessionID = resv.Results.MediaSessionID
a.StreamExpiresAt = resv.Results.ExpiresAt

return resv.Results.Answer, nil
}

func (a *API) ExtendStream() error {
var reqv struct {
Command string `json:"command"`
Params struct {
MediaSessionID string `json:"mediaSessionId"`
} `json:"params"`
}
reqv.Command = "sdm.devices.commands.CameraLiveStream.ExtendWebRtcStream"
reqv.Params.MediaSessionID = a.StreamSessionID

b, err := json.Marshal(reqv)
if err != nil {
return err
}

uri := "https://smartdevicemanagement.googleapis.com/v1/enterprises/" +
a.StreamProjectID + "/devices/" + a.StreamDeviceID + ":executeCommand"
req, err := http.NewRequest("POST", uri, bytes.NewReader(b))
if err != nil {
return err
}

req.Header.Set("Authorization", "Bearer "+a.Token)

client := &http.Client{Timeout: time.Second * 5000}
res, err := client.Do(req)
if err != nil {
return err
}

if res.StatusCode != 200 {
return errors.New("nest: wrong status: " + res.Status)
}

var resv struct {
Results struct {
ExpiresAt time.Time `json:"expiresAt"`
MediaSessionID string `json:"mediaSessionId"`
} `json:"results"`
}

if err = json.NewDecoder(res.Body).Decode(&resv); err != nil {
return err
}

a.StreamSessionID = resv.Results.MediaSessionID
a.StreamExpiresAt = resv.Results.ExpiresAt

return nil
}

type Device struct {
Name string `json:"name"`
Type string `json:"type"`
Expand Down Expand Up @@ -203,3 +266,20 @@ type Device struct {
// DisplayName string `json:"displayName"`
//} `json:"parentRelations"`
}

func (a *API) StartExtendStreamTimer() {
// Calculate the duration until 30 seconds before the stream expires
duration := time.Until(a.StreamExpiresAt.Add(-30 * time.Second))
a.extendTimer = time.AfterFunc(duration, func() {
if err := a.ExtendStream(); err != nil {
return
}
duration = time.Until(a.StreamExpiresAt.Add(-30 * time.Second))
a.extendTimer.Reset(duration)
})

}

func (a *API) StopExtendStreamTimer() {
a.extendTimer.Stop()
}
5 changes: 4 additions & 1 deletion pkg/nest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

type Client struct {
conn *webrtc.Conn
api *API
}

func NewClient(rawURL string) (*Client, error) {
Expand Down Expand Up @@ -74,7 +75,7 @@ func NewClient(rawURL string) (*Client, error) {
return nil, err
}

return &Client{conn: conn}, nil
return &Client{conn: conn, api: nestAPI}, nil
}

func (c *Client) GetMedias() []*core.Media {
Expand All @@ -90,10 +91,12 @@ func (c *Client) AddTrack(media *core.Media, codec *core.Codec, track *core.Rece
}

func (c *Client) Start() error {
c.api.StartExtendStreamTimer()
return c.conn.Start()
}

func (c *Client) Stop() error {
c.api.StopExtendStreamTimer()
return c.conn.Stop()
}

Expand Down