1 /**
  2  * This file is part of the smilText parser implemented in JavaScript,
  3  *
  4  * Copyright (C) 2003-2009 Stichting CWI, 
  5  * Science Park 123, 1098 XG Amsterdam, The Netherlands.
  6  *
  7  * smilText parser in JavaScript is free software; you can redistribute it and/or modify
  8  * it under the terms of the GNU Lesser General Public License as published by
  9  * the Free Software Foundation; either version 2.1 of the License, or
 10  * (at your option) any later version.
 11  *
 12  * smilText parser in JavaScript is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15  * GNU Lesser General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Lesser General Public License
 18  * along with smilText parser in JavaScript; if not, write to the Free Software
 19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 20  */
 21 
 22 /**
 23  @name cwi.smilText.Time
 24  @namespace Hold classes and methods related to the smilText time engine.
 25  @version 1.0
 26  @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a>
 27 */
 28 Namespace("cwi.smilText.Time");
 29 
 30 
 31 
 32 
 33 
 34 /* Solve library dependencies */
 35 Import("cwi.adt.Hashtable");
 36 Import("cwi.adt.DoubleLinkedList");
 37 
 38 /**
 39  * Stand for the play event of a Playable object.
 40  */
 41 cwi.smilText.Time.EVENT_PLAY = 1;
 42 
 43 /**
 44  * Stand for the pause event of a Playable object.
 45  */
 46 cwi.smilText.Time.EVENT_PAUSE = 2;
 47 
 48 /**
 49  * Stand for the stop event of a Playable object.
 50  */
 51 cwi.smilText.Time.EVENT_STOP = 0;
 52 
 53 /**
 54  * Stand for the natural end event of a Playable object.
 55  */
 56 cwi.smilText.Time.EVENT_END = -1;
 57 
 58 /**
 59  * Define the Playable interface.
 60  * @constructor
 61  * @author <a href="mailto:rlaiola@cwi.nl">Rodrigo Laiola Guimaraes</a>
 62  */
 63 cwi.smilText.Time.Playable = function()
 64 {
 65 	JSINER.extend(this);
 66 		
 67 	/**
 68 	* Variables
 69 	* @private
 70 	*/
 71 	this.timeNow = -1;							// Keep the current time.
 72 	this.state = 0;								// state: 0 (stopped); 1 (playing); 2 (paused)
 73 	this.externalClock = null;					// True whether the time is controlled by an external source. 
 74 												// False, otherwise.
 75 	this.eventListeners = new Hashtable();		// Keep event listeners
 76 	
 77 	// Creates a list for each type of event.
 78 	this.eventListeners.put(cwi.smilText.Time.EVENT_STOP, new DoubleLinkedList());
 79 	this.eventListeners.put(cwi.smilText.Time.EVENT_PLAY, new DoubleLinkedList());
 80 	this.eventListeners.put(cwi.smilText.Time.EVENT_PAUSE, new DoubleLinkedList());
 81 	this.eventListeners.put(cwi.smilText.Time.EVENT_END, new DoubleLinkedList());
 82 };
 83 
 84 /**
 85 * Add a callback function as a listener of an event. Listeners are notified in the event transition.
 86 * @param {integer} eventType The Playable event type.
 87 * @param {function} callback THe callback function.
 88 * @see cwi.smilText.Time.EVENT_PLAY
 89 * @see cwi.smilText.Time.EVENT_PAUSE
 90 * @see cwi.smilText.Time.EVENT_STOP
 91 * @see cwi.smilText.Time.EVENT_END
 92 */
 93 cwi.smilText.Time.Playable.prototype.addEventListener = function(eventType, callback) {
 94 	var list = this.eventListeners.get(eventType);
 95 	if (list && callback) {
 96 		this.removeEventListener(eventType, callback);
 97 		list.insertEnd(callback);
 98 	}
 99 }
