Mercurial > ff-herdict-preso
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 }); |