Video Calls in the Time of Covid


About Us

We’re Apollo350, a full-service software agency who will help you craft and execute your video product strategy.

I decided to build a simple tool that would alert the people in my house when I am on a video call, and write up the process behind it. Think of this as a template for using CDK to rapidly prototype an idea.

The Problem

As a company that specializes in video products, we are often on video calls or recording ourselves. Having someone walk into the background can be distracting, and sometimes embarrassing. 

We could send everybody in the house a text message when we are about to do a call, but that’s no fun. Let’s build a tool to alert people to a video call starting, and ending. Think of this as a template for prototyping a quick and dirty automated task.

The Solution

The solution seems simple, detect a video call has started or ended, and then send an alert to everyone in the house. 

The solution can be split into two components, triggers and alerts. Triggers are how we detect that a video call has started or ended. Alerts are the way that we notify everyone necessary.

We have a lot of leeway for how we can approach this, so let’s break down a few different solutions.

Alerts

Here are a few different alert mechanisms that could be used, once we have started or ended a video call.

1. SMS Messages

Virtually everybody has a phone, and most people have unlimited SMS messages, so this seems like a no-brainer. Using the Twilio API, we could message everyone in a cross platform manner, at low to no cost.

2. Flashing lights and a siren

This is obnoxious and unnecessary, but it is more fun...

Triggers

Here are three different trigger mechanisms that could be used, with different levels of practicality.

1. Zoom API

Zoom does have a webhook API to detect state changes, but of course this would only work if the call is on Zoom. Since we develop a lot of custom video products, this won’t always be the case.

2. Hook into the webcam itself

If we can hook into the webcam’s state itself, that would be the most effective trigger, as it would work for any kind of video application. However, since webcams are hardware devices, any solution may be device specific.

3. Big Red Button

Hitting a button whenever I get on a video all would be manual, but it would also be a lot of fun.

The Optimal Solutions

For triggers, a Zoom webhook is the most cross-platform solution, and has the advantage of working automatically on all devices, without any installation required. Let’s focus on building this as the only trigger.

For notifications, SMS is obviously the better solution, but it isn’t location aware. If someone isn’t home, they don’t need to know I am going on a call. The lights and sirens would only let people 

in the vicinity know, but they are definitely distracting.

So, let’s use Zoom and SMS!

Infrastructure

The application will be split into just two components:

  1. Video call detection. This will see that a video call has started or ended, and then post to the trigger handler. This is a combination of a Zoom Webhook Application, and a local script running on the user’s computer. 
  2. Notification gateway. This will be a lambda function that will accept and validate incoming webhooks, and then send notifications to the end users.

What is CDK?

CDK is infrastructure as code. There’s no need to dive into the AWS console, and dig through menus and inputs to define your stack. Instead, you write a configuration, and it builds the whole stack for you. 

The downside of this, is that there is much less discoverability of features. Digging through menus and web interfaces often reveals hidden functionality. In exchange, however, you get a fully reproducible stack, that can be emulated locally.

Getting started with CDK

CDK reads your AWS credentials from your environment, if they are set up. After installing AWS CLI, you can run `aws configure` to populate your local credentials.

Next, install CDK,

npm install -g aws-cdk

And then create our initial CDK project.

mkdir on-call
cd on-call
cdk init app --language javascript

We now have a bare bones CDK project. Let’s add a lambda, to handle our incoming webhooks.

Adding a lambda

npm install @aws-cdk/aws-lambda
npm install @aws-cdk/aws-apigateway

Let’s define the lambda, by adding trigger.js to on-call/lib 

// Read in .env variables, so we don't have to commit them anywhere, or pollute process.env
const {
  ZOOM_ACCESS_KEY,
} = dotenv.parse(fs.readFileSync('.env'));

const handler = new lambda.Function(this, "TriggerHandler", {
  runtime: lambda.Runtime.NODEJS_14_X,
  code: lambda.Code.fromAsset("resources"),
  handler: "trigger.main",
  environment: {
    ZOOM_ACCESS_KEY,
  },
});

const api = new apigateway.RestApi(this, "trigger-api", {
  restApiName: "Trigger Service",
  description: "This service handles video call triggers.",
});

const getTriggerIntegration = new apigateway.LambdaIntegration(handler, {
  requestTemplates: {
    "application/json": '{ "statusCode": "200" }'
  },
});

