var Timeline = { currentTimeline: null, currentDirection: 1, currentParams: null, currentProgress: null, currentInterval: 0, currentBeginTime: 0, play: function(aTimeline, aDirection, aParams) { var timeOffset = 0; if (this.currentTimeline) { //if (aTimeline == this.currentTimeline && aDirection != this.currentDirection) //{ // timeOffset = (new Date()).getTime() - this.currentBeginTime; //} this.stop(); } this.currentProgress = []; for (var i = 0; i < aTimeline.length; ++i) { this.currentProgress[i] = { playing: false, frame: 0, progress: 0, tweenProgress: 0 }; } this.currentTimeline = aTimeline; this.currentDirection = aDirection this.currentParams = aParams; this.currentBeginTime = (new Date()).getTime() - timeOffset; this.currentInterval = window.setInterval(this.intervalCallback, 10); }, stop: function() { // Stop the timer and reset all tracking data window.clearInterval(this.currentInterval); this.currentTimeline = null; this.currentParams = null; this.currentProgress = null; this.currentInterval = 0; this.currentBeginTime = 0; }, getIsPlaying: function() { return this.currentTimeline != null; }, tick: function() { // Determine how many milliseconds have elapsed since play begain var t = (new Date()).getTime() - this.currentBeginTime; // Use activeCount to count how many keyframes have yet to finish var activeCount = 0; // XXX This currently won't fire any events for keyframes that // are completely skipped due to animation lag. Perhaps we should // at least fire the first/last frame events for these? for (var i = 0; i < this.currentTimeline.length; ++i) { var keyframe = this.currentTimeline[i]; var progressData = this.currentProgress[i]; // If we have not reached the end of this keyframe... if (t < keyframe.begin+keyframe.duration) { ++activeCount; // If we are past the beginning of this keyframe if (t >= keyframe.begin) { ++progressData.frame; if (progressData.frame == 1) { progressData.playing = true; // This is the first tick for this keyframe this.dispatchFrameEvent(keyframe, progressData, "onfirstframe"); this.dispatchFrameEvent(keyframe, progressData, "onframe"); } else { // Determine how many milliseconds have elapsed in the keyframe duration var kt = t - keyframe.begin; progressData.progress = kt/keyframe.duration; // Determine the percent done, using a tweening function if provided if ("tween" in keyframe) { progressData.tweenProgress = keyframe.tween(kt, 0, kt, keyframe.duration, 0, 100)/kt; } else { progressData.tweenProgress = progressData.progress; } if (this.currentDirection != 1) { progressData.progress = 1 - progressData.progress; progressData.tweenProgress = 1 - progressData.tweenProgress; } this.dispatchFrameEvent(keyframe, progressData, "onframe"); } } } // If this keyframe is currently playing, but past the end else if (progressData.playing) { ++progressData.frame; progressData.progress = this.currentDirection == 1 ? 1 : 0; progressData.tweenProgress = this.currentDirection == 1 ? 1 : 0; this.dispatchFrameEvent(keyframe, progressData, "onframe"); this.dispatchFrameEvent(keyframe, progressData, "onlastframe"); progressData.playing = false; } // If we passed the keyframe without ever playing it else if (!progressData.playing && !progressData.frame) { ++progressData.frame; progressData.progress = this.currentDirection == 1 ? 1 : 0; progressData.tweenProgress = this.currentDirection == 1 ? 1 : 0; progressData.playing = true; this.dispatchFrameEvent(keyframe, progressData, "onfirstframe"); this.dispatchFrameEvent(keyframe, progressData, "onframe"); this.dispatchFrameEvent(keyframe, progressData, "onlastframe"); progressData.playing = false; } } // If there are no more keyframes left to play if (activeCount == 0) { Timeline.stop(); } }, dispatchFrameEvent: function(aKeyframe, aData, aName) { if (aName in aKeyframe) { try { aKeyframe[aName](aKeyframe, aData, this.currentParams); } catch (ex) { this.dumpError(ex); } } }, intervalCallback: function() { Timeline.tick(); }, testFrame: function(aKeyFrame, aProgress, aParams) { throw "UNO " + aProgress.frame + " (" + Math.round(aProgress.tweenProgress*100) + "%)"; }, dumpError: function(aMessage) { window.setTimeout("throw '\""+aMessage+"\"'", 0); } };