blog

Photo of popcorn by Martin Abegglen on Flickr

Popcorn, Popup, Popup Videos, Video Annotations, Bring Me Back to Data

by

Thanks to Jimmy Fallon and the Mets bucket hat guy, I’ve found myself stringing together random word associations like this, which is where this title came from.  But honestly, the last thing I would associate with popcorn is a javascript media library.  So I guess random associations is the thing to do.  To be clear, this is not the kind of popcorning made famous by Rob Drydek (MTV’s Ridiculousness) which describes hiding an airbag under an unsuspecting person’s seat and deploying it, sending the poor victim on a quick ride skyward.  This is the kind of popcorn that allows making video annotations (ala VH1’s Popup Videos circa 1980) easy to do.   That’s popcorn.js.

Data Driven Video Annotations Using Popcorn.js

I didn’t always know about popcorn.js.  In fact, I was recently asked to look into porting an older flex/flash media server based video annotation tool to a more modern codebase.  The flex based code was showing it’s age, and it’s inability to run on iPads and iPhones was becoming a concern for the client.  They basic requirement was to allow comments, lines, and markers to be drawn on top of a video, and those notes would then be re-playable for others to view.  It’s a collaborative annotation tool, similar to those widely available for images today. In searching for alternative tools that might fit the bill, I stumbled across popcorn.js.  It’s an event driven javascript media library written by the folks at Mozilla.  At first glance, this looks like an extremely powerful tool for interacting with videos on the web in a very intriguing way.  At it’s simplest, popcorn.js fires events at specific times as a video is playing.  Using these events, one can utilize the full power of javascript to do almost anything you can think of.  In our case, we need to show and hide elements at specific positions on top of the video element.  So as a quick experiment on the feasibility of using popcorn.js as a video annotation tool, I decided to write a quick proof of concept.  Think of this as an unboxing of popcorn.js.  Yesterday was my first meeting with popcorn, so I am by no means an expert.  But I figured it would be fun to see what it can do. First, let’s lay some groundwork.  We’re going to concentrate on the re-playing of the notes on top of the video.  Nevermind how those notes got there in the first place…that’s something for another day.  But let’s assume we have an array of note objects.  Keep in mind that this data might be embedded in the page or might come as json data return from an ajax server call.  In any event, we’ll assume the following format:

data = [{
    title: "Annotation Demo",
    text: "A Data Driven Video Annotation Demo",
    start: 0,
    end: 1.5,
    position: { top: '35%', left: '20%' },
    size: { w: '60%', h: '30%' },
    class: 'bubble'
}, {
    title: "THE LOGO!",
    text: "Logo Happens Here",
    start: 4,
    end: 6,
    position: { top: '0', left: '25%' },
    size: { w: '50%', h: '20%' },
    class: 'bubble-bottom'
}, {
    title: "THE POP!",
    text: "Pop happens there",
    start: 1.9,
    end: 3.5,
    position: { top: '55%', left: '85%' },
    size: { w: '40%', h: '25%' },
    class: 'bubble'
}];
  • start – the time in seconds when this note should be displayed
  • end – the time in seconds when this note should disappear
  • position & size – these are stored in percentages, to allow notes to have the correct size and position regardless of the current scale of the source video.  We’ll assume the tool used to create the notes calculated these percentages based on the video’s current scale at the time of creation.
  • class – this isn’t necessary, but allows some styling of the popup notes, including which direction to show the text bubble arrow

The html

The html document is fairly simple.  There are 3 basic elements that need to be included:

  1. video tag to render the video.  Hopefully, this one is pretty obvious.  We are going to be using popcorn.js’s favorite little demo video, which is a quick 6 second bumper for popcorn.
  2. #notes overlay div – This div is the container for all the notes that will be displayed on the page.  It will be positioned directly on top of the video element using css that will be described next.  Just keep in mind that the overly div should exactly cover the video element.
    3. note prototype – Rather than manually creating new nodes directly in jquery to dynamically add note elements to the pages, we’ll define a note prototype element.  The .prototype class has been set to display:none, meaning it won’t show on the page initially.  As we need to create new elements, we’ll clone that prototype, fill in the dynamic content, and add it to the #notes overlay div.
