JavaScript Tutorial: Photo booth

Learn our JavaScript SDK by getting your hands dirty.

Introduction

In this tutorial we will use the Appstax backend together with some new HTML5 technologies to create a web application that takes snapshots using your web camera and saves them to an Appstax collection. You can try the finished app at photobooth.appstax.io and view the complete code in our github repository.

Photobooth

Requirements

  • Your computer needs to have a web camera attached (most laptops have this these days)
  • For the web camera and video to work, your browser needs to support user media. Latest versions of Chrome, Firefox and Opera can be used. It will unfortunately not work in Internet Explorer, Safari and some other browsers.
  • Version 1.0.4 or later of the command line client is required for this tutorial. Run appstax -v to see your installed version.

Install the command line tool

  • Quick install on Mac OSX: Use this command on the terminal:
    curl -s https://appstax.com/download/cli/install_osx.sh | sudo /bin/bash
  • Quick install on Linux:
    curl -s https://appstax.com/download/cli/install_linux.sh | sudo /bin/bash
  • Manual installation:
    Download the latest release and copy the appropriate binary to somewhere on your system PATH.

On windows, place appstax.exe in c:\Windows\System32 or any custom PATH you have configured in your environment variables.

Sign up for an appstax account

Go to appstax.com to create your account if you don't already have one.

Create an app with DataStorage and Hosting

Go to the Admin UI and create a new app called "Photobooth". For this tutorial you need to enable the DataStorage and Hosting components.

Set up the project on your computer

Open terminal and run these commands to create a new directory called "photbooth" to work in

mkdir photobooth
cd photobooth

Then set up an app in this directory by running

appstax init

Follow the instructions on the screen to log in to your account, and select the app you created before. Select the "JavaScript: Basic project" template and choose a suitable domain name to deploy your app on later. If you type a domain name that is already in use you will be prompted to try again.

The file structure on your computer should now look like this:

photobooth/appstax.conf
photobooth/public
photobooth/public/app.css
photobooth/public/app.js
photobooth/public/appstax.js
photobooth/public/index.html

Show your face on the screen

Open public/index.html in a text editor and replace its contents with the html shown below. It contains a complete html document with a div#booth element with video and button elements, a list to display the snapshots, as well as the tags neccesary to include app.css and app.js.

<!DOCTYPE html>
<html>
<head>
  <title>Photo Booth</title>
  <link rel="stylesheet" href="app.css"/>
</head>
<body>
  <div id="booth">
    <video></video>
    <div class="flash"></div>
    <button></button>
  </div>
  <div id="snapshots">
    <ul>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </div>
  <script src="appstax.js"></script>
  <script src="app.js"></script>
</body>

Now open public/app.css and paste in this code to add layout to your app:

body {
  background-image: radial-gradient(circle, rgba(255,255,255,0.00) 50%, rgba(0,0,0,0.12) 100%);
}

#booth {
  width: 540px;
  height: 405px;
  display: block;
  margin: 100px auto 30px auto;
  position: relative;
  background: #261919;
  border: 10px solid #FFFFFF;
  box-shadow: 0px 8px 15px 1px rgba(0,0,0,0.06);
  border-radius: 4px;
}

#booth video {
  background: black;
  width: 540px;
  height: 405px;
}

#booth button {
  background: #D0021B;
  background-image: linear-gradient(to bottom, #d93434, #7a1b1b);
  border: 3px solid #FFFFFF;
  box-shadow: 0px 5px 6px 0px rgba(0,0,0,0.50);
  width: 40px;
  height: 40px;
  position: absolute;
  left: 250px;
  bottom: 20px;
  border-radius: 20px;
  outline: none;
  cursor: pointer;
}

#booth button:hover {
  background-image: linear-gradient(to bottom, #FF4747, #9D2222);
}

#booth button:active {
  background-image: linear-gradient(to bottom, #AE2424, #7a1b1b);
}

#booth .flash {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  transition: background .5s;
}

#booth .flash.on {
  background: white;
  transition: background 0s linear 0s;
}

#snapshots {
  width: 580px;
  margin: 0 auto;
  overflow: scroll;
}

#snapshots ul {
  display: table;
  border-spacing: 15px;
  margin: 0 auto;
  padding: 0;
}

#snapshots li {
  display: table-cell;
  width: 120px;
  height: 90px;
  background: #EFEFEF;
  border: 4px solid #FFFFFF;
  box-shadow: 0px 8px 15px 1px rgba(0,0,0,0.06);
  border-radius: 4px;
}

#snapshots img {
  display: block;
}

Now the layout is taken care of. The next step is to connect your web cam to the video element. Open public/app.js and add this code below the call to appstax.init(...)

window.addEventListener("load", function() {
  initVideo();
});

function initVideo() {
  navigator.getUserMedia = navigator.getUserMedia || 
                           navigator.webkitGetUserMedia ||
                           navigator.mozGetUserMedia;
  if(typeof navigator.getUserMedia === "undefined") {
    alert("A web camera is required for this app, but your browser does not support it. Please download Firefox, Google Chrome or Opera and try again.")
  } else {
    navigator.getUserMedia({audio:false, video: true}, function(localMediaStream) {
    var video = document.querySelector("video");
    video.autoplay = true;
    video.src = window.URL.createObjectURL(localMediaStream);
    }, function(error) {
      alert("You need to allow access to your web camera. Reload the page to try again.");
    }); 
  }
}

