1 2 /* ***** BEGIN LICENSE BLOCK ***** 3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 * 5 * The contents of this file are subject to the Mozilla Public License Version 6 * 1.1 (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * http://www.mozilla.org/MPL/ 9 * 10 * Software distributed under the License is distributed on an "AS IS" basis, 11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 * for the specific language governing rights and limitations under the 13 * License. 14 * 15 * The Original Code is guacamole-common-js. 16 * 17 * The Initial Developer of the Original Code is 18 * Michael Jumper. 19 * Portions created by the Initial Developer are Copyright (C) 2010 20 * the Initial Developer. All Rights Reserved. 21 * 22 * Contributor(s): 23 * 24 * Alternatively, the contents of this file may be used under the terms of 25 * either the GNU General Public License Version 2 or later (the "GPL"), or 26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 * in which case the provisions of the GPL or the LGPL are applicable instead 28 * of those above. If you wish to allow use of your version of this file only 29 * under the terms of either the GPL or the LGPL, and not to allow others to 30 * use your version of this file under the terms of the MPL, indicate your 31 * decision by deleting the provisions above and replace them with the notice 32 * and other provisions required by the GPL or the LGPL. If you do not delete 33 * the provisions above, a recipient may use your version of this file under 34 * the terms of any one of the MPL, the GPL or the LGPL. 35 * 36 * ***** END LICENSE BLOCK ***** */ 37 38 /** 39 * Namespace for all Guacamole JavaScript objects. 40 * @namespace 41 */ 42 var Guacamole = Guacamole || {}; 43 44 /** 45 * Abstract audio channel which queues and plays arbitrary audio data. 46 * @constructor 47 */ 48 Guacamole.AudioChannel = function() { 49 50 /** 51 * Reference to this AudioChannel. 52 * @private 53 */ 54 var channel = this; 55 56 /** 57 * When the next packet should play. 58 * @private 59 */ 60 var next_packet_time = 0; 61 62 /** 63 * Queues up the given data for playing by this channel once all previously 64 * queued data has been played. If no data has been queued, the data will 65 * play immediately. 66 * 67 * @param {String} mimetype The mimetype of the data provided. 68 * @param {Number} duration The duration of the data provided, in 69 * milliseconds. 70 * @param {String} data The base64-encoded data to play. 71 */ 72 this.play = function(mimetype, duration, data) { 73 74 var packet = 75 new Guacamole.AudioChannel.Packet(mimetype, data); 76 77 var now = Guacamole.AudioChannel.getTimestamp(); 78 79 // If underflow is detected, reschedule new packets relative to now. 80 if (next_packet_time < now) 81 next_packet_time = now; 82 83 // Schedule next packet 84 packet.play(next_packet_time); 85 next_packet_time += duration; 86 87 }; 88 89 }; 90 91 // Define context if available 92 if (window.webkitAudioContext) { 93 Guacamole.AudioChannel.context = new webkitAudioContext(); 94 } 95 96 /** 97 * Returns a base timestamp which can be used for scheduling future audio 98 * playback. Scheduling playback for the value returned by this function plus 99 * N will cause the associated audio to be played back N milliseconds after 100 * the function is called. 101 * 102 * @return {Number} An arbitrary channel-relative timestamp, in milliseconds. 103 */ 104 Guacamole.AudioChannel.getTimestamp = function() { 105 106 // If we have an audio context, use its timestamp 107 if (Guacamole.AudioChannel.context) 108 return Guacamole.AudioChannel.context.currentTime * 1000; 109 110 // If we have high-resolution timers, use those 111 if (window.performance) { 112 113 if (window.performance.now) 114 return window.performance.now(); 115 116 if (window.performance.webkitNow) 117 return window.performance.webkitNow(); 118 119 } 120 121 // Fallback to millisecond-resolution system time 122 return new Date().getTime(); 123 124 }; 125 126 /** 127 * Abstract representation of an audio packet. 128 * 129 * @constructor 130 * 131 * @param {String} mimetype The mimetype of the data contained by this packet. 132 * @param {String} data The base64-encoded sound data contained by this packet. 133 */ 134 Guacamole.AudioChannel.Packet = function(mimetype, data) { 135 136 /** 137 * Schedules this packet for playback at the given time. 138 * 139 * @function 140 * @param {Number} when The time this packet should be played, in 141 * milliseconds. 142 */ 143 this.play = undefined; // Defined conditionally depending on support 144 145 // If audio API available, use it. 146 if (Guacamole.AudioChannel.context) { 147 148 var readyBuffer = null; 149 150 // By default, when decoding finishes, store buffer for future 151 // playback 152 var handleReady = function(buffer) { 153 readyBuffer = buffer; 154 }; 155 156 // Convert to ArrayBuffer 157 var binary = window.atob(data); 158 var arrayBuffer = new ArrayBuffer(binary.length); 159 var bufferView = new Uint8Array(arrayBuffer); 160 161 for (var i=0; i<binary.length; i++) 162 bufferView[i] = binary.charCodeAt(i); 163 164 // Get context and start decoding 165 Guacamole.AudioChannel.context.decodeAudioData( 166 arrayBuffer, 167 function(buffer) { handleReady(buffer); } 168 ); 169 170 // Set up buffer source 171 var source = Guacamole.AudioChannel.context.createBufferSource(); 172 source.connect(Guacamole.AudioChannel.context.destination); 173 174 var play_when; 175 176 function playDelayed(buffer) { 177 source.buffer = buffer; 178 source.noteOn(play_when / 1000); 179 } 180 181 /** @ignore */ 182 this.play = function(when) { 183 184 play_when = when; 185 186 // If buffer available, play it NOW 187 if (readyBuffer) 188 playDelayed(readyBuffer); 189 190 // Otherwise, play when decoded 191 else 192 handleReady = playDelayed; 193 194 }; 195 196 } 197 198 else { 199 200 // Build data URI 201 var data_uri = "data:" + mimetype + ";base64," + data; 202 203 // Create audio element to house and play the data 204 var audio = new Audio(); 205 audio.src = data_uri; 206 207 /** @ignore */ 208 this.play = function(when) { 209 210 // Calculate time until play 211 var now = Guacamole.AudioChannel.getTimestamp(); 212 var delay = when - now; 213 214 // Play now if too late 215 if (delay < 0) 216 audio.play(); 217 218 // Otherwise, schedule later playback 219 else 220 window.setTimeout(function() { 221 audio.play(); 222 }, delay); 223 224 }; 225 226 } 227 228 }; 229