1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2008, 2009, 2010, 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) 2008, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Alerts of an event by playing a sound.
 26  * @private
 27  */
 28 ZmSoundAlert = function() {
 29 	this.html5AudioEnabled = ZmSoundAlert.isHtml5AudioEnabled();
 30 	this.enabled = this.html5AudioEnabled || AjxPluginDetector.detectQuickTime() || AjxPluginDetector.detectWindowsMedia();
 31 	if (this.enabled) {
 32 		var element = this._element = document.createElement("DIV");
 33 		element.style.position = 'relative';
 34 		element.style.top = '-1000px';
 35 		element.style.left = '-1000px';
 36 		document.body.appendChild(this._element);
 37 	} else {
 38 		DBG.println("No QuickTime or Windows Media plugin detected. Sound alerts are disabled.")
 39 	}
 40 };
 41 
 42 //this tests for wav file support. we might want to refactor it for various file types if needed later
 43 ZmSoundAlert.isHtml5AudioEnabled =
 44 function() {
 45 
 46 	try {
 47 		var audio = new Audio("");
 48 		if (!audio.canPlayType) {
 49 			return false;
 50 		}
 51 	}
 52 	catch (e) {
 53 		return false;
 54 	}
 55 
 56 	// canPlayType(type) returns: "", "no", "maybe" or "probably"
 57 	var canPlayWav = audio.canPlayType("audio/wav");
 58 	return (canPlayWav !== "no" && canPlayWav !== "");
 59 
 60 }
 61 
 62 ZmSoundAlert.prototype.toString =
 63 function() {
 64 	return "ZmSoundAlert";
 65 };
 66 
 67 ZmSoundAlert.getInstance =
 68 function() {
 69 	return ZmSoundAlert.INSTANCE = ZmSoundAlert.INSTANCE || new ZmSoundAlert();
 70 };
 71 
 72 ZmSoundAlert.prototype.start =
 73 function(soundFile) {
 74 	if (!this.enabled) {
 75 		return;
 76 	}
 77 
 78 	var time = new Date().getTime();
 79 	if (this._lastTime && ((time - this._lastTime) < 5000)) {
 80 		return;
 81 	}
 82 	this._lastTime = time;
 83 
 84 	soundFile = soundFile || "/public/sounds/im/alert.wav";
 85 	var url = appContextPath + soundFile;
 86 
 87 	var html;
 88 	if (this.html5AudioEnabled) {
 89 		html = '<audio src="' + url + '" autoplay="yes"></audio>';
 90 	}
 91 	else {
 92 		var embedId = Dwt.getNextId();
 93 		var htmlArr = [
 94 			"<object CLASSID='CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6' type='audio/wav'>",
 95 			"<param name='url' value='", url, "'>",
 96 			"<param name='autostart' value='true'>",
 97 			"<param name='controller' value='true'>",
 98 			"<embed id='", embedId, "' src='", url, "' controller='false' autostart='true' type='audio/wav'/>",
 99 			"</object>"
100 		];
101 		html = htmlArr.join("");
102 	}
103 	this._element.innerHTML = html;
104 
105 	if (!this.html5AudioEnabled && AjxEnv.isFirefox && AjxEnv.isWindows) {
106 		// The quicktime plugin steals focus and breaks our keyboard nav.
107 		// The best workaround I've found for this is to blur the embed
108 		// element, and that only works after the sound plays.
109 		//
110 		// Unfortunately it seems that on a slow connection this prevents
111 		// the sound from playing. I'm hoping this is less bad than killing
112 		// keyboard focus.
113 		//
114 		// Mozilla bug: https://bugzilla.mozilla.org/show_bug.cgi?id=78414
115 		if (this._blurActionId) {
116 			AjxTimedAction.cancelAction(this._blurActionId);
117 			this._blurActionId = null;
118 		}
119 		this._blurEmbedTimer(embedId, 0);
120 	}
121 };
122 
123 ZmSoundAlert.prototype._blurEmbedTimer =
124 function(embedId, tries) {
125 	var action = new AjxTimedAction(this, this._blurEmbed, [embedId, tries]);
126 	this._blurActionId = AjxTimedAction.scheduleAction(action, 500);
127 };
128 
129 ZmSoundAlert.prototype._blurEmbed =
130 function(embedId, tries) {
131 	this._blurActionId = null;
132 
133 	// Take focus from the embed.
134 	var embedEl = document.getElementById(embedId);
135 	if (embedEl && embedEl.blur) {
136 		embedEl.blur();
137 	}
138 
139 	// Force focus to the keyboard manager's focus obj.
140 	var focusObj = appCtxt.getKeyboardMgr().getFocusObj();
141 	if (focusObj && focusObj.focus) {
142 		focusObj.focus();
143 	}
144 
145 	// Repeat hack.
146 	if (tries < 2) {
147 		this._blurEmbedTimer(embedId, tries + 1);
148 	}
149 };
150