Publish and run the app

Let's take a look at what we've got so far by deploying the app to Appstax Hosting. Run these two commands:

appstax deploy public
appstax open

The first command packages and uploads the contents of the public directory to the Appstax backend. The second command opens your browser at the address where your files are deployed. So if you selected "mybooth" as your hosting subdomain you should now be looking at your face on http://mybooth.appstax.io/

When trying out this in a browser you will need to allow it access to you your camera. If you don't see anything and there is no popup asking for camera access you will need to check your browsers preferences for any settings that could be restricting access to your camera from the hosting domain you have selected.

After each of the next steps you can run appstax deploy public again and reload the browser page to see the changes you've made.

Create the data model

To store the pictures we will create a collection called "snapshots" with a single file column named "image". Run this command to set it up now:

appstax collection snapshots image:file

You can also create collections and manage your data model using the Admin Interface.

Taking snapshots

To trigger the snapshot we add a button click handler that captures a video frame, adds it to an appstax object and saves it to our snapshots collection.

Add these functions to the end of public/app.js:

function initButton() {
  var button = document.querySelector("button");
  button.addEventListener("click", function() {
    flash();
    saveSnapshot();
  });
}

function flash() {
  var flash = document.querySelector("#booth .flash");
  flash.className = "flash on";
  setTimeout(function() {
    flash.className = "flash";
  }, 1)
}

function saveSnapshot() {
  var file = getVideoFrameAsFile();
  var snapshot = appstax.object("snapshots");
  snapshot.image = appstax.file(file);
  snapshot.save();
}

function getVideoFrameAsFile() {
  var name = "snapshot.png";
  var mime = "image/png";
  var video  = document.querySelector("video");
  var canvas = document.createElement("canvas");
  canvas.width  = video.clientWidth;
  canvas.height = video.clientHeight;
  var ctx = canvas.getContext("2d");
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  var dataURI = canvas.toDataURL(mime);
  var binary = atob(dataURI.split(',')[1]);
  var array = [];
  for(var i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  var file = new File([new Uint8Array(array)], name, {type: mime});
  return file;
}

You also need to add a call to initButton() to the load event listener we added earlier. This event listener should now look like this:

window.addEventListener("load", function() {
  initVideo();
  initButton();
});

Now run appstax deploy public again and reload the browser page. Look into the camera, smile, and press the red button to take a few snapshots.

Take a look at the data

At this point in the tutorial you haven't yet written any code to view the images. To confirm that the data is being stored you can use this command to view the data in your snapshots collection:

appstax find snapshots

The output will look something like this:

+--------------+--------------------------------+---------------+--------------------------------+
|    image     |          sysCreated            |  sysObjectId  |          sysUpdated            |
+--------------+--------------------------------+---------------+--------------------------------+
| snapshot.png | 2015-06-01T14:15:42.873593263Z | 730jyxID19e8  | 2015-06-01T14:15:42.873593263Z |
| snapshot.png | 2015-06-01T14:15:43.818989839Z | VeYanPu1P5bp  | 2015-06-01T14:15:43.818989839Z |
| snapshot.png | 2015-06-01T14:15:45.215373791Z | 6kpmnlsNNm76P | 2015-06-01T14:15:45.215373791Z |
+--------------+--------------------------------+---------------+--------------------------------+

You can also view the data in your collections using the Admin Interface.

Show snapshots

Add these two functions that load and render the snapshots to the end of public/app.js:

function loadSnapshots() {
  appstax.findAll("snapshots").then(renderSnapshots);
}

function renderSnapshots(snapshots) {
  var list = document.querySelector("#snapshots ul");
  list.innerHTML = "";
  snapshots.sort(function(a, b) {
    return b.created.getTime() - a.created.getTime();
  });
  snapshots.forEach(function(snapshot) {
    var url = snapshot.image.imageUrl("resize", {width:120});
    list.innerHTML += "<li><img src='" + url + "'/></li>"
  });
}

You also need to add a call to loadSnapshots() to the load event listener we added earlier. This part of the code should now look like this:

window.addEventListener("load", function() {
  loadSnapshots();
  initVideo();
  initButton();
});

We also want to refresh the snapshot list each time a new one is added. Change the saveSnapshot() function to chain a .then(loadSnapshots) to the promise returned by .save():

function saveSnapshot() {
  var file = getVideoFrameAsFile();
  var snapshot = appstax.object("snapshots");
  snapshot.image = appstax.file(file);
  snapshot.save().then(loadSnapshots);
}

Things to try on your own

The tutorial stops here, but there are loads of fun things you can do to extend this app. Here are a few suggestions:

  • Add a countdown before the picture is taken
  • Let people delete bad snapshots
  • Add image filters
  • Add user signup to enable private photos
  • Let people share photos with other users

Summary

You may be surprised by how little of the code in this tutorial is actually related to appstax.
Here is the data model setup:

appstax collection snapshots image:file

This is all that is needed to save files:

var snapshot = appstax.object("snapshots");
snapshot.image = appstax.file(file);
snapshot.save();

Getting the snapshots from the backend is even shorter:

appstax.findAll("snapshots").then(renderSnapshots);

And then there is the .imageUrl() call to get resized image url:

var url = snapshot.image.imageUrl("resize", {width:120});

Thats all! Congratulations on finishing an appstax app!