1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5//     You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//     See the License for the specific language governing permissions and
13// limitations under the License.
14
15(function(scope, testing) {
16
17  var sequenceNumber = 0;
18
19  var AnimationPlayerEvent = function(target, currentTime, timelineTime) {
20    this.target = target;
21    this.currentTime = currentTime;
22    this.timelineTime = timelineTime;
23
24    this.type = 'finish';
25    this.bubbles = false;
26    this.cancelable = false;
27    this.currentTarget = target;
28    this.defaultPrevented = false;
29    this.eventPhase = Event.AT_TARGET;
30    this.timeStamp = Date.now();
31  };
32
33  scope.Player = function(source) {
34    this._sequenceNumber = sequenceNumber++;
35    this._currentTime = 0;
36    this._startTime = null;
37    this.paused = false;
38    this._playbackRate = 1;
39    this._inTimeline = true;
40    this._finishedFlag = false;
41    this.onfinish = null;
42    this._finishHandlers = [];
43    this._source = source;
44    this._inEffect = this._source._update(0);
45    this._idle = true;
46    this._currentTimePending = false;
47  };
48
49  scope.Player.prototype = {
50    _ensureAlive: function() {
51      this._inEffect = this._source._update(this.currentTime);
52      if (!this._inTimeline && (this._inEffect || !this._finishedFlag)) {
53        this._inTimeline = true;
54        scope.timeline._players.push(this);
55      }
56    },
57    _tickCurrentTime: function(newTime, ignoreLimit) {
58      if (newTime != this._currentTime) {
59        this._currentTime = newTime;
60        if (this.finished && !ignoreLimit)
61          this._currentTime = this._playbackRate > 0 ? this._totalDuration : 0;
62        this._ensureAlive();
63      }
64    },
65    get currentTime() {
66      if (this._idle || this._currentTimePending)
67        return null;
68      return this._currentTime;
69    },
70    set currentTime(newTime) {
71      newTime = +newTime;
72      if (isNaN(newTime))
73        return;
74      scope.restart();
75      if (!this.paused && this._startTime != null) {
76        this._startTime = this._timeline.currentTime - newTime / this._playbackRate;
77      }
78      this._currentTimePending = false;
79      if (this._currentTime == newTime)
80        return;
81      this._tickCurrentTime(newTime, true);
82      scope.invalidateEffects();
83    },
84    get startTime() {
85      return this._startTime;
86    },
87    set startTime(newTime) {
88      newTime = +newTime;
89      if (isNaN(newTime))
90        return;
91      if (this.paused || this._idle)
92        return;
93      this._startTime = newTime;
94      this._tickCurrentTime((this._timeline.currentTime - this._startTime) * this.playbackRate);
95      scope.invalidateEffects();
96    },
97    get playbackRate() {
98      return this._playbackRate;
99    },
100    set playbackRate(value) {
101      var oldCurrentTime = this.currentTime;
102      this._playbackRate = value;
103      if (oldCurrentTime != null) {
104        this.currentTime = oldCurrentTime;
105      }
106    },
107    get finished() {
108      return !this._idle && (this._playbackRate > 0 && this._currentTime >= this._totalDuration ||
109          this._playbackRate < 0 && this._currentTime <= 0);
110    },
111    get _totalDuration() { return this._source._totalDuration; },
112    get playState() {
113      if (this._idle)
114        return 'idle';
115      if ((this._startTime == null && !this.paused && this.playbackRate != 0) || this._currentTimePending)
116        return 'pending';
117      if (this.paused)
118        return 'paused';
119      if (this.finished)
120        return 'finished';
121      return 'running';
122    },
123    play: function() {
124      this.paused = false;
125      if (this.finished || this._idle) {
126        this._currentTime = this._playbackRate > 0 ? 0 : this._totalDuration;
127        this._startTime = null;
128        scope.invalidateEffects();
129      }
130      this._finishedFlag = false;
131      scope.restart();
132      this._idle = false;
133      this._ensureAlive();
134    },
135    pause: function() {
136      if (!this.finished && !this.paused && !this._idle) {
137        this._currentTimePending = true;
138      }
139      this._startTime = null;
140      this.paused = true;
141    },
142    finish: function() {
143      if (this._idle)
144        return;
145      this.currentTime = this._playbackRate > 0 ? this._totalDuration : 0;
146      this._startTime = this._totalDuration - this.currentTime;
147      this._currentTimePending = false;
148    },
149    cancel: function() {
150      this._inEffect = false;
151      this._idle = true;
152      this.currentTime = 0;
153      this._startTime = null;
154    },
155    reverse: function() {
156      this._playbackRate *= -1;
157      this._startTime = null;
158      this.play();
159    },
160    addEventListener: function(type, handler) {
161      if (typeof handler == 'function' && type == 'finish')
162        this._finishHandlers.push(handler);
163    },
164    removeEventListener: function(type, handler) {
165      if (type != 'finish')
166        return;
167      var index = this._finishHandlers.indexOf(handler);
168      if (index >= 0)
169        this._finishHandlers.splice(index, 1);
170    },
171    _fireEvents: function(baseTime) {
172      var finished = this.finished;
173      if ((finished || this._idle) && !this._finishedFlag) {
174        var event = new AnimationPlayerEvent(this, this._currentTime, baseTime);
175        var handlers = this._finishHandlers.concat(this.onfinish ? [this.onfinish] : []);
176        setTimeout(function() {
177          handlers.forEach(function(handler) {
178            handler.call(event.target, event);
179          });
180        }, 0);
181      }
182      this._finishedFlag = finished;
183    },
184    _tick: function(timelineTime) {
185      if (!this._idle && !this.paused) {
186        if (this._startTime == null)
187          this.startTime = timelineTime - this._currentTime / this.playbackRate;
188        else if (!this.finished)
189          this._tickCurrentTime((timelineTime - this._startTime) * this.playbackRate);
190      }
191
192      this._currentTimePending = false;
193      this._fireEvents(timelineTime);
194      return !this._idle && (this._inEffect || !this._finishedFlag);
195    },
196  };
197
198  if (WEB_ANIMATIONS_TESTING) {
199    testing.Player = scope.Player;
200  }
201
202})(webAnimations1, webAnimationsTesting);
203