1 /**
  2  * The Render Engine
  3  * HTML5
  4  *
  5  * @fileoverview The HTML5 sound system.
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  * @author: $Author: bfattori $
  9  * @version: $Revision: 1555 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 // The class this file defines and its required classes
 34 R.Engine.define({
 35     "class":"R.sound.HTML5",
 36     "requires":[
 37         "R.sound.AbstractSoundSystem"
 38     ]
 39 });
 40 
 41 /**
 42  * @class Initializes the HTML5 sound system.  The sound types supported by the HTML5 sound system
 43  *    are determined by file extension on the URL:
 44  *    <ul>
 45  *       <li>mp3 - Audio MP3 format (Safari, Chrome, IE9)</li>
 46  *       <li>aac - Apple's AAC format (Safari)</li>
 47  *       <li>ogg - Ogg Vorbis format (Firefox, Chrome, Opera)</li>
 48  *       <li>pcm - WebM audio-only format (Firefox)</li>
 49  *       <li>wav - Wave audio format (Firefox, Safari, Opera, IE9)</li>
 50  *    </ul>
 51  *
 52  * @constructor
 53  * @extends R.sound.AbstractSoundSystem
 54  */
 55 R.sound.HTML5 = function () {
 56     return R.sound.AbstractSoundSystem.extend(/** @scope R.sound.HTML5.prototype */{
 57 
 58         supported:false,
 59         types:null,
 60         audioRoot:null,
 61 
 62         /** @private */
 63         constructor:function () {
 64             this.base();
 65 
 66             // Check for audio support on this browser before doing anything more
 67             if (!R.engine.Support.sysInfo().support.audio) {
 68                 this.supported = false;
 69                 return;
 70             }
 71 
 72             // Get an audio element we can use to determine if the type is supported
 73             this.audioRoot = new Audio("");
 74             this.supported = true;
 75 
 76             // Interrogate the supported audio types
 77             this.types = {
 78                 'mp3':this.verifyType("audio/mpeg"),
 79                 'aac':this.verifyType("audio/mp4; codecs='mp4a.40.2'"),
 80                 'ogg':this.verifyType("audio/ogg; codecs='vorbis'"),
 81                 'pcm':this.verifyType("audio/webm; codecs='vorbis'"),
 82                 'wav':this.verifyType("audio/x-wav; codecs='1'") || this.verifyType("audio/wave; codecs='1'") ||
 83                     this.verifyType("audio/wav; codecs='1'") || this.verifyType("audio/x-pn-wav; codecs='1'")
 84             };
 85         },
 86 
 87         /**
 88          * Verify if the given type can be played
 89          * @param mime
 90          */
 91         verifyType:function (mime) {
 92             return (this.audioRoot.canPlayType(mime) != "");
 93         },
 94 
 95         /**
 96          * Retrieve the sound from the network, when the sound system is ready, and create the sound object.
 97          * @param resourceLoader {R.resources.loades.SoundLoader} The sound resource loader
 98          * @param name {String} The name of the sound object
 99          * @param url {String} The URL of the sound to load
100          * @return {R.resources.types.Sound} The sound object
101          * @private
102          */
103         retrieveSound:function (resourceLoader, name, url) {
104             // First check to see if we've already got an audio element cached
105             var sound = this.base(resourceLoader, name, url);
106 
107             if (sound.getSoundObject() == null) {
108                 // Nope, this is a new sound object
109 
110                 // Check if the browser supports this sound format
111                 var ext = url.split("/");
112                 ext = ext[ext.length - 1].split(".")[1];
113                 if (!this.types[ext]) {
114                     // The sound type is not supported
115                     sound.setSupportedTypeFlag(false);
116                     return sound;
117                 }
118 
119                 // The type is supported, load the audio element and make sure it's not set to autoplay
120                 var audio = new Audio(url);
121                 audio.autoplay = false;
122                 audio.volume = 0.5;
123 
124                 // Store the sound object
125                 sound.setSoundObject(audio);
126             }
127 
128             return sound;
129         },
130 
131         /**
132          * Destroy the given sound object
133          * @param sound {R.resources.types.Sound} The sound object
134          */
135         destroySound:function (sound) {
136             if (!(this.supported || this.getSoundReadyState(sound))) {
137                 return;
138             }
139             sound.pause();
140             sound.src = "";
141         },
142 
143         /**
144          * Play the given sound object
145          * @param sound {R.resources.types.Sound} The sound object
146          */
147         playSound:function (sound) {
148             if (!(this.supported || this.getSoundReadyState(sound))) {
149                 return;
150             }
151             sound.play();
152         },
153 
154         /**
155          * Stop the given sound object
156          * @param sound {R.resources.types.Sound} The sound object
157          */
158         stopSound:function (sound) {
159             if (!(this.supported || this.getSoundReadyState(sound))) {
160                 return;
161             }
162             sound.pause();
163             sound.currentTime = 0;
164         },
165 
166         /**
167          * Pause the given sound object
168          * @param sound {R.resources.types.Sound} The sound object
169          */
170         pauseSound:function (sound) {
171             if (!(this.supported || this.getSoundReadyState(sound))) {
172                 return;
173             }
174             sound.pause();
175         },
176 
177         /**
178          * Resume the given sound object
179          * @param sound {R.resources.types.Sound} The sound object
180          */
181         resumeSound:function (sound) {
182             if (!(this.supported || this.getSoundReadyState(sound))) {
183                 return;
184             }
185             if (sound.paused) {
186                 sound.play();
187             }
188         },
189 
190         /**
191          * Mute the given sound object
192          * @param sound {R.resources.types.Sound} The sound object
193          */
194         muteSound:function (sound) {
195             if (!(this.supported || this.getSoundReadyState(sound))) {
196                 return;
197             }
198             sound.muted = true;
199         },
200 
201         /**
202          * Unmute the given sound object
203          * @param sound {R.resources.types.Sound} The sound object
204          */
205         unmuteSound:function (sound) {
206             if (!(this.supported || this.getSoundReadyState(sound))) {
207                 return;
208             }
209             sound.muted = false;
210         },
211 
212         /**
213          * Set the volume of the given sound object
214          * @param sound {R.resources.types.Sound} The sound object
215          * @param volume {Number} A value between 0 and 100, with 0 being muted
216          */
217         setSoundVolume:function (sound, volume) {
218             if (!(this.supported || this.getSoundReadyState(sound))) {
219                 return;
220             }
221             sound.volume = volume / 100;
222         },
223 
224         /**
225          * Pan the given sound object from left to right (unsupported in HTML5)
226          * @param sound {R.resources.types.Sound} The sound object
227          * @param pan {Number} A value between -100 and 100, with -100 being full left
228          *         and zero being center
229          */
230         setSoundPan:function (sound, pan) {
231             return;
232         },
233 
234         /**
235          * Set the position, within the sound's length, to play at
236          * @param sound {R.resources.types.Sound} The sound object
237          * @param millisecondOffset {Number} The millisecond offset from the start of
238          *         the sounds duration
239          */
240         setSoundPosition:function (sound, millisecondOffset) {
241             if (!(this.supported || this.getSoundReadyState(sound))) {
242                 return;
243             }
244             sound.currentTime = Math.floor(millisecondOffset / 1000);
245         },
246 
247         /**
248          * Get the position, in milliseconds, within a playing or paused sound
249          * @param sound {R.resources.types.Sound} The sound object
250          * @return {Number}
251          */
252         getSoundPosition:function (sound) {
253             if (!(this.supported || this.getSoundReadyState(sound))) {
254                 return 0;
255             }
256             return sound.currentTime * 1000;
257         },
258 
259         /**
260          * Get the size of the sound object, in bytes (unsupported in HTML5)
261          * @param sound {R.resources.types.Sound} The sound object
262          * @return {Number}
263          */
264         getSoundSize:function (sound) {
265             return 0;
266         },
267 
268         /**
269          * Get the length (duration) of the sound object, in milliseconds
270          * @param sound {R.resources.types.Sound} The sound object
271          * @return {Number}
272          */
273         getSoundDuration:function (sound) {
274             if (!(this.supported || this.getSoundReadyState(sound))) {
275                 return 0;
276             }
277             var d = sound.duration;
278             return (isNaN(d) ? 0 : d);
279         },
280 
281         /**
282          * Determine if the sound object is ready to be used
283          * @param sound {R.resources.types.Sound} The sound object
284          * @return {Boolean} <code>true</code> if the sound is ready
285          */
286         getSoundReadyState:function (sound) {
287             if (!this.supported) {
288                 return true;
289             }
290             if (!sound) {
291                 return false;
292             }
293             return (sound.readyState >= R.sound.HTML5.HAVE_ENOUGH_DATA);
294         }
295 
296     }, {
297         HAVE_NOTHING:0,
298         HAVE_METADATA:1,
299         HAVE_CURRENT_DATA:2,
300         HAVE_FUTURE_DATA:3,
301         HAVE_ENOUGH_DATA:4
302     });
303 
304 };
305