<div class="content">
   <p> </p>
   <div id="wrapper">
      <video height="360" width="600" id="notes_video" controls>
         <source src="http://videos.mozilla.org/serv/webmademovies/popcornplug.mp4" />
         <source src="http://videos.mozilla.org/serv/webmademovies/popcornplug.ogv" />
         <source src="http://videos.mozilla.org/serv/webmademovies/popcornplug.webm" />
      </video>
      <div id="notes">
         <div class="prototype">
            <h4 class="title"></h4>
            <p class="text"></p>
         </div>
      </div>
   </div>
</div>

The CSS

As stated earlier, we’re going to hide the prototype class by default.  This is a useful tactic that allows multiple templates to be added to a particular page, in the case that there is more than 1 dynamically populated element.  It’s kind of a template system, when a full-fledged template library like mustache is overkill.  (BTW, popcorn.js does include a mustache plugin, which might be more effective for templating more complex elements)

body {
    text-align:center;
    font-family:Helvetica, Arial
}
.prototype {
    display:none;
}

The positioning of the overlay div is created using a mixture of relative and absolute positioning.  The wrapper element is an outer wrapper that holds the video and overlay elements, acting as the bounding container.  We will define this element as position:relative, making all child containers position relative to it. Second, we’ll define the note overlay as having an absolute position.  Giving the element a top of 0 will placed it at the top of the #wrapper element.  Using a left of 50% and a margin-left of -300px is basically a way of centering an absolute positioned element of a fixed width.  Finally, we’ll add the pointer-events:none so that the overlay div won’t handle mouse events and will let them be handled by the video element underneath it.  (Failing to do this will block interaction with the video element, meaning you can’t play/pause/seek, etc.  Note that this does not work in IE.)

#wrapper {
    position:relative;
}
#notes {
    position:absolute;
    top: 0;
    left: 50%;
    width: 600px;
    height: 360px;
    margin-left:-300px;
    pointer-events: none;
}
#notes > * {
    display:none;
    position:absolute;
    color:#dedede;
    padding:5px;
}

Finally, some simple styling of the note popups themselves.

.title {
    margin:0 auto;
    color:#efefef;
    font-weight:normal;
}
.text {
    font-size:smaller;
}

The Javascript

And finally, we’re to the good stuff. Popcorn.js needs to be initialized in the DOMDontentLoaded event, which is run after everything is loaded, including the video element. To initialize the Popcorn object, we simply call Popcorn() with the id of the video element as the only parameter. Next, we’ll create the note elements and add them to the #notes overlay div. To accomplish this, we’ll iterate over each of the items in the data array. For each item, we’ll clone the note prototype element, remove the prototype class (since it’s no longer just a prototype), and fill in the details. This include setting the appropriate size and position. Once we’re done, we’ll append the new block to the #notes div, making it a real part of the DOM. Next, we’ll setup a video event for each of the items. To do this, we’ll make use of the code() plugin from popcorn.js. This plugin allows arbitrary code to be run based on the start and end times that are passed into the listener. In this case, we just need to show() or hide() the target element (the note block we just created), as needed. I was thinking there would be a plugin for popcorn that shows or hides a particular DOM element, but I couldn’t seem to find one. It would be fairly trivial to write such a plugin, which would make for a useful addition to the library, IMHO. Finally, we’ll start the video.

document.addEventListener("DOMContentLoaded", function () {
var pop = Popcorn("#notes_video");
$(data).each(function(i, noteData) {
    var noteDiv = $('#notes .prototype').clone().removeClass('prototype');
noteDiv.find('.title').text(noteData.title).parent()
        .find('.text').text(noteData.text).parent()
        .attr("data-note-id", i)
        .css({
            'top': noteData.position.top,
            'left': noteData.position.left
        })
        .width(noteData.size.w)
        .height(noteData.size.h)
        .addClass(noteData.class)
        .appendTo($('#notes'));
    pop.code({
        start: noteData.start,
        end: noteData.end,
        onStart: function (options) {
            options.target.show();
        },
        onEnd: function (options) {
            options.target.hide();
        },
        target: noteDiv
    });
}
pop.play();
}

The Demo

Here’s a link to a demo of the complete code. http://jsfiddle.net/montanajob/tywcN/ And there you have it, a quick and easy video annotation player, thanks to popcorn.js.

Overall, I was really impressed at how quick and easy it was to do this.  Obviously, this is just a small part of a complete video annotation tool. But traditionally, the effort has been much greater to get to this point using other solutions. I feel popcorn has a big future, and could really open up possibilities when working with video on the web.

+ more