Have you used cloud functions yet? They come in many flavors: Amazon Lambda, Cloudflare Workers, Zeit Serverless Functions, and the one we’re using here: Netlify Functions.
Cloud functions are an essential component of the JAMstack. I had not heard the name JAMstack until recently. For the uninitiated (like me) it stands for Javascript, APIs and Markup. You may have seen JAMstack technologies like Gatsby, Next.js and tools of this nature that focus on performance, new developer tooling, and leveraging CDNs to serve pre-compiled HTML pages. I will be at JAMstack Conf 2019 in SF, if you will be there too, then come find me and say hi!
All the code in this post is open source here on GitHub in our examples repo: muxinc/examples/signed-playback-netlify.
The Main Benefits of Cloud Functions
- Run code close to your clients (clients can be browsers, mobile apps, internet-of-things devices, self-driving cars, drones, anything that is talking to your server). Like a CDN, cloud functions are deployed to edge data centers to minimize latency between your clients and the server that runs their code.
- Protect your origin servers from being flooded with traffic. Cloud functions are a good way to cache data and intercept requests and respond to your users before they reach your origin servers. This means less bandwidth and CPU that your origin servers have to process.
Cloud functions, like Netlify Functions, might be a good option for you if you are using Mux’s Signed URLs feature.
A Little Background About Signed Urls
When you create a video asset via Mux’s POST /video
API you can also create a Playback ID (Mux API docs) and specify the playback_policy
as either "public"
or "signed"
.
A “public” playback policy can be played back on any site, in any player and does not have an expiration. A “signed” playback policy requires that when the playback URL is requested from a player, it has to be accompanied by a “token” param that is generated and signed on your server.
This is how it looks:
public playback URL:
https://stream.mux.com/${playbackId}.m3u8
signed playback URL:
https://stream.mux.com/${playbackId}.m3u8?token=${token}
The token
param is what you need to create on your server in order for the playback URL to work.
Create a Mux Asset
- Sign up for a mux.com account (free account comes with $20 credit)
- Go to settings/access-tokens and click “Generate new token” to create a token you can use for API calls
- Copy your token id (we'll call this
MUX_TOKEN_ID
) and secret (MUX_TOKEN_SECRET
). You will need these to make two api calls. - Create a Mux video asset with a “signed” playback policy
curl https://api.mux.com/video/v1/assets \
-X POST \
-H "Content-Type: application/json" \
-u ${MUX_TOKEN_ID}:${MUX_TOKEN_SECRET} \
-d '{ "input": "https://storage.googleapis.com/muxdemofiles/mux-video-intro.mp4", "playback_policy": "signed" }'
- Copy the
playback_id
from the response, you will need this later.
Create a URL Signing Key
curl https://api.mux.com/video/v1/signing-keys \
-X POST \
-H "Content-Type: application/json" \
-u ${MUX_TOKEN_ID}:${MUX_TOKEN_SECRET}
- Copy the
id
(MUX_TOKEN_ID
) and theprivate_key
(MUX_PRIVATE_KEY
) from the response, you will need these later. These are the keys you will need to create signed urls for playback.
Setup a Netlify project
- Create a new directory for your project
mkdir netlify-mux-signing && cd netlify-mux-signing
- Install the Netlify CLI and run
netlify init
to create a new project. You can choose to connect Netlify to a GitHub repository. - If you’re starting from scratch, run
yarn init
to create an emptypackage.json
andgit init
to make this a git repository. - Now you have a barebones project that is connected to Netlify, but nothing is in it yet (you can see there is a hidden and gitignored directory called
.netlify
which Netlify uses to handle deploys and Netlify commands - Run
yarn add netlify-lambda
to install the netlify-lambda package into your project (it’s recommended to install this locally instead of globally). - Run
yarn add @mux/mux-node
to add the Mux node SDK to your project
Step 1: Create a module to generate a signing token
Create a src/
folder in your project and let’s create a small module called mux_signatures.js
. It will export one function called signPlaybackId which takes a playback id and returns a token that is generated with Mux.JWT.sign
:
// ./src/mux_signatures
import Mux from '@mux/mux-node';
export const signPlaybackId = function (playbackId) {
return Mux.JWT.sign(playbackId, {
keyId: process.env.MUX_SIGNING_KEY,
keySecret: process.env.MUX_PRIVATE_KEY
})
}
Our lambda function is going to use this module in Step 2.
Step 2: Create a sign_playback_id
cloud function
Create a Netlify Function entry point. This is the single function that will handle one request. The idomatic pattern for creating cloud functions is to do one file and one javascript function per route. We will create a directory called functions/
and add a file called sign_playback_id.js
.
// ./functions/sign_playback_ids.js
const keySecret = process.env.MUX_PRIVATE_KEY
import { signPlaybackId } from './src/mux_signatures';
exports.handler = async (event, context) => {
try {
const { queryStringParameters } = event;
const { playbackId } = queryStringParameters;
if (!playbackId) {
return { statusCode: 400, body: JSON.stringify({errors: [{message: 'Missing playbackId in query string'}]}) };
}
const token = await signPlaybackId(playbackId);
return {
statusCode: 302,
headers: {
'Access-Control-Allow-Origin': '*',
location: `https://stream.mux.com/${playbackId}.m3u8?token=${token}`
},
body: '',
}
} catch (e) {
console.error(e);
return { statusCode: 500, body: JSON.stringify({ errors: [{message: 'Server Error'}] }) };
}
}
Step 3: Add netlify.toml
Add a netlify.toml
file to the root directory and tell Netlify where your functions will live. This tells Netlify that before we deploy we are going to build our functions into the ./.netlify/functions
directory
[build]
functions = "./.netlify/functions"
Step 4: Connect to git
In order to use Netlify Functions you will now need to commit your code and push it up to a git repository like GitHub. Do that next and in Netlify’s dashboard connect your git repository to the Netlify project that you created. After connecting you git repository then
Step 5: Set your environment variables
In your Netlify project dashboard, naviate to "Settings" > "Deploys" > “Environment” to set your environment variables. Enter the MUX_SIGNING_KEY
and MUX_PRIVATE_KEY
from the Create a URL Signing Key step above.
Step 6: Test in development
- Open one terminal and run
netlify dev
this will start a local Netlify dev server - Open another terminal window and run
netlify-lambda serve ./functions
this will build your functions/, get them ready to handle requests and watch the filesystem for changes. - In a third terminal window, curl your endpoint to test out the function (replace
<netlify-port>
and<playback-id>
with your values.
curl -I 'http://localhost:<netlify-port>/.netlify/functions/sign_playback_id?playbackId=<playback-id>'
You should see a 302 (redirect) response with a location
header for the signed url.
When you make any changes to your source files, netlify-lambda serve
will pick up on the changes and recompile the functions into ./.netlify/functions
.
Deploy
When you’re ready to deploy, you can deploy from the command line with netlify-lambda build ./functions && netlify deploy --prod
. This will build the functions and then push up the changes to Netlify.
Try making a POST request to your cloud function on Netlify:
curl -I 'https://<your-netlify-app>.netlify.com/.netlify/functions/sign_playback_id?playbackId=<playback-id>'
Just like in dev, you should get back a 302 response with a location
header that points to the signed playback URL.
https://stream.mux.com/${playbackId}.m3u8?token=${token}
This is what your response should look like:
HTTP/2 302
access-control-allow-origin: *
cache-control: no-cache
location: https://stream.mux.com/jqi1UtiO3gccQ019UcYjGJTLO9Ee00TLMY.m3u8?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkFsVFZncktBVTYzVldIdVplcDEwMVhZUk5mbHozeDIxRiJ9.eyJleHAiOjE1NzE3NjE0NzMsImF1ZCI6InYiLCJzdWIiOiJqcWkxVXRpTzNnY2NRMDE5VWNZakdKVExPOUVlMDBUTE1ZIn0.i7oANZ6inmwmGVQjon4WEv_gKcqQ2v8GuQA8xuCBdT0Reegkm6WyTdU-VloZvAt7duaRR3-T8dt147vUQjM1n70CLi0996pwMejYWIbRHUMqrDBtsENHG8T9jtz-EJcBGONSzgs7fBQIVQx8xJvPuX4YqpylDK_lNX0-RDqfhz5THAfuyxzePJod709msD8kbHAqnIke5lHzbQNHuO2ecNFVCb2ZozW7XkIEctyLxrDAK1ITtQV8iHek3whwO9S05kM-5bQzomJEliN3mXBqCwMBmyIp8l88YKl59tVXDdU-l-cZvZjt1GYKv0J7shO-oBYcr00NmVKkP7bie_w50w
date: Tue, 15 Oct 2019 16:24:33 GMT
age: 0
server: Netlify
x-nf-request-id: 80484951-e7ff-46f3-b78e-1349b8514bec-1426623
Now, in your player, you can use your netlify function as the URL src. Here's an example for a web player (note that in order to get HLS in a <video>
tag to work outside of Safari you will need to use another library like [Video.js](https://videojs.com" target="_blank" or HLS.js:
<video src="https://<your-netlify-project>.netlify.com/.netlify/functions/sign_playback_id?playbackId=<playback-id>"></video>
And here's an example on iOS with AVPlayer in Swift:
let url = URL(string: "https://<your-netlify-project>.netlify.com/.netlify/functions/sign_playback_id?playbackId=<playback-id>")
player = AVPlayer(url: url!)
player!.play()
The player will load the netlify URL, get the 302 redirect to the signed Mux URL and load the HLS manifest from stream.mux.com.
Restrict who can access your function
Now that your cloud function is working, you can add some security around it to make sure you only allow authorized users to generate signed urls.
For web players you will want to change this line in the sign_playback_id.js
function:
'Access-Control-Allow-Origin': '*',
You can use the Access-Control-Allow-Origin header to control the CORS rules for the resource.
Top comments (0)