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