Creating a digital kissing booth
First off, let me start with a personal confession: I love to over-complicate things. This project was no different. “Let’s use HTML5 to capture the visitor’s webcam!” and “Let’s have a large TV in the office with our faces that rotate through when it’s your turn” were definitely mentioned several times throughout this project. Did either of those happen? Nope. Am I bummed they didn’t? Nope.
With any large project, it’s important to have a wish list. Developing your wish list at the beginning can lead to great ideas, or even great questions, that help you develop your project in the best way possible. You might not get your wish, but that shouldn’t keep you from wishing. Okay, now that I’ve spouted off about wishes and fishes, let me get into the meat of this post: how we made a digital kissing booth.
This was an incredibly technical project. Definitely the most technical internal project we’ve done here at KPS3. We had three different servers, two Raspberry Pis, and a ton of bandwidth. The best part? It all went off without a hitch. Not one crash, not one error page. Everything went smoothly, and we raised nearly $4,000 for two local nonprofits. You can read more about the back story of the project and its success over here. I’m going to just bore you with the technical details.
We started with the most important step: brainstorming. The first week of the project we did nothing other than talk about it. We had a few meetings, a few walks on the roof, and quite a few IM’s about the project, but no work actually began. After a week of letting the idea marinate, we were ready to go. Rob started with a technical flow, a technical outline, and a step-by-step user story. The groundwork had been laid. No turning back now.
The “Corner Pi”
We knew that everyone couldn’t donate money for a digital kiss, but we still wanted everyone to enjoy the project. We also wanted our promotion to be as real-time as possible. So, we created a live stream. Using a Raspberry Pi, wide-angle lens, and a bunch of gaffers tape (and a few thumbtacks), we mounted a Raspberry Pi in the back corner of our creative room. This pi had it’s own ethernet cable, static IP address, and a bash script running raspivid and ffmpeg. We streamed straight to a Rackspace Media Server and delivered the feed live on the site with less than a 1s delay. Thanks to SiDigital for testing this theory out first and writing about it so we could learn from their experience.
The “Kissing Pi”
This Pi was a bit more complicated. It’s funny how something so seemingly simple can snowball so quickly. Here are the basics of what was necessary for the Kissing Pi to work:
- User comes to website & sees “Corner Pi” livestream and decides to donate to charity.
- User donates to cause (enter payment processing setup & integration)
- If donation is approved, user gets put into a Queue (enter database and websockets)
- Once it’s someone’s turn in the queue, we need our office to know (enter admin panel with some type of office notification)
- Once a KPS3er is at the booth, we need a way to initiate the kiss (enter Kissing Pi button that gets pushed to start recording)
- Once the KPS3er has finished their kiss, we need a way to end the kiss (enter the Kissing Pi button that gets pushed to end recording)
- After the video feed is captured from the button presses, we need to pull out frames from the video is recorded and create a GIF using ffmpeg and imagemagick
- After the GIF is created, it gets stored on a Rackspace CDN
- The user is then redirected to their personal GIF
- We update the website in several areas with the new GIF that was just created without users having to refresh (more websockets)
- Steps 4-10 repeat until queue is empty
That’s the “quick” overview of how it all worked on our side. Let’s go into it a bit more.
The Nitty Gritty
We also needed to integrate with a payment processor. We chose to use Stripe because of their no-nonsense API. Integrating with Stripe was one of the simplest parts of the process (and if you haven’t taken a look at their Email Receipts API, do it). Once we received a successful payment callback from Stripe, we’d move the user into the queue.
The queue is probably the most deceptively complicated part of the app. In theory and on the frontend, it was very simple. The number would just decrease by one each time a user successfully received their GIF. But, there was a lot more strategy that need to go into building the queue. What if the app crashed? How could we preserve the queue? Users had already donated. We couldn’t reassign them, or just lose their place. Okay, so a user needs to be able to rejoin at their current position. How do they rejoin? What unique identifier do we have that they could type in? How are we saving their spot in a queue so we know where they are? This line of questioning eventually led to the realization that we needed a database. The database had to store users (their email, kisses, GIF urls) and KPS3 employees. We spun up another server to keep the database separate from the web app (if the app crashed, we didn’t want the db crashing too) and used Mongodb as our database of choice. Mongodb is easy to integrate, plays well with Node, and is blazing fast. If for some reason our app did go down, we had a screen ready for users to come back, enter their email address, and rejoin the queue as if nothing happened. Luckily, we didn’t end up need it.
The queue also used websockets to update. Every time a GIF was successfully delivered to a user, our queue would decrease by one. Once it was your turn in line, more magic happened. What people sometimes forget is the KPS3 side of the app. We needed to be alerted when someone was up in the queue, so we’d know when to kiss. How were we going to do that in an office full of people? We have 6+ rooms spanning the entire office, and the kissing booth was only in one of these rooms. What if someone had to take a bathroom break or was in a meeting? All of these concerns were taken care of by our own admin panel. Each KPS3er had their own username and password and could set their status (On, Nope, BRB).
Each time someone new was first in the queue, the node app would do several things:
- Randomly select a KPS3 employee that had a status of “On”
- Find that user, play sound through their browser, and flash on their screen, letting them know it’s their “turn”
- Play a 45 second clip from one of our “Kissing Classics” on the loudspeakers incase that person missed their notifications and someone needed to sub in. (If you’re curious, here are the Kissing Classics: Beatles – All My Loving, Journey – Kiss Me Softly, Faith Hill – This Kiss, Seal – Kiss from a Rose, Sixpense – Kiss Me, Aerosmith – Walk This Way)
- Alert Jonathan, who was manning the logs, with whose turn it is (again, incase someone missed their notifications)
- Initiate the Kissing Pi Button and set it to the “Ready” state (or rather, make the button start blinking)
All of that happened within a brief 1-2 second window following someone entering the first spot in the queue.
In order to interface with the Pi, we had to get our Python on. If you’re looking to quickly get started with the Pi, there are a lot of different starting points. In our case, Kissing Pi would just be constantly waiting for a callback from the node server. Once it received one, it would go grab the first item in the Mongodb queue, select the random employee, play the music, and start blinking the button. Once one of us hit the blinking red arcade button (which interfaced to the Pi using the PiFace extension board), the Pi camera module would start recording (we used the PiCamera Python library). While recording, our button would stay solid red. Once we were done, we’d push the button again, the LED would turn off, and the h264 video would get Posted to our Node server. After the Node server received the video, it’d strip out frames with imagemagick and create the GIF using ffmpeg, serve it to the CDN, and websockets would distribute it to the user, the homepage, and anyone waiting in the queue. It would also begin the queue reduction process and repeat until queue is empty.
And that’s really it. There were a ton of moving parts, but nothing we couldn’t handle. We planned for contingencies along the way and tried to ask every question early on in the process. On the big day, we had over a thousand unique visitors on the site. Some donated, some watched the live stream all day, and some just tuned in for a few minutes and continued with their daily routine. We were able to raise nearly $4,000 and give almost 120 kisses, and not one part of our app failed at any point. We also had a killer time doing it.
Major kudos to Jonathan for all his work on this project. He handled nearly all the dev and worked tirelessly until it all worked just right.