diff js/ff-herdict-preso.js @ 7:7b67d3e40479

Refactored content and DOM structure, refactored code into a syncVisualsWithAudio() function.
author Atul Varma <varmaa@toolness.com>
date Wed, 10 Feb 2010 12:07:27 -0800
parents 37a4eb5f9695
children c9bb7e3e1821
line wrap: on
line diff
--- a/js/ff-herdict-preso.js	Wed Feb 10 10:49:43 2010 -0800
+++ b/js/ff-herdict-preso.js	Wed Feb 10 12:07:27 2010 -0800
@@ -34,61 +34,82 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
+// This function synchronizes any visual HTML content with the current
+// position of audio content. It's a generic function that can be used
+// to provide e.g. the visuals for a narrated slide-show, or
+// subtitles for a movie.
+//
+// The options object should contain the following keys:
+//
+//   audio        - CSS selector that points to a single HTML5
+//                  <audio> element. The visuals will be synchronized
+//                  with this. Required.
+// 
+//   visuals      - CSS selector that points to a collection
+//                  of DOM elements, each of which is a visual
+//                  to be displayed at some point in the audio. Required.
+// 
+//   visibleClass - Class to add to a visual element when it is
+//                  being displayed. Defaults to "visible".
+// 
+//   syncAttr     - Attribute name on each visual element that
+//                  identifies the time into the audio, in seconds, at
+//                  which the visual element it's attached to should
+//                  be displayed. Every visual element must have
+//                  this attribute, or else an exception will be
+//                  thrown when this function is called. Defaults to
+//                  "data-at".
+function syncVisualsWithAudio(options) {
+  var syncInfo = [];
+  var audio = $(options.audio).get(0);
+  var visuals = $(options.visuals);
+  var visibleClass = options.visibleClass || "visible";
+  var syncAttr = options.syncAttr || "data-at";
+
+  visuals.each(
+    function() {
+      var rawTimestamp = $(this).attr(syncAttr);
+      var timestamp = parseFloat(rawTimestamp);
+      if (isNaN(timestamp))
+        throw new Error("bad '" + syncAttr + "' attribute: " +
+                        rawTimestamp);
+      syncInfo.push({timestamp: timestamp, visual: this});
+    });
+
+  // Return the DOM element that should be displayed as the active
+  // visual the given number of seconds into the audio.
+  function findVisualForTime(timestamp) {
+    var bestVisual;
+
+    for (var i = 0; i < syncInfo.length; i++) {
+      var info = syncInfo[i];
+      if (info.timestamp <= timestamp)
+        bestVisual = info.visual;
+    }
+
+    return bestVisual;
+  }
+
+  // Potentially change the current visual depending on
+  // how far we are into the movie.
+  function maybeChangeVisual() {
+    var visual = findVisualForTime(audio.currentTime);
+    if (visual && !$(visual).hasClass(visibleClass)) {
+      visuals.removeClass(visibleClass);
+      $(visual).addClass(visibleClass);
+    }
+  }
+
+  audio.addEventListener("timeupdate", maybeChangeVisual, false);
+  maybeChangeVisual();
+}
+
 $(window).ready(
   function() {
-    // Ordered list of slides in the presentation. Each element
-    // is a JS object containing the following keys:
-    //
-    //   timestamp - number of seconds into the presentation
-    //               at which the slide should be shown.
-    //
-    //   element   - the DOM element containing the slide's
-    //               content; has the HTML class 'slide'.
-    var slides = [];
-
-    // The main HTML5 audio element that stores our presentation's
-    // soundtrack.
-    var audio = $("audio#main").get(0);
-
-    // Build the 'slides' list by analyzing all slides in the DOM
-    // and parsing out their metadata, throwing an exception
-    // if we encounter any errors.
-    $("#slides .slide").each(
-      function() {
-        var rawTimestamp = $(this).attr("data-at");
-        var timestamp = parseFloat(rawTimestamp);
-        if (isNaN(timestamp))
-          throw new Error("bad 'data-at' attribute: " + rawTimestamp);
-        slides.push({timestamp: timestamp, element: this});
-      });
-
-    // Return the DOM element that should be displayed as the active
-    // slide the given number of seconds into the movie.
-    function findSlideElementForTime(timestamp) {
-      var bestElement;
-
-      for (var i = 0; i < slides.length; i++) {
-        var slide = slides[i];
-        if (slide.timestamp < timestamp)
-          bestElement = slide.element;
-      }
-
-      return bestElement;
-    }
-
-    // Potentially change the current slide depending on
-    // how far we are into the movie.
-    function maybeChangeSlide() {
-      var element = findSlideElementForTime(audio.currentTime);
-      if (element && !$(element).hasClass("visible")) {
-        $("#slides .visible").removeClass("visible");
-        $(element).addClass("visible");
-      }
-    }
-
     // Whenever the current time in the audio soundtrack
-    // changes, alter the current visual if necessary. This
+    // changes, alter the current slide if necessary. This
     // effectively makes the audio UI work just like a
     // movie UI.
-    audio.addEventListener("timeupdate", maybeChangeSlide, false);
+    syncVisualsWithAudio({audio: "audio#main",
+                          visuals: "#slides > div"});
   });