-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathtimer.js
159 lines (135 loc) · 6.27 KB
/
timer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*! timer.js v3.0.0
* https://github.com/adamcik/media-progress-timer
* Copyright (c) 2015-2016 Thomas Adamcik
* Licensed under the Apache License, Version 2.0 */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.ProgressTimer = factory();
}
}(this, function () {
'use strict';
// Helper function to provide a reference time in milliseconds.
var now = typeof window.performance !== 'undefined' &&
typeof window.performance.now !== 'undefined' &&
window.performance.now.bind(window.performance) || Date.now ||
function() { return new Date().getTime(); };
// Helper to warn library users about deprecated features etc.
function warn(msg) {
window.setTimeout(function() { throw msg; }, 0);
}
// Creates a new timer object, works with both 'new ProgressTimer(options)'
// and just 'ProgressTimer(options). Optionally the timer can also be
// called with only the callback instead of options.
function ProgressTimer(options) {
if (!(this instanceof ProgressTimer)) {
return new ProgressTimer(options);
} else if (typeof options === 'function') {
options = {'callback': options};
} else if (typeof options !== 'object') {
throw '"ProgressTimer" must be called with a callback or options.';
} else if (typeof options['callback'] !== 'function') {
throw '"ProgressTimer" needs a callback to operate.';
}
this._userCallback = options['callback'];
this._updateId = null;
this._state = null; // Gets initialized by the set() call.
var frameDuration = 1000 / (options['fallbackTargetFrameRate'] || 30);
// TODO: Remove this legacy code path at some point.
if (options['updateRate'] && !options['fallbackTargetFrameRate']) {
warn('"ProgressTimer" no longer supports the updateRate option.');
frameDuration = Math.max(options['updateRate'], 1000 / 60);
}
var useFallback = (
typeof window.requestAnimationFrame === 'undefined' ||
typeof window.cancelAnimationFrame === 'undefined' ||
options['disableRequestAnimationFrame'] || false);
// Make sure this works in _update.
var update = this._update.bind(this);
if (useFallback) {
this._schedule = function(timestamp) {
var timeout = Math.max(timestamp + frameDuration - now(), 0);
return window.setTimeout(update, Math.floor(timeout));
};
this._cancel = window.clearTimeout.bind(window);
} else {
this._schedule = window.requestAnimationFrame.bind(window, update);
this._cancel = window.cancelAnimationFrame.bind(window);
}
this.reset(); // Reuse reset code to ensure we start in the same state.
}
// If called with one argument the previous duration is preserved. Note
// that the position can be changed while the timer is running.
ProgressTimer.prototype.set = function(position, duration) {
if (arguments.length === 0) {
throw '"ProgressTimer.set" requires the "position" arugment.';
} else if (arguments.length === 1) {
// Fallback to previous duration, whatever that was.
duration = this._state.duration;
} else {
// Round down and make sure zero and null are treated as inf.
duration = Math.floor(Math.max(
duration === null ? Infinity : duration || Infinity, 0));
}
// Make sure '0 <= position <= duration' always holds.
position = Math.floor(Math.min(Math.max(position || 0, 0), duration));
this._state = {
initialTimestamp: null,
initialPosition: position,
position: position,
duration: duration
};
// Update right away if we don't have anything running.
if (this._updateId === null) {
// TODO: Consider wrapping this in a try/catch?
this._userCallback(position, duration);
}
return this;
};
// Start the timer if it is not already running.
ProgressTimer.prototype.start = function() {
if (this._updateId === null) {
this._updateId = this._schedule(0);
}
return this;
};
// Cancel the timer if it us currently tracking progress.
ProgressTimer.prototype.stop = function() {
if (this._updateId !== null) {
this._cancel(this._updateId);
// Ensure we correctly reset the initial position and timestamp.
this.set(this._state.position, this._state.duration);
this._updateId = null; // Last step to avoid callback in set()
}
return this;
};
// Marks the timer as stopped, sets position to zero and duration to inf.
ProgressTimer.prototype.reset = function() {
return this.stop().set(0, Infinity);
};
// Calls the user callback with the current position/duration and then
// schedules the next update run via _schedule if we haven't finished.
ProgressTimer.prototype._update = function(timestamp) {
var state = this._state; // We refer a lot to state, this is shorter.
// Make sure setTimeout has a timestamp and store first reference time.
timestamp = timestamp || now();
state.initialTimestamp = state.initialTimestamp || timestamp;
// Recalculate position according to start location and reference.
state.position = (
state.initialPosition + timestamp - state.initialTimestamp);
// Ensure callback gets an integer and that 'position <= duration'.
var userPosisition = Math.min(
Math.floor(state.position), state.duration);
// TODO: Consider wrapping this in a try/catch?
this._userCallback(userPosisition, state.duration);
if (state.position < state.duration) {
this._updateId = this._schedule(timestamp); // Schedule update.
} else {
this._updateId = null; // Unset since we didn't reschedule.
}
};
return ProgressTimer;
}));