diff --git a/src/animation-constructor.js b/src/animation-constructor.js
index 7afe249..1f4ee89 100644
--- a/src/animation-constructor.js
+++ b/src/animation-constructor.js
@@ -40,16 +40,18 @@
};
var pendingGroups = [];
- function addPendingGroup(group) {
+ scope.awaitStartTime = function(groupPlayer) {
+ if (!isNaN(groupPlayer.startTime) || !groupPlayer._isGroup)
+ return;
if (pendingGroups.length == 0) {
requestAnimationFrame(updatePendingGroups);
}
- pendingGroups.push(group);
- }
+ pendingGroups.push(groupPlayer);
+ };
function updatePendingGroups() {
var updated = false;
while (pendingGroups.length) {
- pendingGroups.shift()();
+ pendingGroups.shift()._updateChildren();
updated = true;
}
return updated;
@@ -66,18 +68,44 @@
},
});
+ // TODO: Call into this less frequently.
+ scope.Player.prototype._updateChildren = function() {
+ if (isNaN(this.startTime) || !this.source || !this._isGroup)
+ return;
+ var offset = 0;
+ for (var i = 0; i < this.source.children.length; i++) {
+ var child = this.source.children[i];
+ var childPlayer;
+
+ if (i >= this._childPlayers.length) {
+ childPlayer = window.document.timeline.play(child);
+ child.player = this.source.player;
+ this._childPlayers.push(childPlayer);
+ } else {
+ childPlayer = this._childPlayers[i];
+ }
+
+ if (childPlayer.startTime != this.startTime + offset) {
+ childPlayer.startTime = this.startTime + offset;
+ childPlayer._updateChildren();
+ }
+
+ if (this.playbackRate == -1 && this.currentTime < offset && childPlayer.currentTime !== -1) {
+ childPlayer.currentTime = -1;
+ }
+
+ if (this.source instanceof window.AnimationSequence)
+ offset += child.activeDuration;
+ }
+ };
+
window.document.timeline.play = function(source) {
+ // TODO: Handle effect callback.
if (source instanceof window.Animation) {
+ // TODO: Handle null target.
var player = source.target.animate(source._effect, source.timing);
- // TODO: make source setter call cancel.
player.source = source;
source.player = player;
- source._nativePlayer = player;
- var cancel = player.cancel;
- player.cancel = function() {
- player.source = null;
- cancel.call(this);
- };
return player;
}
// FIXME: Move this code out of this module
@@ -89,122 +117,20 @@
player._removePlayers();
return;
}
- if (isNaN(player._startTime))
+ if (isNaN(player.startTime))
return;
- updateChildPlayers(player);
+ player._updateChildren();
};
- function updateChildPlayers(updatingPlayer) {
- var offset = 0;
-
- // TODO: Call into this less frequently.
-
- for (var i = 0; i < updatingPlayer.source.children.length; i++) {
- var child = updatingPlayer.source.children[i];
-
- if (i >= updatingPlayer._childPlayers.length) {
- var newPlayer = window.document.timeline.play(child);
- newPlayer.startTime = updatingPlayer.startTime + offset;
- child.player = updatingPlayer.source.player;
- updatingPlayer._childPlayers.push(newPlayer);
- if (!(child instanceof window.Animation))
- updateChildPlayers(newPlayer);
- }
-
- var childPlayer = updatingPlayer._childPlayers[i];
- if (updatingPlayer.playbackRate == -1 && updatingPlayer.currentTime < offset && childPlayer.currentTime !== -1) {
- childPlayer.currentTime = -1;
- }
-
- if (updatingPlayer.source instanceof window.AnimationSequence)
- offset += child.activeDuration;
- }
- };
-
- addPendingGroup(function() {
- if (player.source)
- updateChildPlayers(player);
- });
// TODO: Use a single static element rather than one per group.
var player = document.createElement('div').animate(ticker, source.timing);
- player._childPlayers = [];
player.source = source;
- source._nativePlayer = player;
+ player._isGroup = true;
source.player = player;
-
- player._removePlayers = function() {
- while (this._childPlayers.length)
- this._childPlayers.pop().cancel();
- };
-
+ scope.awaitStartTime(player);
return player;
}
};
-
- function isGroupPlayer(player) {
- return !!player._childPlayers;
- }
-
- var playerProto = Object.getPrototypeOf(document.documentElement.animate([]));
- scope.hookMethod(playerProto, 'reverse', function() {
- if (isGroupPlayer(this)) {
- var offset = 0;
- this._childPlayers.forEach(function(child) {
- child.reverse();
- child.startTime = this.startTime + offset * this.playbackRate;
- child.currentTime = this.currentTime + offset * this.playbackRate;
- if (this.source instanceof window.AnimationSequence)
- offset += child.source.activeDuration;
- }.bind(this));
- }
- });
-
- scope.hookMethod(playerProto, 'pause', function() {
- if (isGroupPlayer(this)) {
- this._childPlayers.forEach(function(child) {
- child.pause();
- });
- }
- });
-
- scope.hookMethod(playerProto, 'play', function() {
- if (isGroupPlayer(this)) {
- this._childPlayers.forEach(function(child) {
- var time = child.currentTime;
- child.play();
- child.currentTime = time;
- });
- }
- });
-
- scope.hookMethod(playerProto, 'cancel', function() {
- if (isGroupPlayer(this)) {
- this.source = null;
- this._removePlayers();
- }
- });
-
- scope.hookSetter(playerProto, 'currentTime', function(v) {
- if (isGroupPlayer(this)) {
- var offset = 0;
- this._childPlayers.forEach(function(child) {
- child.currentTime = v - offset;
- if (this.source instanceof window.AnimationSequence)
- offset += child.source.activeDuration;
- }.bind(this));
- }
- });
-
- scope.hookSetter(playerProto, 'startTime', function(v) {
- if (isGroupPlayer(this)) {
- var offset = 0;
- this._childPlayers.forEach(function(child) {
- child.startTime = v + offset;
- if (this.source instanceof window.AnimationSequence)
- offset += child.source.activeDuration;
- }.bind(this));
- }
- });
}(webAnimationsShared, webAnimationsMaxifill, webAnimationsTesting));
diff --git a/src/effect-callback.js b/src/effect-callback.js
index 0fcfa56..8261f60 100644
--- a/src/effect-callback.js
+++ b/src/effect-callback.js
@@ -18,11 +18,11 @@
Element.prototype.animate = function(effect, timing) {
if (typeof effect == 'function') {
- var player = originalAnimate.call(element, [], timing);
+ var player = new scope.Player(originalAnimate.call(element, [], timing));
bind(player, this, effect, timing);
return player;
}
- return originalAnimate.call(this, effect, timing);
+ return new scope.Player(originalAnimate.call(this, effect, timing));
};
var sequenceNumber = 0;
@@ -88,20 +88,9 @@
}
}
- var playerProto = Object.getPrototypeOf(document.documentElement.animate([]));
- function registerHook() {
+ scope.Player.prototype._register = function() {
if (this._callback)
register(this._callback);
};
- scope.hookMethod(playerProto, 'play', registerHook);
- scope.hookMethod(playerProto, 'reverse', registerHook);
- scope.hookMethod(playerProto, 'cancel', function() {
- if (this._callback) {
- register(this._callback);
- this._callback._player = null;
- }
- });
- scope.hookSetter(playerProto, 'currentTime', registerHook);
- scope.hookSetter(playerProto, 'startTime', registerHook);
})(webAnimationsShared, webAnimationsMaxifill, webAnimationsTesting);
diff --git a/src/hooks.js b/src/hooks.js
deleted file mode 100644
index 133b9c4..0000000
--- a/src/hooks.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2014 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function(scope) {
- scope.hookSetter = function(proto, property, setter) {
- var original = Object.getOwnPropertyDescriptor(proto, property);
- Object.defineProperty(proto, property, {
- enumerable: true,
- configurable: true,
- get: original.get,
- set: function(v) {
- original.set.call(this, v);
- setter.call(this, v);
- }
- });
- };
-
- scope.hookMethod = function(proto, property, f) {
- var original = proto[property];
- proto[property] = function() {
- var result = original.call(this);
- f.call(this);
- return result;
- };
- };
-})(webAnimationsMaxifill);
diff --git a/src/maxifill-player.js b/src/maxifill-player.js
new file mode 100644
index 0000000..8fe6f6b
--- /dev/null
+++ b/src/maxifill-player.js
@@ -0,0 +1,139 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function(shared, scope, testing) {
+ scope.Player = function(player) {
+ this.source = null;
+ this._isGroup = false;
+ this._player = player;
+ this._childPlayers = [];
+ this._callback = null;
+ };
+
+ // TODO: add a source getter/setter
+ scope.Player.prototype = {
+ get paused() {
+ return this._player.paused;
+ },
+ get onfinish() {
+ return this._onfinish;
+ },
+ set onfinish(v) {
+ if (typeof v == 'function') {
+ this._onfinish = v;
+ this._player.onfinish = (function(e) {
+ e.target = this;
+ v.call(this, e);
+ }).bind(this);
+ } else {
+ this._player.onfinish = v;
+ this.onfinish = this._player.onfinish;
+ }
+ },
+ get currentTime() {
+ return this._player.currentTime;
+ },
+ set currentTime(v) {
+ this._player.currentTime = v;
+ this._register();
+ this._forEachChild(function(child, offset) {
+ child.currentTime = v - offset;
+ });
+ },
+ get startTime() {
+ return this._player.startTime;
+ },
+ set startTime(v) {
+ this._player.startTime = v;
+ this._register();
+ this._forEachChild(function(child, offset) {
+ child.startTime = v + offset;
+ });
+ },
+ get playbackRate() {
+ return this._player.playbackRate;
+ },
+ get finished() {
+ return this._player.finished;
+ },
+ play: function() {
+ this._player.play();
+ this._register();
+ scope.awaitStartTime(this);
+ this._forEachChild(function(child) {
+ var time = child.currentTime;
+ child.play();
+ child.currentTime = time;
+ });
+ },
+ pause: function() {
+ this._player.pause();
+ this._register();
+ this._forEachChild(function(child) {
+ child.pause();
+ });
+ },
+ finish: function() {
+ this._player.finish();
+ this._register();
+ // TODO: child players??
+ },
+ cancel: function() {
+ this._player.cancel();
+ if (this._callback) {
+ this._register();
+ this._callback._player = null;
+ }
+ this.source = null;
+ this._removePlayers();
+ },
+ reverse: function() {
+ this._player.reverse();
+ scope.awaitStartTime(this);
+ this._register();
+ this._forEachChild(function(child, offset) {
+ child.reverse();
+ child.startTime = this.startTime + offset * this.playbackRate;
+ child.currentTime = this.currentTime + offset * this.playbackRate;
+ });
+ },
+ addEventListener: function(type, handler) {
+ var wrapped = handler;
+ if (typeof handler == 'function') {
+ wrapped = (function(e) {
+ e.target = this;
+ handler.call(this, e);
+ }).bind(this);
+ handler._wrapper = wrapped;
+ }
+ this._player.addEventListener(type, wrapped);
+ },
+ removeEventListener: function(type, handler) {
+ this._player.removeEventListener(type, (handler && handler._wrapper) || handler);
+ },
+ _removePlayers: function() {
+ while (this._childPlayers.length)
+ this._childPlayers.pop().cancel();
+ },
+ _forEachChild: function(f) {
+ var offset = 0;
+ this._childPlayers.forEach(function(child) {
+ f.call(this, child, offset);
+ if (this.source instanceof window.AnimationSequence)
+ offset += child.source.activeDuration;
+ }.bind(this));
+ },
+ };
+
+})(webAnimationsShared, webAnimationsMaxifill, webAnimationsTesting);
diff --git a/target-config.js b/target-config.js
index a02ae1e..4e6ab50 100644
--- a/target-config.js
+++ b/target-config.js
@@ -19,7 +19,7 @@
'src/transform-handler.js');
var maxifillSrc = minifillSrc.concat(
- 'src/hooks.js',
+ 'src/maxifill-player.js',
'src/animation-constructor.js',
'src/effect-callback.js',
'src/group-constructors.js');
diff --git a/test/js/group-constructors.js b/test/js/group-constructors.js
index 26bdbe8..bab7748 100644
--- a/test/js/group-constructors.js
+++ b/test/js/group-constructors.js
@@ -22,11 +22,5 @@ suite('group-constructors', function() {
tick(2100);
assert.equal(p._childPlayers[1]._childPlayers[0].source.player, p);
assert.equal(p._childPlayers[1]._childPlayers[1].source.player, p);
-
- assert.equal(p.source._nativePlayer.source, p.source);
- assert.equal(p._childPlayers[0].source._nativePlayer.source, p._childPlayers[0].source);
- assert.equal(p._childPlayers[1].source._nativePlayer.source, p._childPlayers[1].source);
- assert.equal(p._childPlayers[1]._childPlayers[0].source._nativePlayer.source, p._childPlayers[1]._childPlayers[0].source);
- assert.equal(p._childPlayers[1]._childPlayers[1].source._nativePlayer.source, p._childPlayers[1]._childPlayers[1].source);
});
});
diff --git a/test/js/group-player-finish-event.js b/test/js/group-player-finish-event.js
index b131d7a..d3857a3 100644
--- a/test/js/group-player-finish-event.js
+++ b/test/js/group-player-finish-event.js
@@ -10,7 +10,7 @@ suite('group-player-finish-event', function() {
new Animation(this.element, [], 500),
]),
]);
- this.player = this.element.animate([], 1000);
+ this.player = document.timeline.play(animation, 1000);
});
teardown(function() {
if (this.element.parent)
diff --git a/test/js/group-player.js b/test/js/group-player.js
index 0e12b9a..4182278 100644
--- a/test/js/group-player.js
+++ b/test/js/group-player.js
@@ -167,9 +167,9 @@ suite('group-player', function() {
}
if (typeof timingList[0] == 'number') {
if (isNaN(timingList[0]))
- assert.ok(isNaN(player._startTime), trace + 'expected NaN startTime');
+ assert.ok(isNaN(player.startTime), trace + 'expected NaN startTime');
else
- assert.equal(player._startTime, timingList[0], trace + ' startTime');
+ assert.equal(player.startTime, timingList[0], trace + ' startTime');
assert.equal(player.currentTime, timingList[1], trace + ' currentTime');
} else {
_checkTimes(player._childPlayers[index], timingList[0], 0, trace + ' ' + index);
diff --git a/test/js/player.js b/test/js/player.js
index 60b0ffb..575510a 100644
--- a/test/js/player.js
+++ b/test/js/player.js
@@ -88,7 +88,7 @@ suite('player', function() {
tick(100);
tick(1200);
assert.equal(p.finished, true);
- assert.equal(p._startTime, 100);
+ assert.equal(p.startTime, 100);
assert.equal(p.currentTime, 1000);
tick(1500);
assert.equal(p.currentTime, 1000);
@@ -97,7 +97,7 @@ suite('player', function() {
assert.ok(isNaN(p._startTime));
assert.equal(p.currentTime, 1000);
tick(1600);
- assert.equal(p._startTime, 2600);
+ assert.equal(p.startTime, 2600);
assert.equal(p.currentTime, 1000);
});
test('playing after finishing works as expected', function() {
@@ -106,16 +106,16 @@ suite('player', function() {
tick(100);
tick(1200);
assert.equal(p.finished, true);
- assert.equal(p._startTime, 100);
+ assert.equal(p.startTime, 100);
assert.equal(p.currentTime, 1000);
tick(1500);
assert.equal(p.currentTime, 1000);
assert.equal(isTicking(), false);
p.play();
- assert.ok(isNaN(p._startTime));
+ assert.ok(isNaN(p.startTime));
assert.equal(p.currentTime, 0);
tick(1600);
- assert.equal(p._startTime, 1600);
+ assert.equal(p.startTime, 1600);
assert.equal(p.currentTime, 0);
});
test('limiting works as expected', function() {
@@ -259,11 +259,11 @@ suite('player', function() {
tick(90);
var nofill = document.body.animate([], 100);
var fill = document.body.animate([], {duration: 100, fill: 'forwards'});
- assert.deepEqual(document.timeline.players, [nofill, fill]);
+ assert.deepEqual(document.timeline.players, [nofill._player || nofill, fill._player || fill]);
tick(100);
- assert.deepEqual(document.timeline.players, [nofill, fill]);
+ assert.deepEqual(document.timeline.players, [nofill._player || nofill, fill._player || fill]);
tick(400);
- assert.deepEqual(document.timeline.players, [fill]);
+ assert.deepEqual(document.timeline.players, [fill._player || fill]);
});
test('discarded players get re-added on modification', function() {
tick(90);
@@ -272,14 +272,14 @@ suite('player', function() {
tick(400);
assert.deepEqual(document.timeline.players, []);
player.currentTime = 0;
- assert.deepEqual(document.timeline.players, [player]);
+ assert.deepEqual(document.timeline.players, [player._player || player]);
});
test('players in the before phase are not discarded', function() {
tick(100);
var player = document.body.animate([], 100);
player.currentTime = -50;
tick(110);
- assert.deepEqual(document.timeline.players, [player]);
+ assert.deepEqual(document.timeline.players, [player._player || player]);
});
test('players that go out of effect should not clear the effect of players that are in effect', function() {
var target = document.createElement('div');
diff --git a/web-animations.html b/web-animations.html
index 5d3d3a4..f4083cc 100644
--- a/web-animations.html
+++ b/web-animations.html
@@ -28,7 +28,7 @@
-
+