100 
101 /**
102 * Remove a callback function as an event listener.
103 * @param {integer} eventType The Playable event type.
104 * @param {function} callback THe callback function.
105 * @see cwi.smilText.Time.EVENT_PLAY
106 * @see cwi.smilText.Time.EVENT_PAUSE
107 * @see cwi.smilText.Time.EVENT_STOP
108 * @see cwi.smilText.Time.EVENT_END
109 */
110 cwi.smilText.Time.Playable.prototype.removeEventListener = function(eventType, callback) {
111 	var list = this.eventListeners.get(eventType);
112 	if (list && callback) {
113 		list.resetIterator();
114 		while(list.hasNext()) {
115 			if (list.getCurrent() == callback) {
116 				list.remove();
117 				break;
118 			}
119 			list.moveToNext();
120 		}
121 	}
122 }
123 
124 /**
125 * Fire an event and notify all associated listeners. 
126 * @param {integer} eventType The Playable event to be fired.
127 * @see cwi.smilText.Time.EVENT_PLAY
128 * @see cwi.smilText.Time.EVENT_PAUSE
129 * @see cwi.smilText.Time.EVENT_STOP
130 * @see cwi.smilText.Time.EVENT_END
131 */
132 cwi.smilText.Time.Playable.prototype.fireEvent = function(eventType) {
133 	switch(eventType) {
134 		case cwi.smilText.Time.EVENT_PLAY:
135 			this.play();
136 			break;
137 		case cwi.smilText.Time.EVENT_PAUSE:
138 			this.pause();
139 			break;
140 		case cwi.smilText.Time.EVENT_STOP:
141 			this.stop();
142 			break;
143 		case cwi.smilText.Time.EVENT_END:
144 			if (this.isPlaying()) {
145 				this.state = cwi.smilText.Time.EVENT_END;
146 				this.notifyAll(eventType);
147 				this.notifyAll(cwi.smilText.Time.EVENT_STOP);
148 				//this.stop();
149 			}
150 			break;
151 	}
152 }
153 
154 /**
155 * Notify all the event listeners of a given event. 
156 * @private
157 */
158 cwi.smilText.Time.Playable.prototype.notifyAll = function(eventType) {
159 	var list = this.eventListeners.get(eventType);
160 	if (list) {
161 		list.resetIterator();
162 		while(list.hasNext()) {
163 			list.getCurrent().apply(this);
164 			list.moveToNext();
165 		}
166 	}
167 }
168 
169 /**
170 * Return the current time (in milliseconds).
171 * @return {integer}
172 */
173 cwi.smilText.Time.Playable.prototype.getTime = function() {
174 	return this.timeNow;
175 }
176 
177 /**
178 * Set the current time. The seekTo method will be called whether the new time 
179 * is before the current time.
180 * @param {integer} t the new time (in milliseconds).
181 */
182 cwi.smilText.Time.Playable.prototype.setTime = function(t) {
183 	if (this.isStopped() || this.isPaused())
184 		return;
185 		
186 	if (this.timeNow > t)
187 		this.seekTo(t);
188 	
189 	this.timeNow = t;
190 }
191 
192 /**
193 * Play the Playable Object.
194 */
195 cwi.smilText.Time.Playable.prototype.play = function() 
196 {
197 	if (this.externalClock == undefined || this.externalClock == null)
198 		this.setExternalClock(false);
199 		
200 	if (this.state != cwi.smilText.Time.EVENT_PLAY) {
201 		if (this.state == cwi.smilText.Time.EVENT_STOP ||
202 			this.state == cwi.smilText.Time.EVENT_END) {
203 			this.seekTo(0);
204 		}
205 		
206 		this.notifyAll(cwi.smilText.Time.EVENT_PLAY);
207 	}
208 	
209 	this.state = cwi.smilText.Time.EVENT_PLAY;
210 }
211 
212 /**
213 * Return true whether the Playable Object is playing. Otherwise, false.
214 * @return {boolean}
215 */
216 cwi.smilText.Time.Playable.prototype.isPlaying = function() 
217 {
218 	return this.state == cwi.smilText.Time.EVENT_PLAY;
219 }
220 
221 /**
222 * Pause the Playable Object.
223 */
224 cwi.smilText.Time.Playable.prototype.pause = function() 
225 {
226 	if (this.state == cwi.smilText.Time.EVENT_PLAY) {
227 		this.notifyAll(cwi.smilText.Time.EVENT_PAUSE);
228 		this.state = cwi.smilText.Time.EVENT_PAUSE;
229 	}
230 }
231 
232 /**
233 * Return true whether the Playable Object is paused. Otherwise, false.
234 * @return {boolean}
235 */
236 cwi.smilText.Time.Playable.prototype.isPaused = function() 
237 {
238 	return this.state == cwi.smilText.Time.EVENT_PAUSE;
239 }
240 
241 /**
242 * Stop the Playable Object.
243 */
244 cwi.smilText.Time.Playable.prototype.stop = function() 
245 {
246 	if (this.state != cwi.smilText.Time.EVENT_STOP) {
247 		this.notifyAll(cwi.smilText.Time.EVENT_STOP);
248 	}
249 	
250 	//if (this.state != cwi.smilText.Time.EVENT_END) {
251 		this.seekTo(0);
252 	//}
253 	this.state = cwi.smilText.Time.EVENT_STOP;
254 }
255 
256 /**
257 * Return true whether the Playable Object is stopped. Otherwise, false.
258 * @return {boolean}
259 */
260 cwi.smilText.Time.Playable.prototype.isStopped = function() 
261 {
262 	return this.state == cwi.smilText.Time.EVENT_STOP;
263 }
264 
265 /**
266 * Perform the seek operation to a given time moment. The current time is also updated.
267 * @param {integer} t the desired time instant (in milliseconds).
268 */
269 cwi.smilText.Time.Playable.prototype.seekTo = function(t) {
270 	this.timeNow = t;
271 }
272 
273 /**
274  * Setup the timing source.
275  * @param {string} flag true if an external clock source will be used. In this case,
276  * the setTime method must be called by the external clock. Otherwise, false. 
277  * And an internal clock will be used.
278  */
279 cwi.smilText.Time.Playable.prototype.setExternalClock = function(flag)
280 {
281 	if (this.externalClock == flag)
282 		return;
283 	
284 	this.externalClock = flag;
285 	
286 	if (!this.externalClock)
287 	{
288 		cwi.smilText.Time.getInstance().register(this);
289 	} else {
290 		cwi.smilText.Time.getInstance().unregister(this);
291 	}
292 }
293 
294 
295 
296 
297 
298 /* Solve library dependency */
299 Import("cwi.adt.DoubleLinkedList");
300 Import("cwi.smilText.Time.Playable");
301 
302 /**
303  * Hold the smilText time engine instance, which controls the documents' scheduling.
304  */
305 cwi.smilText.Time.instance = new function()
306 {
307 	
308 	/** 
309 	* Variables
310 	* @private
311 	*/
312 	this.list = new DoubleLinkedList();		// Keep registered documents.
313 	this.timeInc = 100;						// Time increment.
314 	
315 	/**
316 	* Store a given document.
317 	* @private
318 	* @param {cwi.smilText.Time.Playable} doc the playable object to be registered
319 	* @see cwi.smilText.Time.Playable
320 	*/
321 	this.register = function(doc)
322 	{
323 		this.list.insertEnd(doc);
324 	}
325 	
326 	/**
327 	* Remove a given document from the time engine.
328 	* @private
329 	* @param {cwi.smilText.Time.Playable} doc the playable object to be unregistered
330 	* @return {boolean}
331 	* @see cwi.smilText.Time.Playable
332 	*/
333 	this.unregister = function(doc)
334 	{
335 		this.list.resetIterator();
336 		while(this.list.hasNext())
337 		{
338 			var d = this.list.getCurrent();
339 			if (d === doc) {
340 				this.list.remove();
341 				return true;
342 			}
343 			this.list.moveToNext();
344 		}
345 		
346 		return false;
347 	}
348 	
349 	/**
350 	 * Update the current time of registered playable objects.
351 	 * @private
352 	 */	
353 	this.updateTime = function()
354 	{
355 		this.list.resetIterator();
356 		while(this.list.hasNext())
357 		{
358 			var d = this.list.getCurrent();
359 			// update time
360 			d.setTime(d.getTime() + this.timeInc);
361 			this.list.moveToNext();
362 		}
363 		
364 		setTimeout("cwi.smilText.Time.getInstance().updateTime()", this.timeInc);
365 	}
366 	
367 	/* Make sure the time engine is started */
368 	this.updateTime();
369 };
370 
371 /**
372 * Register a given playable object to be scheduled by the time engine.
373 * @param {cwi.smilText.Time.Playable} doc the playable object to be registered
374 * @see cwi.smilText.Time.Playable
375 */
376 cwi.smilText.Time.register = function(doc) 
377 {
378 	cwi.smilText.Time.getInstance().register(doc);
379 };
380 
381 /**
382 * Remove a given playable object from the time engine scheduler.
383 * @param {cwi.smilText.Time.Playable} doc the playable object to be unregistered
384 * @return {boolean}
385 * @see cwi.smilText.Time.Playable
386 */
387 cwi.smilText.Time.unregister = function(doc)
388 {
389 	return cwi.smilText.Time.getInstance().unregister(doc);
390 };
391 
392 /**
393 * Get the singleton instance of the time engine.
394 * @return {cwi.smilText.Time}
395 */
396 cwi.smilText.Time.getInstance = function() {
397 	return cwi.smilText.Time.instance;
398 }
399