1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2007, 2008, 2009, 2010, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2007, 2008, 2009, 2010, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a sound plugin control.
 26  * @constructor
 27  * @class
 28  * This class represents a widget that plays sounds. It uses a plugin such as Quick Time
 29  * or Windows Media to play the sounds and to display player controls. Do not invoke the
 30  * constructor directly. Instead use the create() method, which will choose the right
 31  * concrete class based on available plugins.
 32  *
 33  * @param	{hash}	params		a hash of parameters
 34  * @param {DwtControl}	params.parent	 the parent widget
 35  * @param {number}	params.width		the width of player
 36  * @param {number}	params.height		the height of player
 37  * @param {number}	params.volume		volume on a scale of 0 - {@link DwtSoundPlugin.MAX_VOLUME}
 38  * @param {string}	params.url		{String} the sound url
 39  * @param {boolean}	[params.offscreen]	{Boolean} if <code>true</code>, the player is initially offscreen. Use an appropriate position style
 40  * 							  if you set this to <code>true</code>. (This reduces flicker, and a tendency for the QT player
 41  * 							  to float in the wrong place when it's first created)
 42  * @param {string}	[params.className] the CSS class
 43  * @param {constant}	[params.posStyle=DwtControl.STATIC_STYLE] 	the positioning style (see {@link DwtControl})
 44  * 
 45  * @extends		DwtControl
 46  */
 47 DwtSoundPlugin = function(params) {
 48 	if (arguments.length == 0) return;
 49 	params.className = params.className || "DwtSoundPlugin";
 50 	DwtControl.call(this, {parent:params.parent, className:params.className, posStyle:params.posStyle});
 51 	this._width = params.width || 200;
 52 	this._height = params.height || 18;
 53 	if (params.offscreen) {
 54 		this.setLocation(Dwt.LOC_NOWHERE, Dwt.LOC_NOWHERE);
 55 	}
 56 };
 57 
 58 DwtSoundPlugin.prototype = new DwtControl;
 59 DwtSoundPlugin.prototype.constructor = DwtSoundPlugin;
 60 
 61 /**
 62  * Defines the "max" volume.
 63  */
 64 DwtSoundPlugin.MAX_VOLUME = 256;
 65 
 66 // Status codes.
 67 DwtSoundPlugin.WAITING = 1;
 68 DwtSoundPlugin.LOADING = 2;
 69 DwtSoundPlugin.PLAYABLE = 3;
 70 DwtSoundPlugin.ERROR = 4;
 71 
 72 /**
 73  * Factory method. Creates an appropriate sound player for whatever plugins are or are not installed.
 74  *
 75  * @param	{hash}	params		a hash of parameters
 76  * @param {DwtControl}	params.parent	 the parent widget
 77  * @param {number}	params.width		the width of player
 78  * @param {number}	params.height		the height of player
 79  * @param {number}	params.volume		volume on a scale of 0 - {@link DwtSoundPlugin.MAX_VOLUME}
 80  * @param {string}	params.url		{String} the sound url
 81  * @param {boolean}	[params.offscreen]	{Boolean} if <code>true</code>, the player is initially offscreen. Use an appropriate position style
 82  * 							  if you set this to <code>true</code>. (This reduces flicker, and a tendency for the QT player
 83  * 							  to float in the wrong place when it's first created)
 84  * @param {string}	[params.className] the CSS class
 85  * @param {constant}	[params.posStyle=DwtControl.STATIC_STYLE] 	the positioning style (see {@link DwtControl})
 86  */
 87 DwtSoundPlugin.create =
 88 function(params) {
 89 	var pluginClass = this._getPluginClass();
 90 	DBG.println("DwtSoundPlugin.create class= " + pluginClass.prototype.toString() + " url=" + params.url);
 91 	return new pluginClass(params);
 92 };
 93 
 94 /**
 95  * Checks if the plugin is missing.
 96  * 
 97  * @return	{boolean}	<code>true</code> if plugin is missing
 98  */
 99 DwtSoundPlugin.isPluginMissing =
100 function() {
101 	var pluginClass = this._getPluginClass();
102 	return pluginClass._pluginMissing;
103 };
104 
105 /**
106  * Checks if scripting is broken.
107  * 
108  * @return	{boolean}	<code>true</code> if scripting is broken
109  */
110 DwtSoundPlugin.isScriptingBroken =
111 function() {
112 	var pluginClass = this._getPluginClass();
113 	return pluginClass._isScriptingBroken;
114 };
115 
116 DwtSoundPlugin._getPluginClass =
117 function() {
118 	if (!DwtSoundPlugin._pluginClass) {
119 		if (AjxEnv.isIE) {
120             var version;
121             try {
122                 version = AjxPluginDetector.getQuickTimeVersion();
123             } catch (e) {
124             }
125 
126             //Use Quicktime for IE 8, as IE8 windows media player does not work with httpOnly cookie attribute
127             //TODO: Currently if Quick time is not installed, users will not get any prompt to install it.
128             if (AjxEnv.isIE8 && version) {
129                 DwtSoundPlugin._pluginClass = DwtQTSoundPlugin;
130             } else if (AjxPluginDetector.detectWindowsMedia()) {
131                 //IE8 windows media player does not work with httpOnly cookie attribute
132                 DwtSoundPlugin._pluginClass = DwtWMSoundPlugin;
133             }
134 
135 		} 
136 		else if (AjxEnv.isSafari5up && !AjxEnv.isChrome && !AjxEnv.isWindows) {
137 			//safari quicktime does not work with httpOnly cookie attribute
138 			DwtSoundPlugin._pluginClass = DwtHtml5SoundPlugin;
139 		}
140 		else {
141 			var version = AjxPluginDetector.getQuickTimeVersion();
142 			if (version) {
143 				DBG.println("DwtSoundPlugin: QuickTime version=" + version);
144 				if (DwtQTSoundPlugin.checkVersion(version) && DwtQTSoundPlugin.checkScripting()) {
145 					DwtSoundPlugin._pluginClass = DwtQTSoundPlugin;
146 				} else {
147 					DwtSoundPlugin._pluginClass = DwtQTBrokenSoundPlugin;
148 				}
149 			} else {
150 				if (window.DBG && !DBG.isDisabled()) {
151 					DBG.println("DwtSoundPlugin: unable to get QuickTime version. Checking if QuickTime is installed at all...");
152 					AjxPluginDetector.detectQuickTime(); // Called only for logging purposes.
153 				}
154 			}
155 		}
156 		if (!DwtSoundPlugin._pluginClass) {
157 			DwtSoundPlugin._pluginClass = DwtMissingSoundPlugin;
158 		}
159 		DBG.println("DwtSoundPlugin: plugin class = " + DwtSoundPlugin._pluginClass.prototype.toString());
160 	}
161 	return DwtSoundPlugin._pluginClass;
162 };
163 
164 // "Abstract" methods.
165 /**
166  * Plays the sound.
167  * 
168  */
169 DwtSoundPlugin.prototype.play =
170 function() {
171 };
172 
173 /**
174  * Pauses the sound.
175  * 
176  */
177 DwtSoundPlugin.prototype.pause =
178 function() {
179 };
180 
181 /**
182  * Rewinds the sound.
183  * 
184  */
185 DwtSoundPlugin.prototype.rewind =
186 function() {
187 };
188 
189 /**
190  * Sets the current time in milliseconds.
191  * 
192  * @param	{number}	time		the time (in milliseconds)
193  */
194 DwtSoundPlugin.prototype.setTime =
195 function(time) {
196 };
197 
198 /**
199  * Sets the volume.
200  *
201  * @param {number}	volume	the volume
202  * 
203  * @see		DwtSoundPlugin.MAX_VOLUME
204  */
205 DwtSoundPlugin.prototype.setVolume =
206 function(volume) {
207 };
208 
209 /*
210  * Fills in the event with the following status information:
211  * - status, a constant representing the loaded state of the sound
212  * - duration, the length of the sound
213  * - time, the current time of the sound
214  * Returns true to continue monitoring status
215  */
216 DwtSoundPlugin.prototype._resetEvent =
217 function(event) {
218 	return false;
219 };
220 
221 DwtSoundPlugin.prototype.dispose =
222 function() {
223 	DwtControl.prototype.dispose.call(this);
224 	this._ignoreStatus();
225 };
226 
227 /**
228  * Adds a change listener to monitor the status of the sound being played.
229  * The listener will be passed an event object with the following fields:
230  * <ul>
231  * <li>status, a constant representing the loaded state of the sound</li>
232  * <li>duration, the length of the sound</li>
233  * <li>time, the current time of the sound</li>
234  * </ul>
235  * 
236  * @param {AjxListener}	listener	the listener
237  */
238 DwtSoundPlugin.prototype.addChangeListener =
239 function(listener) {
240     this.addListener(DwtEvent.ONCHANGE, listener);
241     this._monitorStatus();
242 };
243 
244 DwtSoundPlugin.prototype._monitorStatus =
245 function() {
246 	if (this.isListenerRegistered(DwtEvent.ONCHANGE)) {
247 		if (!this._statusAction) {
248 			this._statusAction = new AjxTimedAction(this, this._checkStatus);
249 		}
250 		this._statusActionId = AjxTimedAction.scheduleAction(this._statusAction, 250);
251 	}
252 };
253 
254 DwtSoundPlugin.prototype._ignoreStatus =
255 function() {
256 	if (this._statusActionId) {
257 		AjxTimedAction.cancelAction(this._statusActionId);
258 	}
259 };
260 
261 DwtSoundPlugin.prototype._checkStatus =
262 function() {
263 	this._statusActionId = 0;
264 	if (!this._changeEvent) {
265 		this._changeEvent = new DwtEvent(true);
266 		this._changeEvent.dwtObj = this;
267 	}
268 	var keepChecking = this._resetEvent(this._changeEvent);
269     this.notifyListeners(DwtEvent.ONCHANGE, this._changeEvent);
270     if (keepChecking) {
271 		this._monitorStatus();
272     }
273 };
274 
275 DwtSoundPlugin.prototype._createQTHtml =
276 function(params) {
277 	// Adjust volume because the html parameter is in [0 - 100], while the
278 	// javascript method takes [0 - 256].
279 	var volume = params.volume * 100 / 256;
280 	var html = [
281 		"<embed classid='clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B' ",
282 		"id='", this._playerId,
283 		"' width='", this._width,
284 		"' height='", this._height,
285 		"' src='", params.url,
286 		"' volume='", volume,
287 		"' enablejavascript='true' type='audio/wav'/>"
288 	];
289 	this.getHtmlElement().innerHTML = html.join("");
290 };
291 
292 /**
293  * Sound player that goes through the QuickTime (QT) plugin.
294  * @class
295  * Some useful references when dealing with quick time:
296  * <ul>
297  * <li>Quick Time script reference
298  * <a href="http://developer.apple.com/documentation/QuickTime/Conceptual/QTScripting_JavaScript/bQTScripting_JavaScri_Document/chapter_1000_section_5.html" target="_blank">http://developer.apple.com/documentation/QuickTime/Conceptual/QTScripting_JavaScript/bQTScripting_JavaScri_Document/chapter_1000_section_5.html</a>
299  * </li>
300  * <li>Quick Time embed tag attributes tutorial
301  * <a href="http://www.apple.com/quicktime/tutorials/embed2.html" target="_blank">http://www.apple.com/quicktime/tutorials/embed2.html</a>
302  * </li>
303  * </ul>
304  * 
305  * @param	{hash}	params		a hash of parameters
306  * 
307  * @extends		DwtSoundPlugin
308  * 
309  * @private
310  */
311 DwtQTSoundPlugin = function(params) {
312 	if (arguments.length == 0) return;
313 	params.className = params.className || "DwtSoundPlugin";
314 	DwtSoundPlugin.call(this, params);
315 
316 	this._playerId = Dwt.getNextId();
317 	this._createHtml(params);
318 };
319 
320 DwtQTSoundPlugin.prototype = new DwtSoundPlugin;
321 DwtQTSoundPlugin.prototype.constructor = DwtQTSoundPlugin;
322 
323 DwtQTSoundPlugin.prototype.toString =
324 function() {
325 	return "DwtQTSoundPlugin";
326 };
327 
328 /**
329  * Checks the QuickTime version.
330  * 
331  * @param	{array}	version	the version as an array (for example: 7.1.6 is [7, 1, 6] )
332  * @return	{boolean}	<code>true</code> if version is OK
333  */
334 DwtQTSoundPlugin.checkVersion =
335 function(version) {
336 	if (AjxEnv.isFirefox) {
337 		// Quicktime 7.1.6 introduced a nasty bug in Firefox that can't be worked around by
338 		// the checkScripting() routine below. I'm going to disable all QT versions that
339 		// are greater than 7.1.6. We should change this check when QT is fixed. More info:
340 		// http://lists.apple.com/archives/quicktime-users/2007/May/msg00016.html
341 		var badVersion = "716";
342 		var currentVersion = "";
343 		for(var i = 0; i < version.length; i++) {
344 			currentVersion += version[i];
345 		}
346 		return currentVersion != badVersion;
347 	} 
348 	return true;
349 };
350 
351 /**
352  * Checks scripting.
353  * 
354  * @return	{boolean}	<code>true</code> if scripting is OK
355  */
356 DwtQTSoundPlugin.checkScripting =
357 function() {
358 	var success = false;
359 	var shell = DwtControl.fromElementId(window._dwtShellId);
360 	var args = {
361 		parent: shell,
362 		width: 200,
363 		height: 16,
364 		offscreen: true,
365 		posStyle: DwtControl.RELATIVE_STYLE,
366 		url: "/public/sounds/im/alert.wav", // Not a valid url.
367 		volume: 0
368 	};
369 	var test = new DwtQTSoundPlugin(args);
370 	try {
371 		var element = test._getPlayer();
372 		success = element.GetQuickTimeVersion && element.GetQuickTimeVersion();
373 		if (!success) {
374 			DBG.println("The QuickTime plugin in this browser does not support JavaScript.");
375 		}
376 	} catch (e) {
377 		DBG.println("An exception was thrown while checking QuickTime: " + e.toString());
378 	} finally {
379 		test.dispose();
380 	}
381 	return success;
382 };
383 
384 DwtQTSoundPlugin.prototype.play =
385 function() {
386 	var player = this._getPlayer();
387     try {
388 	    player.Play();
389     }catch(e) {
390     }
391 	this._monitorStatus();
392 };
393 
394 DwtQTSoundPlugin.prototype.pause =
395 function() {
396 	try {
397 		var player = this._getPlayer();
398 		player.Stop();
399 	} catch (e) {
400 		// Annoying: QT gets all messed up if you stop it before it's loaded.
401 		// I could try and do more here...check the status, if it's waiting then
402 		// set some flag and then if I somehow knew when the sound loaded, I
403 		// could prevent it from playing.
404 		DBG.println("Failed to stop QuickTime player.");
405 	}
406 };
407 
408 DwtQTSoundPlugin.prototype.rewind =
409 function() {
410 	try {
411 		var player = this._getPlayer();
412 		player.Rewind();
413 	} catch (e) {
414 		// Grrr. Same problem here as described in pause();
415 		DBG.println("Failed to rewind QuickTime player.");
416 	}
417 };
418 
419 DwtQTSoundPlugin.prototype.setTime =
420 function(time) {
421 	var player = this._getPlayer();
422 	try {
423 		var scale = 1000 / player.GetTimeScale(); // Converts to milliseconds.
424 		player.SetTime(time / scale);
425 	} catch (e) {
426 		// Grrr. Same problem here as described in pause();
427 		DBG.println("Failed to rewind QuickTime player.");
428 	}
429 };
430 
431 DwtQTSoundPlugin.prototype.setVolume =
432 function(volume) {
433 	var player = this._getPlayer();
434 	player.SetVolume(volume);
435 };
436 
437 DwtQTSoundPlugin.prototype._resetEvent =
438 function(event) {
439 	var keepChecking = true;
440 	var player = this._getPlayer();
441 	event.finished = false;
442 	var valid = false;
443 	if (player) {
444 		var status = player.GetPluginStatus();
445 		switch (status) {
446 			case "Waiting":
447 				event.status = DwtSoundPlugin.LOADING;
448 				break;
449 			case "Loading":
450 				event.status = DwtSoundPlugin.LOADING;
451 				break;
452 			case "Playable":
453 			case "Complete":
454 				valid = true;
455 				event.status = DwtSoundPlugin.PLAYABLE;
456 				break;
457 			default :
458 				event.status = DwtSoundPlugin.ERROR;
459 				event.errorDetail = status;
460 				keepChecking = false;
461 				break;
462 		}
463 	}
464 	if (valid) {
465 		var scale = 1000 / player.GetTimeScale(); // Converts to milliseconds.
466 		event.time = player.GetTime() * scale;
467 		event.duration = player.GetDuration() * scale;
468 	} else {
469 		event.status = DwtSoundPlugin.WAITING;
470 		event.time = 0;
471 		event.duration = 100;
472 	}
473 	if (event.status == DwtSoundPlugin.PLAYABLE && event.time == event.duration) {
474 		event.time = 0;
475 		event.finished = true;
476 		keepChecking = false;
477 	}
478 	return keepChecking;
479 };
480 
481 DwtQTSoundPlugin.prototype._createHtml =
482 function(params) {
483 	this._createQTHtml(params);
484 };
485 
486 DwtQTSoundPlugin.prototype._getPlayer =
487 function() {
488 	return document.getElementById(this._playerId);
489 };
490 
491 //////////////////////////////////////////////////////////////////////////////
492 // Sound player that uses the QuickTime (QT) plugin, but does not
493 // make script calls to the plugin. This handles the bad quicktime
494 // installs all over the place.
495 //
496 //////////////////////////////////////////////////////////////////////////////
497 DwtQTBrokenSoundPlugin = function(params) {
498 	if (arguments.length == 0) return;
499 	params.className = params.className || "DwtSoundPlugin";
500 	DwtSoundPlugin.call(this, params);
501 
502 	this._playerId = Dwt.getNextId();
503 	this._createHtml(params);
504 };
505 
506 DwtQTBrokenSoundPlugin.prototype = new DwtSoundPlugin;
507 DwtQTBrokenSoundPlugin.prototype.constructor = DwtQTBrokenSoundPlugin;
508 
509 DwtQTBrokenSoundPlugin.prototype.toString =
510 function() {
511 	return "DwtQTBrokenSoundPlugin";
512 };
513 
514 DwtQTBrokenSoundPlugin._isScriptingBroken = true;
515 
516 DwtQTBrokenSoundPlugin.prototype._resetEvent =
517 function(event) {
518 	// Make up some fake event data
519 	event.time = 0;
520 	event.duration = 100;
521 	event.status = DwtSoundPlugin.PLAYABLE;
522 	event.finished = true; // Allows messages to be marked as read.
523 	return false; // Stop checking status.
524 };
525 
526 DwtQTBrokenSoundPlugin.prototype._createHtml =
527 function(params) {
528 	this._createQTHtml(params);
529 };
530 
531 /**
532  * Sound player that goes through the HTML5 audio tag
533  * @class
534  * This class provides and HTML5 audio widget
535  *
536  * @param	{hash}	params		a hash of parameters
537  *
538  * @extends		DwtSoundPlugin
539  *
540  * @private
541  */
542 DwtHtml5SoundPlugin = function(params) {
543 	if (arguments.length == 0) return;
544 	params.className = params.className || "DwtSoundPlugin";
545 	DwtSoundPlugin.call(this, params);
546 
547 	this._playerId = Dwt.getNextId();
548     this._retryLoadAudio = 0;
549 	this._createHtml(params);	
550 };
551 
552 DwtHtml5SoundPlugin.prototype = new DwtSoundPlugin;
553 DwtHtml5SoundPlugin.prototype.constructor = DwtHtml5SoundPlugin;
554 
555 DwtHtml5SoundPlugin.prototype.toString = 
556 function() {
557 	return "DwtHtml5SoundPlugin";
558 };
559 
560 DwtHtml5SoundPlugin.prototype._createHtml = 
561 function(params) {
562     if (AjxEnv.isSafari) {
563         AjxRpc.invoke(null, params.url, { 'X-Zimbra-Encoding': 'x-base64' },
564             this._setSource.bind(this), AjxRpcRequest.HTTP_GET);
565     } else {
566         this.getHtmlElement().innerHTML = this._getAudioHtml(params.url);
567     }
568 };
569 
570 DwtHtml5SoundPlugin.prototype._getAudioHtml =
571 function(source) {
572     var html = [
573      "<audio autoplay='yes' ",
574      "id='", this._playerId,
575      "' preload ",
576      "><source src='", source,
577      "' type='", ZmVoiceApp.audioType, "' />",
578      "</audio>"
579      ];
580     return html.join("");
581 };
582 
583 DwtHtml5SoundPlugin.prototype._setSource =
584 function(response) {
585     if(response.success){
586         this.getHtmlElement().innerHTML = this._getAudioHtml('data:' + ZmVoiceApp.audioType +';base64,' + response.text);
587     }
588 };
589 
590 DwtHtml5SoundPlugin.prototype.play =
591 function() {
592 	var player = this._getPlayer();
593     if (player && player.readyState){
594         try {
595 	        this._monitorStatus();
596             player.play();
597         } catch (ex){
598             DBG.println("Exception in DwtHtml5SoundPlugin.prototype.play: "+ ex);
599         }
600     }
601 };
602 
603 DwtHtml5SoundPlugin.prototype.pause =
604 function() {
605 	var player = this._getPlayer();
606     if (player && player.readyState){
607         try {
608             player.pause();
609         } catch (ex){
610             DBG.println("Exception in DwtHtml5SoundPlugin.prototype.pause: "+ ex);
611         }
612     }
613 };
614 
615 DwtHtml5SoundPlugin.prototype._getPlayer =
616 function() {
617 	return document.getElementById(this._playerId);
618 };
619 
620 
621 DwtHtml5SoundPlugin.prototype.rewind =
622 function() {
623 	this.setTime(0);
624 };
625 
626 DwtHtml5SoundPlugin.prototype.setTime =
627 function(time) {
628     if (isNaN(time)){
629         time = 0;
630     }
631     time = (time > 0) ?  (time / 1000) : 0;
632 	var player = this._getPlayer();
633     try {
634         if (player && player.readyState && player.currentTime != time ){
635             player.currentTime = time;
636             if (player.controls){
637                 player.controls.currentPosition =  time;
638             }
639         }
640     } catch(ex){
641         DBG.println("Exception in DwtHtml5SoundPlugin.prototype.setTime: "+ ex);
642     }
643 };
644 
645 DwtHtml5SoundPlugin.prototype._resetEvent =
646 function(event) {
647 	var keepChecking = true;
648 	var player = this._getPlayer();
649 	event.finished = false;
650 	/*
651 	use HTML5 canPlay event instead
652 	var valid = false;
653 	if (player) {
654 		var status = player.duration;
655 		switch (status) {
656 			case "NaN":
657 				event.status = DwtSoundPlugin.LOADING;
658 				break;
659 			default :
660 				event.status = DwtSoundPlugin.PLAYABLE;
661 				valid = true;
662 				keepChecking = false;
663 				break;
664 		}
665 	} */
666 	/*if (valid) {
667 		var scale = 1000; // Converts to milliseconds.
668 		event.time = player.currentTime * scale;
669 		event.duration = player.duration * scale;
670 	} */
671 	if(!valid) {
672 		event.status = DwtSoundPlugin.WAITING;
673 		event.time = 0;
674 		event.duration = 100;
675 	}
676 	if (event.status == DwtSoundPlugin.PLAYABLE && event.time == event.duration) {
677 		event.time = 0;
678 		event.finished = true;
679 		keepChecking = false;
680 	}
681 	return keepChecking;
682 };
683 
684 /**
685  * Adds a change listener to monitor the status of the sound being played.
686  * Handles the HTML5 event timeupdate 
687  * The listener will be passed an event object with the following fields:
688  * <ul>
689  * <li>status, a constant representing the loaded state of the sound</li>
690  * <li>duration, the length of the sound</li>
691  * <li>time, the current time of the sound</li>
692  * </ul>
693  *
694  * @param {AjxListener}	listener	the listener
695  */
696 DwtHtml5SoundPlugin.prototype.addChangeListener =
697 function(listener) {
698 	var player = this._getPlayer();
699     if (!player){
700         if (this._retryLoadAudio < 10){
701             setTimeout(this.addChangeListener.bind(this,listener), 1000);
702         }
703         this._retryLoadAudio++;
704         return;
705     }
706 	var obj = this;
707 	player.addEventListener("timeupdate", function(e) { 
708 			listener.handleEvent({time: player.currentTime * 1000, duration: player.duration * 1000, status: DwtSoundPlugin.PLAYABLE});}, false);
709 	player.addEventListener("ended", function(e) {
710 			player.pause(); 
711 			obj.setTime(0);
712 			listener.obj.setFinished();
713 	});
714 	player.addEventListener("canplay", function(event) {
715 		var scale = 1000; // Converts to milliseconds.
716 		event.time = player.currentTime * scale;
717 		event.duration = player.duration * scale;
718 		player.play();
719 	});
720 	this._monitorStatus();
721 };
722 //////////////////////////////////////////////////////////////////////////////
723 // Sound player that goes through the Windows Media (WM) plugin.
724 //
725 // Some useful references when dealing with wmp:
726 // Adding Windows Media to Web Pages - Adding Scripting
727 //   http://msdn2.microsoft.com/en-us/library/ms983653.aspx#adding_scripting__yhbx
728 // Parameters supported by Windows Media Player
729 //   http://www.mioplanet.com/rsc/embed_mediaplayer.htm
730 // WM Object Model Reference:
731 //   http://msdn2.microsoft.com/en-us/library/bb249259.aspx
732 //////////////////////////////////////////////////////////////////////////////
733 DwtWMSoundPlugin = function(params) {
734 	if (arguments.length == 0) return;
735 	params.className = params.className || "DwtSoundPlugin";
736 	DwtSoundPlugin.call(this, params);
737 
738 	this._playerId = Dwt.getNextId();
739 	this._createHtml(params);
740 };
741 
742 DwtWMSoundPlugin.prototype = new DwtSoundPlugin;
743 DwtWMSoundPlugin.prototype.constructor = DwtWMSoundPlugin;
744 
745 DwtWMSoundPlugin.prototype.toString =
746 function() {
747 	return "DwtWMSoundPlugin";
748 };
749 
750 DwtWMSoundPlugin.prototype.play =
751 function() {
752 	var player = this._getPlayer();
753 	player.controls.play();
754 	this._monitorStatus();
755 };
756 
757 DwtWMSoundPlugin.prototype.pause =
758 function() {
759 	var player = this._getPlayer();
760 	player.controls.pause();
761 };
762 
763 DwtWMSoundPlugin.prototype.rewind =
764 function() {
765 	this.setTime(0);
766 };
767 
768 DwtWMSoundPlugin.prototype.setTime =
769 function(time) {
770 	var player = this._getPlayer();
771 	player.controls.currentPosition = time / 1000;
772 };
773 
774 DwtWMSoundPlugin.prototype.setVolume =
775 function(volume) {
776 	var volume = volume * 100 / 256;
777 	var player = this._getPlayer();
778 	player.settings.volume = volume;
779 };
780 
781 DwtWMSoundPlugin.prototype._resetEvent =
782 function(event) {
783 	var keepChecking = true;
784 	var player = this._getPlayer();
785 	var error = player.currentMedia.error;
786 	if (error) {
787 		event.status = DwtSoundPlugin.ERROR;
788 		event.errorDetail = error.errorDescription;
789 		keepChecking = false;
790 	} else if (!player.controls.isAvailable("currentPosition")) { // if (!is loaded)
791 		// Whatever....fake data.
792 		event.status = DwtSoundPlugin.LOADING;
793 		event.time = 0;
794 		event.duration = 100;
795 	} else {
796 		event.status = DwtSoundPlugin.PLAYABLE;
797 		event.time = player.controls.currentPosition * 1000;
798 		event.duration = player.currentMedia.duration * 1000 || event.time + 100; // Make sure max > min in slider
799 		if (!event.time) {
800 			event.finished = true;
801 			keepChecking = false;
802 			player.close();
803 		}
804 	}
805 	return keepChecking;
806 };
807 
808 //TODO: Take out all the AjxEnv stuff in here, unless we find a way to use WMP in Firefox.
809 DwtWMSoundPlugin.prototype._createHtml =
810 function(params) {
811 	var volume = params.volume * 100 / 256;
812 
813 	var html = [];
814 	var i = 0;
815 	if (AjxEnv.isIE) {
816 		html[i++] = "<object classid='CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6' id='";
817 		html[i++] = this._playerId;
818 		html[i++] = "'>";
819 	} else {
820 		html[i++] = "<embed classid='CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6' id='";
821 		html[i++] = this._playerId;
822 		html[i++] = "' ";
823 	}
824 	var pluginArgs = {
825 		width: this._width,
826 		height: this._height,
827 		url: params.url,
828 		volume: volume,
829 		enablejavascript: "true" };
830 	for (var name in pluginArgs) {
831 		if (AjxEnv.isIE) {
832 			html[i++] = "<param name='";
833 			html[i++] = name;
834 			html[i++] = "' value='";
835 			html[i++] = pluginArgs[name];
836 			html[i++] = "'/>";
837 		} else {
838 			html[i++] = name;
839 			html[i++] = "='";
840 			html[i++] = pluginArgs[name];
841 			html[i++] = "' ";
842 		}
843 	}
844 	if (AjxEnv.isIE) {
845 		html[i++] = "</object>";
846 	} else {
847 		html[i++] = " type='application/x-mplayer2'/>";
848 	}
849 
850 	this.getHtmlElement().innerHTML = html.join("");
851 	DBG.printRaw(html.join(""));
852 };
853 
854 DwtWMSoundPlugin.prototype._getPlayer =
855 function() {
856 	return document.getElementById(this._playerId);
857 };
858 
859 //////////////////////////////////////////////////////////////////////////////
860 // Sound player for browsers without a known sound plugin.
861 //////////////////////////////////////////////////////////////////////////////
862 DwtMissingSoundPlugin = function(params) {
863 	if (arguments.length == 0) return;
864 	params.className = params.className || "DwtSoundPlugin";
865 	DwtSoundPlugin.call(this, params);
866 
867     var args = { };
868     this.getHtmlElement().innerHTML = AjxTemplate.expand("dwt.Widgets#DwtMissingSoundPlayer", args);
869 
870     this._setMouseEventHdlrs();
871 };
872 
873 DwtMissingSoundPlugin.prototype = new DwtSoundPlugin;
874 DwtMissingSoundPlugin.prototype.constructor = DwtMissingSoundPlugin;
875 
876 DwtMissingSoundPlugin.prototype.toString =
877 function() {
878 	return "DwtMissingSoundPlugin";
879 };
880 
881 DwtMissingSoundPlugin._pluginMissing = true;
882 
883 DwtMissingSoundPlugin.prototype.addHelpListener =
884 function(listener) {
885 	this.addListener(DwtEvent.ONMOUSEDOWN, listener);
886 };
887