Yes, it can be done!

Stream from the New Nest Camera (Battery) to the Web

The new Nest Camera (Battery) lacks many of the features of its predecessors. The feed can only be viewed through the Google Home app on your phone. Most disappointingly, there is no easy way to embed the camera feed on a public website... but I found a difficult way!

I love webcams – ones that are placed in fun places with a good view to let you look out at the world from the comfort of your laptop. I’ve noticed that some of my favorite shared cams were from Nest, so when I needed to set up a camera for the Dolphin Club, I just went out and bought the latest Google Nest Camera expecting to be able to set it up quickly and easily.

That was a mistake.

Google acquired Nest a few years ago and you’d think that acquisition would lead to better products. Sadly not. I soon discovered the new Nest Camera (Battery) lacks many of the features of its ‘Nest’ predecessors. Most notably, it is not controllable from the old Nest ecosystem, and the feed can only be viewed through the Google Home app on your phone. Most disappointingly, there is no easy way to embed the camera feed on a public website.

With the older Nest Cameras it looks like it is pretty simple to add a video feed into a website via an iframe. Clunky, but functional. With the new cameras, that option is not even available. Product reviews online are fairly scathing about this regression, and people seem to just be hoping until sometime in 2022 when Google might add some of this functionality.

But, I looked further, and Google does have Smart Device Management API where there are some WebRTC endpoints for the new type of Camera.

Hmmm, I wondered, Is it possible to use the APIs to connect to it?

So, I set off for a day of futzing, and I was able to create a streaming feed with the Nest Camera (Battery) API to make a live feed for the Dolphin Club Weather Page

So, yes, it’s possible, but it is not for the faint of heart. You’ll need to be familiar with the Google ecosystem, Javascript, APIs and the like.

This is not a tutorial, but more of a roadmap for how you can get it to work – with some notes about all the problems I ran into along the way.

Streaming to the Web from Google’s Device API

The first hoop to jump through is authorization. The camera is accessible only to viewers with a valid oAuth2 token. To create these tokens, you’ll need to setup a google developer account and configure and link a bunch of Google Cloud Services.

The Nest API Getting Started Guide does a good job of walking you through this part, so I won’t duplicate it too much. It’s a bunch of steps, but you only have to do it once.

Summary:

  1. With the Google Account that you use to access your Google Home network, you’ll register for Device Access – This costs $5.
  2. You’ll then to create a project in Google Cloud and get an oAuth2 Client ID. If you’re not already up and running with creating projects in GCP, this may take some time.
  3. You’ll then need to Create a “Device Project” from the Device Access Console.
  4. Then you’ll need to Link those accounts using the Partner Connections Manager. This will give you an authorization code which will allow your developer account to create tokens.
  5. Then you’ll use that Auth code and your oAuth id and secret to Create an Access Token.
  6. Finally, use your access token to make a devices.list call. This first call activates the account.

Simple, right?

Going forward, the access tokens are what you’ll use to authorize the request for the video feed. Tokens only last for an hour, so you’ll need to get a new token before you make a call. You can either call for a new token, or just keep refreshing the existing token every hour and store it.

Refreshing the token is fairly straightforward, and you can test it out with CURL.

curl -L -X POST 'https://www.googleapis.com/oauth2/v4/token?client_id=<<oauth2-client-id>>&client_secret=<<oauth2-client-secret>>&refresh_token=<<refresh-token>>&grant_type=refresh_token'

It doesn’t appear that generating new tokens invalidate the old tokens. I’m not quite sure what the benefit of refreshing a token is versus creating a whole new one.

We’re not done with oAuth stuff yet, but at this stage you should be able to create an access token from the command line. You can start using that to test connecting to the webRTC feed.

Creating a One-Way Stream with WebRTC

Google have setup their new cameras to stream audio and video using WebRTC.

WebRTC is designed to enable peer-to-peer communications over the web and relies less on media servers to relay in between. In theory, WebRTC should allow you to build your own version of Zoom that runs entirely on web browsers. Theory and reality are still a long ways off, but there are lots of tutorials that will help you build a two person chat room using webRTC.

Google are using WebRTC to allow web browsers to communicate directly with the device and establish a one-way stream. This is a big change from most other webcams and Nest cams which used RTSP combined with a media server to rebroadcast the stream.

I haven’t been able to load test the webcam, but even with WebRTC, I would imagine you don’t want too many people connecting to the camera directly and consuming the feed.

Building a one-way stream from the camera to the browser is an uncommon use case. It both makes it simpler, but also adds some conditions to watch out for.

This is the JS code that finally worked for me.

const configuration = {
  iceServers: [
    {
      urls: [
        'stun:stun1.l.google.com:19302',
        'stun:stun2.l.google.com:19302',
      ],
    },
  ],
  iceCandidatePoolSize: 10,
};


let peerConnection = null;
let remoteStream = null;


async function startFeed() {

  peerConnection = new RTCPeerConnection(configuration);
  const token = "<AUTH_CODE>"; // You'll need to change this. 
  
  // Prep remote stream
  remoteStream = new MediaStream();
  document.querySelector('#webCamVideo').srcObject = remoteStream;

  // Watch for new tracks from remote stream; add to video stream
  peerConnection.ontrack = event => {
    event.streams[0].getTracks().forEach(track => {
        remoteStream.addTrack(track);
    });
  };

  // Data Channel Required by the SDM API.
  peerConnection.createDataChannel("dataSendChannel");


  // Create the offer.
  const offer = await peerConnection.createOffer({
     offerToReceiveAudio: true, 
     offerToReceiveVideo: true 
  });
  peerConnection.addTransceiver('audio', { direction: 'recvonly' });
  await peerConnection.setLocalDescription(offer);
  
  // Safari doesn't respect the parameter requests of createOffer above, so let's fudge the offer ourselves!
  let offerSdp = offer.sdp;
  offerSdp = offerSdp.replace('a=sendrecv','a=recvonly');

  // Now, send request for SDP connection
  const url = 'https://smartdevicemanagement.googleapis.com/v1/enterprises/<PROJECT_CODE>/devices/<DEVICE_ID>:executeCommand';
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + token
    },
    body: JSON.stringify({
      "command": "sdm.devices.commands.CameraLiveStream.GenerateWebRtcStream",
      "params" : {
        "offerSdp" : offerSdp
      }      
    })
  };
  const response = await fetch(url, options);
  
  // Process the response.
  const data = await response.json();
  let answerSdp = data.results.answerSdp;
  
  // Now start the feed using the answer
  const answer = new RTCSessionDescription({type: 'answer', sdp: answerSdp});
  await peerConnection.setRemoteDescription(answer);

  // The video should now be running.
}

startFeed();

Storing your Auth Code

Authorization tokens are only valid for 3600 seconds (1hr), so you’ll need to handle this. You could generate a new token with Javascript direct from the browser before each RTC call, but that would require you to leave all your secret codes hanging out for anyone to use and reuse.

So it’s better that you generate the auth code on the server and make that accessible as another endpoint. This isn’t terribly complex, but how you do it will depend on what server side backend you have access to.

In my case, the feed is being embedded in a WordPress site, so I set up a cron job to create a new auth token every hour, and save it as a field in the WordPress database. The token is then loaded with the page and is always available for use.

Resources

Here are some resources I found useful:

Leave a Comment