comparison 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
comparison
equal deleted inserted replaced
6:37a4eb5f9695 7:7b67d3e40479
32 * the provisions above, a recipient may use your version of this file under 32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL. 33 * the terms of any one of the MPL, the GPL or the LGPL.
34 * 34 *
35 * ***** END LICENSE BLOCK ***** */ 35 * ***** END LICENSE BLOCK ***** */
36 36
37 // This function synchronizes any visual HTML content with the current
38 // position of audio content. It's a generic function that can be used
39 // to provide e.g. the visuals for a narrated slide-show, or
40 // subtitles for a movie.
41 //
42 // The options object should contain the following keys:
43 //
44 // audio - CSS selector that points to a single HTML5
45 // <audio> element. The visuals will be synchronized
46 // with this. Required.
47 //
48 // visuals - CSS selector that points to a collection
49 // of DOM elements, each of which is a visual
50 // to be displayed at some point in the audio. Required.
51 //
52 // visibleClass - Class to add to a visual element when it is
53 // being displayed. Defaults to "visible".
54 //
55 // syncAttr - Attribute name on each visual element that
56 // identifies the time into the audio, in seconds, at
57 // which the visual element it's attached to should
58 // be displayed. Every visual element must have
59 // this attribute, or else an exception will be
60 // thrown when this function is called. Defaults to
61 // "data-at".
62 function syncVisualsWithAudio(options) {
63 var syncInfo = [];
64 var audio = $(options.audio).get(0);
65 var visuals = $(options.visuals);
66 var visibleClass = options.visibleClass || "visible";
67 var syncAttr = options.syncAttr || "data-at";
68
69 visuals.each(
70 function() {
71 var rawTimestamp = $(this).attr(syncAttr);
72 var timestamp = parseFloat(rawTimestamp);
73 if (isNaN(timestamp))
74 throw new Error("bad '" + syncAttr + "' attribute: " +
75 rawTimestamp);
76 syncInfo.push({timestamp: timestamp, visual: this});
77 });
78
79 // Return the DOM element that should be displayed as the active
80 // visual the given number of seconds into the audio.
81 function findVisualForTime(timestamp) {
82 var bestVisual;
83
84 for (var i = 0; i < syncInfo.length; i++) {
85 var info = syncInfo[i];
86 if (info.timestamp <= timestamp)
87 bestVisual = info.visual;
88 }
89
90 return bestVisual;
91 }
92
93 // Potentially change the current visual depending on
94 // how far we are into the movie.
95 function maybeChangeVisual() {
96 var visual = findVisualForTime(audio.currentTime);
97 if (visual && !$(visual).hasClass(visibleClass)) {
98 visuals.removeClass(visibleClass);
99 $(visual).addClass(visibleClass);
100 }
101 }
102
103 audio.addEventListener("timeupdate", maybeChangeVisual, false);
104 maybeChangeVisual();
105 }
106
37 $(window).ready( 107 $(window).ready(
38 function() { 108 function() {
39 // Ordered list of slides in the presentation. Each element
40 // is a JS object containing the following keys:
41 //
42 // timestamp - number of seconds into the presentation
43 // at which the slide should be shown.
44 //
45 // element - the DOM element containing the slide's
46 // content; has the HTML class 'slide'.
47 var slides = [];
48
49 // The main HTML5 audio element that stores our presentation's
50 // soundtrack.
51 var audio = $("audio#main").get(0);
52
53 // Build the 'slides' list by analyzing all slides in the DOM
54 // and parsing out their metadata, throwing an exception
55 // if we encounter any errors.
56 $("#slides .slide").each(
57 function() {
58 var rawTimestamp = $(this).attr("data-at");
59 var timestamp = parseFloat(rawTimestamp);
60 if (isNaN(timestamp))
61 throw new Error("bad 'data-at' attribute: " + rawTimestamp);
62 slides.push({timestamp: timestamp, element: this});
63 });
64
65 // Return the DOM element that should be displayed as the active
66 // slide the given number of seconds into the movie.
67 function findSlideElementForTime(timestamp) {
68 var bestElement;
69
70 for (var i = 0; i < slides.length; i++) {
71 var slide = slides[i];
72 if (slide.timestamp < timestamp)
73 bestElement = slide.element;
74 }
75
76 return bestElement;
77 }
78
79 // Potentially change the current slide depending on
80 // how far we are into the movie.
81 function maybeChangeSlide() {
82 var element = findSlideElementForTime(audio.currentTime);
83 if (element && !$(element).hasClass("visible")) {
84 $("#slides .visible").removeClass("visible");
85 $(element).addClass("visible");
86 }
87 }
88
89 // Whenever the current time in the audio soundtrack 109 // Whenever the current time in the audio soundtrack
90 // changes, alter the current visual if necessary. This 110 // changes, alter the current slide if necessary. This
91 // effectively makes the audio UI work just like a 111 // effectively makes the audio UI work just like a
92 // movie UI. 112 // movie UI.
93 audio.addEventListener("timeupdate", maybeChangeSlide, false); 113 syncVisualsWithAudio({audio: "audio#main",
114 visuals: "#slides > div"});
94 }); 115 });