api.root.addMethod("POST", getTriggerIntegration); // POST /

And adding resources / trigger.js

/*
This code uses callbacks to handle asynchronous function responses.
It currently demonstrates using an async-await pattern.
AWS supports both the async-await and promises patterns.
For more information, see the following:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/calling-services-asynchronously.html
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
*/

const zoomEvents = {
  STATUS_UPDATED: 'user.presence_status_updated',
};

exports.main = async function (event, context) {
  try {
    const method = event.httpMethod;

    if (method !== "POST") {
      return {
        statusCode: 400,
        headers: {},
        body: "We only accept POST",
      };
    }

    // parse JSON body
    let requestBody = null;
    try {
      requestBody = JSON.parse(event.body);
    } catch (e) {
      return {
        statusCode: 400,
        headers: {},
        body: "Body should be a JSON string",
      };
    }

    // Confirm access key is valid
    if (requestBody ? .payload ? .object ? .id !== process.env.ZOOM_ACCESS_KEY) {
      return {
        statusCode: 400,
        headers: {},
        body: "Invalid ZOOM Access Key",
      };
    }

    const eventType = requestBody ? .event;

    // We only care about status events
    if (eventType !== zoomEvents.STATUS_UPDATED) {
      return {
        statusCode: 200,
        headers: {},
        body: "Event ignored",
      };
    }

    // Success!
    return {
      statusCode: 200,
      headers: {},
      body: "Status has changed",
    };
  } catch (error) {
    const body = error.stack || JSON.stringify(error, null, 2);
    return {
      statusCode: 400,
      headers: {},
      body: JSON.stringify(body),
    }
  }
}

Now we have an API gateway, with a lambda function behind it. Because we are using lamda.Code.fromAsset, we need to run cdk bootstrap once, to pull in some more dependencies.

cdk bootstrap

Testing locally

We can use SAM to run our stack locally, for rapid testing. First we need to synthesize CDK, so that SAM can understand our stack. 

cdk synth --no-staging > template.yaml

Note: CDK will inject your provided environment variables into this template.yaml. Make sure you do not add it to git!

Next, let’s fire up SAM, to test our local API

sam local start-api --warm-containers EAGER --port 3100

Behind the scenes SAM will read the template.yaml file, and pull in and build docker images to run your stack locally. I’ve added two extra arguments here, --port and --warm-containers. Port just sets the port we want the API to be exposed on, and --warm-containers will tell SAM to re-use the same docker container if there have been no code changes.

Without --warm-containers, SAM will rebuild the container for each request, which can be quite slow. However, be warned, there is currently a bug in SAM, where sending multiple concurrent tests to warm containers can cause SAM to start timing out. For development purposes, we are just sending single requests at a time, so that is not a concern here.

Now, Let’s test that our lambda is working correctly.

curl -X POST http://localhost:3100 -d '{}'

We should see “Invalid ZOOM Access Key”, which means that our API is running! 

Let’s try deploying our stack to AWS itself.

Deployment

cdk deploy

At the very end of the console output, CDK will return the URL for the API gateway, which is our public URL.

Now we’ve created an API gateway and a lambda stack, with secure environment variables, that can be easily built and deployed.

CDK can be used with almost every AWS service, so this can be extended to cover your entire infrastructure stack, including permissions, secret management and S3 storage.

Setting up a Zoom API Application

Now, to complete our trigger service, let’s create a webhook application, using the Zoom API. 

https://marketplace.zoom.us/docs/guides/build/webhook-only-app

The Zoom documentation for App creation is very well documented, so I won’t cover it here. In short, I created a zoom webhook application, with the URL of my API gateway as the webhook URL. I am listening to the User’s Presence Status has Changed event.

Sending Notifications

For SMS notifications, I will be using Twilio, which also has extensive API documentation. I have included the code to send SMS in the repository below, so you can start notifying your friends and family that you are very busy and successful online!

Summary

I hope that this post gave some insight into how and why CDK is so useful. Is there a feature of CDK that you think is worth mentioning? Let me know!

All of the code for this post is available at the repository below. Feel free to use it and abuse it any way you see fit.

https://github.com/Apollo350/on-call

And now, a challenge. There are services that let you trigger Alexa routines via a webhook, e.g. https://voicemonkey.io. What is the most distracting way you can tell the whole world you are hopping on a call? Bonus points for loud noises and bright colours.