├── README.md ├── bower.json ├── build ├── dist ├── fifer.js └── fifer.min.js ├── example ├── bang.mp3 ├── bang.ogg └── index.html ├── lib ├── fifer.fallback.swf └── fifer.js └── src ├── Fifer.as ├── javascript └── FiferInterface.as └── manager └── FiferManager.as /README.md: -------------------------------------------------------------------------------- 1 | # Fifer 2 | 3 | ## A lightweight conductor for the HTML5 Audio API with Flash Fallback. 4 | 5 | **Author:** *Joe Harlow* () 6 | 7 | --- 8 | 9 | `Fifer` provides a conductor for handling the use of the HTML5 `Audio API` in your application or game. Where the HTML5 `Audio API` is not available a lightweight (~4kb) Flash fallback is used. 10 | 11 | `Fifer` works best with `mp3`s as its main file type, while providing `ogg` fallbacks for Mozilla Firefox. 12 | 13 | ### Browser Support 14 | --- 15 | 16 | `Fifer` is built on the `HTML5 Audio API`, and will attempt to preload audio files. `Mobile Safari` will not allow the preloading of audio files, however, `Fifer` will degrade gracefully and will work to some extent. 17 | 18 | `Fifer` also provides a lightweight Flash fallback where the `Audio API` is not available. The Flash fallback requires at least `Flash Player 11.1`. 19 | 20 | `Fifer` will use the HTML5 `Audio API` in the following browsers: 21 | 22 | - Microsoft Internet Explorer 9+ 23 | - Mozilla Firefox 21.0+ 24 | - Google Chrome 27.0+ 25 | - Apple Safari 5.1+ 26 | - Opera 15.0+ 27 | 28 | `Fifer` will fallback to Flash in older browsers. 29 | 30 | 31 | ### Installation 32 | --- 33 | 34 | `Fifer` can be installed with `bower`, by running: 35 | 36 | `bower install fifer` 37 | 38 | ### Usage 39 | --- 40 | 41 | `Fifer` can be accessed using either `Fifer` or `fF`. From here on out, we will refer to it as `Fifer`. 42 | 43 | #### `Fifer`(`config /* Object */`) 44 | 45 | `Fifer` itself is a function object. Calling `Fifer`(`config`) will set the global variables and return itself to enable chaining of functionality. 46 | 47 | The `config` object can have the following properties: 48 | 49 | - #####`force` (`boolean`) 50 | Setting `force` to true will force the use of the Flash fallback in all instances. 51 | 52 | Default: `false`. 53 | - #####`swf` (`String`) 54 | The path that the Flash fallback is available from relative to your document root. 55 | 56 | Default: `../lib/fifer.fallback.swf`. 57 | 58 | ##### Example 59 | 60 | Fifer({ 61 | force: true, // force the use of the Flash fallback 62 | swf: 'fifer/fifer.fallback.swf' // set path to Flash fallback swf 63 | }); 64 | 65 | ### Methods (chainable) 66 | --- 67 | 68 | #### `loaded`(`fn /* Function */`) 69 | 70 | The `Function` passed into the `loaded` method will be fired whenever all files in the file stack are preloaded. The first argument passed into `fn` will be the file stack from `Fifer`, `Fifer` also will be passed into the `scope` of `fn`. 71 | 72 | ##### Example 73 | 74 | Fifer.loaded(function(files){ 75 | console.log(files); // logs out the file stack 76 | }); 77 | 78 | #### `registerAudio`(`name /* String */`,`path /* String */`[, `playMultiple /* boolean : false */`]) 79 | 80 | The `registerAudio` method is used to register an audio file with `Fifer`. Once a file is registered, it is immediately preloaded from the `path` argument. The `playMultiple` argument dictates whether a file can have multiple instances played at the same time. `playMultiple` is an optional argument and defaults to false. 81 | 82 | `Fifer` is extended with a `Function` called by the `name` argument where possible (ie. where the `name` does not conflict with a `Fifer` method). When called this `Function` will play the file. 83 | 84 | Calling `registerAudio` later in your application where the `loaded` `Function` has already been called will cause `loaded` to be called again. 85 | 86 | **N.B. To support Mozilla Firefox, `Fifer` requires both the standard `mp3` format files, along with `ogg` fallbacks. The files should be named exactly the same and in the same location. This fallback is automatic.** 87 | 88 | ##### Example 89 | 90 | Fifer 91 | .loaded(function(files){ 92 | console.log(files); // logs out the file stack 93 | this.bang(); // play the bang file once it's loaded 94 | }) 95 | .registerAudio('bang', 'bang.mp3', true); 96 | 97 | #### `play`(`name /* String */`[,`loop /* boolean : false */`, `ended /* Function */`]) 98 | 99 | The `play` method will play the audio file determined by it's name in the file stack. Providing a `loop` argument will allow you to set the file to loop indefinitely. The `ended` Function is an optional callback for when the audio file has finished playing. 100 | 101 | ##### Example 102 | 103 | Fifer 104 | /* The audio file mapped to 'bang' will be 105 | played and looped indefinitely */ 106 | .play('bang', true); 107 | 108 | The same can be achieved with the following: 109 | 110 | Fifer 111 | /* The audio file mapped to 'bang' will be 112 | played and looped indefinitely */ 113 | .bang(true); 114 | 115 | #### `stop`([`name /* String */`]) 116 | 117 | If a `name` argument is provided, the `stop` method will stop all playing instances of that audio file. 118 | 119 | If no `name` argument is provided, all currently playing audio will stop. 120 | 121 | ##### Example 122 | 123 | Fifer.stop('bang'); // will stop only playing instances of 'bang' 124 | Fifer.stop(); // will stop all playing audio 125 | 126 | 127 | #### `stopAll`() 128 | 129 | The `stopAll` method will stop all currently playing audio. 130 | 131 | ##### Example 132 | 133 | Fifer.stopAll(); // will stop all playing audio 134 | 135 | #### `mute`([`name /* String */`]) 136 | 137 | If a `name` argument is provided, the `mute` method will mute all playing instances of that audio file. 138 | 139 | If no `name` argument is provided, all audio in the file stack will be muted until unmuted. 140 | 141 | **If a file is called to play while it is registered as muted, it will play muted.** 142 | 143 | ##### Example 144 | 145 | Fifer.mute('bang'); // all instances of 'bang' will be muted until unmuted 146 | Fifer.mute(); // will mute all audio until unmuted 147 | 148 | #### `muteAll`() 149 | 150 | The `muteAll` method will mute all audio in the stack until unmuted. 151 | 152 | ##### Example 153 | 154 | Fifer.muteAll(); // will mute all audio until unmuted 155 | 156 | #### `unmute`([`name /* String */`]) 157 | 158 | If a `name` argument is provided, the `unmute` method will unmute all instances of that audio file. 159 | 160 | If no `name` argument is provided, all audio in the file stack will be unmuted. 161 | 162 | ##### Example 163 | 164 | Fifer.unmute('bang'); // all instances of 'bang' will be unmuted 165 | Fifer.unmute(); // will unmute all audio in the file stack 166 | 167 | #### `unmuteAll`() 168 | 169 | The `unmuteAll` method will unmute all audio in the stack. 170 | 171 | ##### Example 172 | 173 | Fifer.unmuteAll(); // will unmute all audio in the file stack 174 | 175 | #### `isPlaying`([`name /* String */`]) 176 | 177 | If a `name` argument is provided, it will check to see if a specific file is playing and return a boolean. 178 | 179 | If no `name` argument is provided, it will return a boolean showing if `Fifer` is currently playing *any* audio file. 180 | 181 | ##### Example 182 | 183 | Fifer.isPlaying('bang'); // will return true if the audio file registered as bang is playing 184 | 185 | #### `onAudioProcess`([`fn /* Function */`]) 186 | 187 | The `fn` argument will be called when audio is processed, either using `AudioContext` or Flash's native `computeSpectrum`. An array of values will be passed to Function containing Byte Frequency data from the audio stream. 188 | 189 | *Due to a [bug](http://stackoverflow.com/questions/13958158/why-arent-safari-or-firefox-able-to-process-audio-data-from-mediaelementsource) in Safari's handling of analyzing a MediaElementSource, Safari currently reports the array passed to the Function as empty (0) values* 190 | 191 | ##### Example 192 | 193 | Fifer.onAudioProcess(function(arr) { 194 | console.log(arr); 195 | }); 196 | 197 | #### `!dynamic!`([`loop /* boolean : false */`, `ended /* Function */`]) 198 | 199 | Once a file has been registered with `Fifer` and it has preloaded, `Fifer` will be extended with a `Function` named after that file. 200 | 201 | Calling this dynamic `Function` will play the file. It will also take a `loop` parameter to allow indefinite looping of the file and an `ended` callback for when the audio file has finished playing. 202 | 203 | ##### Example 204 | 205 | Fifer.loaded(function(files){ 206 | /* Fifer is extended with a Function called 'aReallyLongNameThingimajig' */ 207 | this.aReallyLongNameThingimajig(true); // play the 'aReallyLongNameThingimajig' file and loop indefinitely 208 | }) 209 | .registerAudio('aReallyLongNameThingimajig', 'test.mp3', true); 210 | 211 | ### License 212 | --- 213 | 214 | Copyright (C) 2013 Joe Harlow (Fourth of 5 Limited) 215 | 216 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 217 | 218 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 219 | 220 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fifer", 3 | "version": "1.0.0", 4 | "main": [ 5 | "lib/fifer.js" 6 | ], 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "components" 11 | ], 12 | "dependencies": {} 13 | } -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp -rf lib/fifer.js dist/fifer.js 4 | uglifyjs dist/fifer.js -o dist/fifer.min.js --comments '@cc_on!@' 5 | gzip -c dist/fifer.min.js | wc -c 6 | 7 | echo '[Fifer] Build complete.' -------------------------------------------------------------------------------- /dist/fifer.js: -------------------------------------------------------------------------------- 1 | (function(w,d) { 2 | 3 | 'use strict'; 4 | 5 | var _ = {}, 6 | _files = {}, 7 | _playing = {}, 8 | _muted = false, 9 | _initialised = true, 10 | _queue = [], 11 | _process = function() {}, 12 | _loaded = function() {}, 13 | _settings = { 14 | swf : '../lib/fifer.fallback.swf', 15 | force : false, 16 | forceFlashOnNoAudioContext : false 17 | }, 18 | _scope, _internals; 19 | 20 | _.support = (function() { 21 | return 'Audio' in w; 22 | })(); 23 | 24 | _.context = w.AudioContext || w.webAudioContext || w.webkitAudioContext; 25 | if (_.context) { 26 | _.context = new _.context(); 27 | } 28 | 29 | _.warn = (function() { 30 | if ('console' in w && 'warn' in w.console) { 31 | return function wrn() { 32 | console.warn(Array.prototype.slice.call(arguments)[0]); 33 | }; 34 | } else { 35 | return function wrn() {}; 36 | } 37 | })(); 38 | 39 | _.loop = (function() { 40 | if (!_.support) return; 41 | if (typeof new Audio().loop === 'boolean') { 42 | return function(a) { 43 | a.loop = true; 44 | }; 45 | } else { 46 | return function(a) { 47 | a.addEventListener('ended', function() { 48 | this.currentTime = 0; 49 | this.play(); 50 | }, false); 51 | }; 52 | } 53 | })(); 54 | 55 | _.mozillaFirefox = (function() { 56 | return _.support && new Audio().canPlayType('audio/mpeg') === ''; 57 | })(); 58 | 59 | _.extend = function(o,e) 60 | { 61 | for (var p in e) { 62 | o[p] = (o.hasOwnProperty(p)) ? o[p] : e[p]; 63 | } 64 | return o; 65 | }; 66 | 67 | _.fallback = function() { 68 | 69 | var fl; 70 | var xhr = new XMLHttpRequest(); 71 | xhr.onreadystatechange = function() { 72 | if (xhr.readyState === 4 && xhr.status === 404) { 73 | _.warn('[Fifer] Fifer was unable to load the swf for the flash fallback, you can customise where it is using Fifer({ swf : *_file_path_* })'); 74 | } else if (xhr.readyState === 4) { 75 | fl = embed(); 76 | } 77 | }; 78 | xhr.open('HEAD', _settings.swf, false); 79 | xhr.send(); 80 | 81 | function embed() { 82 | var msie = /*@cc_on!@*/0; 83 | 84 | if (msie) { 85 | var html = ''; 86 | html += ' '; 87 | html += ' '; 88 | html += ' '; 89 | html += ' '; 90 | html += ' '; 91 | html += ' '; 92 | 93 | var p = d.createElement('p'); 94 | d.getElementsByTagName('body')[0].appendChild(p); 95 | p.innerHTML = html; 96 | return d.getElementById('fifer-flash'); 97 | } else { 98 | var _flash = d.createElement('object'); 99 | _flash.setAttribute('type', 'application/x-shockwave-flash'); 100 | _flash.setAttribute('data', _settings.swf); 101 | _flash.setAttribute('width', '1'); 102 | _flash.setAttribute('height', '1'); 103 | 104 | var params = { 105 | 'movie' : _settings.swf, 106 | 'quality' : 'high', 107 | 'bgcolor' : '#ffffff', 108 | 'allowScriptAccess' : 'always', 109 | 'allowFullScreen' : 'true' 110 | }; 111 | 112 | for (var o in params) { 113 | var _p = d.createElement('param'); 114 | _p.setAttribute('name', o); 115 | _p.setAttribute('value', params[o]); 116 | _flash.appendChild(_p); 117 | } 118 | 119 | d.getElementsByTagName('body')[0].appendChild(_flash); 120 | return _flash; 121 | } 122 | } 123 | 124 | return fl; 125 | }; 126 | 127 | _internals = { 128 | registerAudio : function($name, $src) { 129 | var a = new Audio(); 130 | a.addEventListener('canplaythrough', function comp() { 131 | a.removeEventListener('canplaythrough', comp); 132 | _internals.loaded($name); 133 | }); 134 | if (_.mozillaFirefox) { 135 | _.warn('[Fifer] Looks like you\'re trying this in Firefox, trying to find an .ogg file.'); 136 | $src = $src.split('.mp3').join('.ogg'); 137 | _files[$name].src = $src; 138 | } 139 | a.src = $src; 140 | a.volume = 0; 141 | a.play(); 142 | d.body.appendChild(a); 143 | setTimeout(function() { a.pause(); }, 10); 144 | }, 145 | playAudio : function($name, $loop, $ended) { 146 | $loop = $loop || false; 147 | $ended = $ended || function() {}; 148 | var id = $name + Math.random() * new Date().getTime(); 149 | var a = new Audio(); 150 | var analyser, node, source; 151 | a.addEventListener('canplaythrough', function comp() { 152 | a.removeEventListener('canplaythrough', comp); 153 | if (_.context) { 154 | node = _.context.createScriptProcessor(2048, 1, 1); 155 | node.connect(_.context.destination); 156 | analyser = _.context.createAnalyser(); 157 | analyser.smoothingTimeConstant = 0.3; 158 | analyser.fftSize = 1024; 159 | source = _.context.createMediaElementSource(a); 160 | source.connect(analyser); 161 | analyser.connect(node); 162 | source.connect(_.context.destination); 163 | node.onaudioprocess = function() { 164 | var array = new Uint8Array(analyser.frequencyBinCount); 165 | analyser.getByteFrequencyData(array); 166 | _process.call(fF, array); 167 | }; 168 | } 169 | }); 170 | 171 | a.src = _files[$name].src; 172 | if ($loop) { 173 | _.loop(a); 174 | } else { 175 | a.addEventListener('ended', function end() { 176 | a.removeEventListener('ended', end); 177 | if (node) node.onaudioprocess = undefined; 178 | $ended.call(fF, _files[$name]); 179 | _internals.ended(id, $name); 180 | }); 181 | } 182 | _files[$name].playing = _files[$name].playing || []; 183 | _files[$name].playing.push(id); 184 | if (_files[$name].muted) { 185 | a.volume = 0; 186 | } 187 | _playing[id] = a; 188 | _playing[id].play(); 189 | }, 190 | stopAudio : function($name) { 191 | while (_files[$name].playing.length) { 192 | var p = _files[$name].playing.shift(); 193 | _playing[p].pause(); 194 | _playing[p].currentTime = 0; 195 | delete _playing[p]; 196 | } 197 | }, 198 | stopAll : function() { 199 | for (var a in _playing) { 200 | _playing[a].pause(); 201 | _playing[a].currentTime = 0; 202 | } 203 | _playing = {}; 204 | }, 205 | mute : function($name) { 206 | if (_files[$name].playing.length) { 207 | for (var i = 0, j = _files[$name].playing.length; i < j; i++) { 208 | var p = _files[$name].playing[i]; 209 | _playing[p].volume = 0; 210 | } 211 | } 212 | 213 | if ($name in _files) { 214 | _files[$name].muted = true; 215 | } 216 | }, 217 | unmute : function($name) { 218 | if (_files[$name].playing.length) { 219 | for (var i = 0, j = _files[$name].playing.length; i < j; i++) { 220 | var p = _files[$name].playing[i]; 221 | _playing[p].volume = 0; 222 | } 223 | } 224 | 225 | if ($name in _files) { 226 | _files[$name].muted = false; 227 | } 228 | }, 229 | muteAll : function() { 230 | for (var p in _playing) { 231 | _playing[p].volume = 0; 232 | } 233 | 234 | for (var f in _files) { 235 | _files[f].muted = true; 236 | } 237 | _muted = true; 238 | }, 239 | unmuteAll : function() { 240 | for (var p in _playing) { 241 | _playing[p].volume = 1; 242 | } 243 | 244 | for (var f in _files) { 245 | _files[f].muted = false; 246 | } 247 | _muted = false; 248 | }, 249 | muted : function() { 250 | return _muted; 251 | }, 252 | loaded : function($name) { 253 | _files[$name].loaded = true; 254 | if (fF[$name]() === 'ff-reserved') { 255 | fF[$name] = function($loop, $ended) { 256 | fF.play($name, $loop, $ended); 257 | return fF; 258 | }; 259 | } 260 | for (var f in _files) { 261 | if (!_files[f].loaded) return; 262 | } 263 | _loaded.call(fF, _files); 264 | }, 265 | ended : function($id,$name) { 266 | if ($id in _playing) { 267 | _files[$name].playing.shift(); 268 | if (_files[$name].ended && $id in _files[$name].ended) _files[$name].ended[$id].call(fF, _files[$name]); 269 | delete _playing[$id]; 270 | } 271 | }, 272 | clearPlaying : function($name) { 273 | if (!_.support || _settings.force) { 274 | if (typeof $name === 'undefined') { 275 | for (var f in _files) { 276 | _files[f].playing = []; 277 | } 278 | } else { 279 | _files[$name].playing = []; 280 | } 281 | } 282 | } 283 | }; 284 | 285 | var fF = function(obj) { 286 | _settings = _.extend(obj, _settings); 287 | _.initialise(); 288 | return fF; 289 | }; 290 | 291 | fF.registerAudio = function($name, $src, $multiple) { 292 | if (!_initialised) { 293 | _queue.push(['registerAudio', $name, $src, $multiple]); 294 | return fF; 295 | } 296 | _files[$name] = { src : $src, loaded : false, multiple : ($multiple || false), playing : [] }; 297 | fF[$name] = fF[$name] || function() { return 'ff-reserved'; }; 298 | _scope.registerAudio($name, $src); 299 | return fF; 300 | }; 301 | 302 | fF.play = function($name, $loop, $ended) { 303 | $loop = $loop || false; 304 | $ended = $ended || function() {}; 305 | if (typeof $name === 'undefined') return; 306 | if (_files[$name].playing.length && !_files[$name].multiple) { 307 | _.warn('[Fifer] Audio: ' + $name + ' is already playing.'); 308 | return fF; 309 | } 310 | if (!_files[$name].loaded) return; 311 | if (!($name in _files)) { 312 | _.warn('[Fifer] No audio registered with the name: ' + $name); 313 | return; 314 | } 315 | var p = _scope.playAudio($name, $loop, $ended); 316 | if (p) { 317 | _files[$name].playing = p.playing; 318 | _playing[p.id] = _scope; 319 | if ($ended) { 320 | _files[$name].ended = _files[$name].ended || {}; 321 | _files[$name].ended[p.id] = $ended; 322 | } 323 | } 324 | return fF; 325 | }; 326 | 327 | fF.stop = function($name) { 328 | if (typeof $name === 'undefined') { 329 | _internals.clearPlaying(); 330 | _scope.stopAll(); 331 | } else { 332 | if (!($name in _files)) { 333 | _.warn('[Fifer] No audio registered with the name: ' + $name); 334 | return fF; 335 | } 336 | if (!_files[$name].playing.length) { 337 | _.warn('[Fifer] The audio: ' + $name + ' is not currently playing.'); 338 | return fF; 339 | } 340 | _internals.clearPlaying($name); 341 | _scope.stopAudio($name); 342 | } 343 | return fF; 344 | }; 345 | 346 | fF.stopAll = function() { 347 | _internals.clearPlaying(); 348 | _scope.stopAll(); 349 | return fF; 350 | }; 351 | 352 | fF.mute = function($name) { 353 | if (typeof $name === 'undefined') { 354 | _scope.muteAll(); 355 | } else { 356 | if (!($name in _files)) { 357 | _.warn('[Fifer] No audio registered with the name: ' + $name); 358 | return; 359 | } 360 | _scope.mute($name); 361 | } 362 | return fF; 363 | }; 364 | 365 | fF.unmute = function($name) { 366 | if (typeof $name === 'undefined') { 367 | _scope.unmuteAll(); 368 | } else { 369 | if (!($name in _files)) { 370 | _.warn('[Fifer] No audio registered with the name: ' + $name); 371 | return; 372 | } 373 | _scope.unmute($name); 374 | } 375 | return fF; 376 | }; 377 | 378 | fF.muteAll = function() { 379 | _scope.muteAll(); 380 | return fF; 381 | }; 382 | 383 | fF.unmuteAll = function() { 384 | _scope.unmuteAll(); 385 | return fF; 386 | }; 387 | 388 | fF.muted = function() { 389 | return _scope.muted(); 390 | }; 391 | 392 | fF.isPlaying = function($name) { 393 | var count = 0; 394 | for (var id in _playing) { 395 | if (_playing.hasOwnProperty(id)) { 396 | if ($name && id.indexOf($name) !== -1) return true; 397 | count++; 398 | } 399 | } 400 | return ($name) ? false : count > 0 ? true : false; 401 | }; 402 | 403 | fF.loaded = function(fn) { 404 | _loaded = fn; 405 | return fF; 406 | }; 407 | 408 | fF.onAudioProcess = function(fn) { 409 | if (!_.support || _settings.force || _.context) { 410 | _process = fn; 411 | } else { 412 | _.warn('[Fifer] Fifer currently only supports audio processing while using the Flash fallback.'); 413 | } 414 | return fF; 415 | }; 416 | 417 | _.initialise = function() { 418 | if (!_.support || _settings.force || !_.context && _settings.forceFlashOnNoAudioContext) { 419 | _initialised = false; 420 | fF.interface = { 421 | responseInitialised : function() { 422 | _.warn('[Fifer] Fifer is using a Flash Fallback as HTML5 Audio is not supported.'); 423 | _initialised = true; 424 | while (_queue.length) { 425 | var q = _queue.shift(); 426 | var m = q.shift(); 427 | fF[m].apply(fF, q); 428 | } 429 | }, 430 | responseLoaded : function($name) { 431 | _internals.loaded($name); 432 | }, 433 | responseCompleted : function($arr) { 434 | _internals.ended($arr[0], $arr[1]); 435 | }, 436 | responseSpectrum : function($arr) { 437 | _process.call(fF, $arr); 438 | } 439 | }; 440 | _scope = _.fallback(); 441 | } else { 442 | _scope = _internals; 443 | } 444 | }; 445 | 446 | _.initialise(); 447 | 448 | w.Fifer = w.fF = fF; 449 | 450 | })(window,document); -------------------------------------------------------------------------------- /dist/fifer.min.js: -------------------------------------------------------------------------------- 1 | !function(w,d){"use strict";var _={},_files={},_playing={},_muted=false,_initialised=true,_queue=[],_process=function(){},_loaded=function(){},_settings={swf:"../lib/fifer.fallback.swf",force:false,forceFlashOnNoAudioContext:false},_scope,_internals;_.support=function(){return"Audio"in w}();_.context=w.AudioContext||w.webAudioContext||w.webkitAudioContext;if(_.context){_.context=new _.context}_.warn=function(){if("console"in w&&"warn"in w.console){return function wrn(){console.warn(Array.prototype.slice.call(arguments)[0])}}else{return function wrn(){}}}();_.loop=function(){if(!_.support)return;if(typeof(new Audio).loop==="boolean"){return function(a){a.loop=true}}else{return function(a){a.addEventListener("ended",function(){this.currentTime=0;this.play()},false)}}}();_.mozillaFirefox=function(){return _.support&&(new Audio).canPlayType("audio/mpeg")===""}();_.extend=function(o,e){for(var p in e){o[p]=o.hasOwnProperty(p)?o[p]:e[p]}return o};_.fallback=function(){var fl;var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState===4&&xhr.status===404){_.warn("[Fifer] Fifer was unable to load the swf for the flash fallback, you can customise where it is using Fifer({ swf : *_file_path_* })")}else if(xhr.readyState===4){fl=embed()}};xhr.open("HEAD",_settings.swf,false);xhr.send();function embed(){var msie=/*@cc_on!@*/0;if(msie){var html='';html+=' ';html+=' ';html+=' ';html+=' ';html+=" ";html+=" ";var p=d.createElement("p");d.getElementsByTagName("body")[0].appendChild(p);p.innerHTML=html;return d.getElementById("fifer-flash")}else{var _flash=d.createElement("object");_flash.setAttribute("type","application/x-shockwave-flash");_flash.setAttribute("data",_settings.swf);_flash.setAttribute("width","1");_flash.setAttribute("height","1");var params={movie:_settings.swf,quality:"high",bgcolor:"#ffffff",allowScriptAccess:"always",allowFullScreen:"true"};for(var o in params){var _p=d.createElement("param");_p.setAttribute("name",o);_p.setAttribute("value",params[o]);_flash.appendChild(_p)}d.getElementsByTagName("body")[0].appendChild(_flash);return _flash}}return fl};_internals={registerAudio:function($name,$src){var a=new Audio;a.addEventListener("canplaythrough",function comp(){a.removeEventListener("canplaythrough",comp);_internals.loaded($name)});if(_.mozillaFirefox){_.warn("[Fifer] Looks like you're trying this in Firefox, trying to find an .ogg file.");$src=$src.split(".mp3").join(".ogg");_files[$name].src=$src}a.src=$src;a.volume=0;a.play();d.body.appendChild(a);setTimeout(function(){a.pause()},10)},playAudio:function($name,$loop,$ended){$loop=$loop||false;$ended=$ended||function(){};var id=$name+Math.random()*(new Date).getTime();var a=new Audio;var analyser,node,source;a.addEventListener("canplaythrough",function comp(){a.removeEventListener("canplaythrough",comp);if(_.context){node=_.context.createScriptProcessor(2048,1,1);node.connect(_.context.destination);analyser=_.context.createAnalyser();analyser.smoothingTimeConstant=.3;analyser.fftSize=1024;source=_.context.createMediaElementSource(a);source.connect(analyser);analyser.connect(node);source.connect(_.context.destination);node.onaudioprocess=function(){var array=new Uint8Array(analyser.frequencyBinCount);analyser.getByteFrequencyData(array);_process.call(fF,array)}}});a.src=_files[$name].src;if($loop){_.loop(a)}else{a.addEventListener("ended",function end(){a.removeEventListener("ended",end);if(node)node.onaudioprocess=undefined;$ended.call(fF,_files[$name]);_internals.ended(id,$name)})}_files[$name].playing=_files[$name].playing||[];_files[$name].playing.push(id);if(_files[$name].muted){a.volume=0}_playing[id]=a;_playing[id].play()},stopAudio:function($name){while(_files[$name].playing.length){var p=_files[$name].playing.shift();_playing[p].pause();_playing[p].currentTime=0;delete _playing[p]}},stopAll:function(){for(var a in _playing){_playing[a].pause();_playing[a].currentTime=0}_playing={}},mute:function($name){if(_files[$name].playing.length){for(var i=0,j=_files[$name].playing.length;i0?true:false};fF.loaded=function(fn){_loaded=fn;return fF};fF.onAudioProcess=function(fn){if(!_.support||_settings.force||_.context){_process=fn}else{_.warn("[Fifer] Fifer currently only supports audio processing while using the Flash fallback.")}return fF};_.initialise=function(){if(!_.support||_settings.force||!_.context&&_settings.forceFlashOnNoAudioContext){_initialised=false;fF.interface={responseInitialised:function(){_.warn("[Fifer] Fifer is using a Flash Fallback as HTML5 Audio is not supported.");_initialised=true;while(_queue.length){var q=_queue.shift();var m=q.shift();fF[m].apply(fF,q)}},responseLoaded:function($name){_internals.loaded($name)},responseCompleted:function($arr){_internals.ended($arr[0],$arr[1])},responseSpectrum:function($arr){_process.call(fF,$arr)}};_scope=_.fallback()}else{_scope=_internals}};_.initialise();w.Fifer=w.fF=fF}(window,document); -------------------------------------------------------------------------------- /example/bang.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5io/fifer-js/9111164814e3d4ed9ae950cb8e6f89e743744873/example/bang.mp3 -------------------------------------------------------------------------------- /example/bang.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5io/fifer-js/9111164814e3d4ed9ae950cb8e6f89e743744873/example/bang.ogg -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fifer Demo 5 | 6 | 7 | 8 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/fifer.fallback.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f5io/fifer-js/9111164814e3d4ed9ae950cb8e6f89e743744873/lib/fifer.fallback.swf -------------------------------------------------------------------------------- /lib/fifer.js: -------------------------------------------------------------------------------- 1 | (function(w,d) { 2 | 3 | 'use strict'; 4 | 5 | var _ = {}, 6 | _files = {}, 7 | _playing = {}, 8 | _muted = false, 9 | _initialised = true, 10 | _queue = [], 11 | _process = function() {}, 12 | _loaded = function() {}, 13 | _settings = { 14 | swf : '../lib/fifer.fallback.swf', 15 | force : false, 16 | forceFlashOnNoAudioContext : false 17 | }, 18 | _scope, _internals; 19 | 20 | _.support = (function() { 21 | return 'Audio' in w; 22 | })(); 23 | 24 | _.context = w.AudioContext || w.webAudioContext || w.webkitAudioContext; 25 | if (_.context) { 26 | _.context = new _.context(); 27 | } 28 | 29 | _.warn = (function() { 30 | if ('console' in w && 'warn' in w.console) { 31 | return function wrn() { 32 | console.warn(Array.prototype.slice.call(arguments)[0]); 33 | }; 34 | } else { 35 | return function wrn() {}; 36 | } 37 | })(); 38 | 39 | _.loop = (function() { 40 | if (!_.support) return; 41 | if (typeof new Audio().loop === 'boolean') { 42 | return function(a) { 43 | a.loop = true; 44 | }; 45 | } else { 46 | return function(a) { 47 | a.addEventListener('ended', function() { 48 | this.currentTime = 0; 49 | this.play(); 50 | }, false); 51 | }; 52 | } 53 | })(); 54 | 55 | _.mozillaFirefox = (function() { 56 | return _.support && new Audio().canPlayType('audio/mpeg') === ''; 57 | })(); 58 | 59 | _.extend = function(o,e) 60 | { 61 | for (var p in e) { 62 | o[p] = (o.hasOwnProperty(p)) ? o[p] : e[p]; 63 | } 64 | return o; 65 | }; 66 | 67 | _.fallback = function() { 68 | 69 | var fl; 70 | var xhr = new XMLHttpRequest(); 71 | xhr.onreadystatechange = function() { 72 | if (xhr.readyState === 4 && xhr.status === 404) { 73 | _.warn('[Fifer] Fifer was unable to load the swf for the flash fallback, you can customise where it is using Fifer({ swf : *_file_path_* })'); 74 | } else if (xhr.readyState === 4) { 75 | fl = embed(); 76 | } 77 | }; 78 | xhr.open('HEAD', _settings.swf, false); 79 | xhr.send(); 80 | 81 | function embed() { 82 | var msie = /*@cc_on!@*/0; 83 | 84 | if (msie) { 85 | var html = ''; 86 | html += ' '; 87 | html += ' '; 88 | html += ' '; 89 | html += ' '; 90 | html += ' '; 91 | html += ' '; 92 | 93 | var p = d.createElement('p'); 94 | d.getElementsByTagName('body')[0].appendChild(p); 95 | p.innerHTML = html; 96 | return d.getElementById('fifer-flash'); 97 | } else { 98 | var _flash = d.createElement('object'); 99 | _flash.setAttribute('type', 'application/x-shockwave-flash'); 100 | _flash.setAttribute('data', _settings.swf); 101 | _flash.setAttribute('width', '1'); 102 | _flash.setAttribute('height', '1'); 103 | 104 | var params = { 105 | 'movie' : _settings.swf, 106 | 'quality' : 'high', 107 | 'bgcolor' : '#ffffff', 108 | 'allowScriptAccess' : 'always', 109 | 'allowFullScreen' : 'true' 110 | }; 111 | 112 | for (var o in params) { 113 | var _p = d.createElement('param'); 114 | _p.setAttribute('name', o); 115 | _p.setAttribute('value', params[o]); 116 | _flash.appendChild(_p); 117 | } 118 | 119 | d.getElementsByTagName('body')[0].appendChild(_flash); 120 | return _flash; 121 | } 122 | } 123 | 124 | return fl; 125 | }; 126 | 127 | _internals = { 128 | registerAudio : function($name, $src) { 129 | var a = new Audio(); 130 | a.addEventListener('canplaythrough', function comp() { 131 | a.removeEventListener('canplaythrough', comp); 132 | _internals.loaded($name); 133 | }); 134 | if (_.mozillaFirefox) { 135 | _.warn('[Fifer] Looks like you\'re trying this in Firefox, trying to find an .ogg file.'); 136 | $src = $src.split('.mp3').join('.ogg'); 137 | _files[$name].src = $src; 138 | } 139 | a.src = $src; 140 | a.volume = 0; 141 | a.play(); 142 | d.body.appendChild(a); 143 | setTimeout(function() { a.pause(); }, 10); 144 | }, 145 | playAudio : function($name, $loop, $ended) { 146 | $loop = $loop || false; 147 | $ended = $ended || function() {}; 148 | var id = $name + Math.random() * new Date().getTime(); 149 | var a = new Audio(); 150 | var analyser, node, source; 151 | a.addEventListener('canplaythrough', function comp() { 152 | a.removeEventListener('canplaythrough', comp); 153 | if (_.context) { 154 | node = _.context.createScriptProcessor(2048, 1, 1); 155 | node.connect(_.context.destination); 156 | analyser = _.context.createAnalyser(); 157 | analyser.smoothingTimeConstant = 0.3; 158 | analyser.fftSize = 1024; 159 | source = _.context.createMediaElementSource(a); 160 | source.connect(analyser); 161 | analyser.connect(node); 162 | source.connect(_.context.destination); 163 | node.onaudioprocess = function() { 164 | var array = new Uint8Array(analyser.frequencyBinCount); 165 | analyser.getByteFrequencyData(array); 166 | _process.call(fF, array); 167 | }; 168 | } 169 | }); 170 | 171 | a.src = _files[$name].src; 172 | if ($loop) { 173 | _.loop(a); 174 | } else { 175 | a.addEventListener('ended', function end() { 176 | a.removeEventListener('ended', end); 177 | if (node) node.onaudioprocess = undefined; 178 | $ended.call(fF, _files[$name]); 179 | _internals.ended(id, $name); 180 | }); 181 | } 182 | _files[$name].playing = _files[$name].playing || []; 183 | _files[$name].playing.push(id); 184 | if (_files[$name].muted) { 185 | a.volume = 0; 186 | } 187 | _playing[id] = a; 188 | _playing[id].play(); 189 | }, 190 | stopAudio : function($name) { 191 | while (_files[$name].playing.length) { 192 | var p = _files[$name].playing.shift(); 193 | _playing[p].pause(); 194 | _playing[p].currentTime = 0; 195 | delete _playing[p]; 196 | } 197 | }, 198 | stopAll : function() { 199 | for (var a in _playing) { 200 | _playing[a].pause(); 201 | _playing[a].currentTime = 0; 202 | } 203 | _playing = {}; 204 | }, 205 | mute : function($name) { 206 | if (_files[$name].playing.length) { 207 | for (var i = 0, j = _files[$name].playing.length; i < j; i++) { 208 | var p = _files[$name].playing[i]; 209 | _playing[p].volume = 0; 210 | } 211 | } 212 | 213 | if ($name in _files) { 214 | _files[$name].muted = true; 215 | } 216 | }, 217 | unmute : function($name) { 218 | if (_files[$name].playing.length) { 219 | for (var i = 0, j = _files[$name].playing.length; i < j; i++) { 220 | var p = _files[$name].playing[i]; 221 | _playing[p].volume = 0; 222 | } 223 | } 224 | 225 | if ($name in _files) { 226 | _files[$name].muted = false; 227 | } 228 | }, 229 | muteAll : function() { 230 | for (var p in _playing) { 231 | _playing[p].volume = 0; 232 | } 233 | 234 | for (var f in _files) { 235 | _files[f].muted = true; 236 | } 237 | _muted = true; 238 | }, 239 | unmuteAll : function() { 240 | for (var p in _playing) { 241 | _playing[p].volume = 1; 242 | } 243 | 244 | for (var f in _files) { 245 | _files[f].muted = false; 246 | } 247 | _muted = false; 248 | }, 249 | muted : function() { 250 | return _muted; 251 | }, 252 | loaded : function($name) { 253 | _files[$name].loaded = true; 254 | if (fF[$name]() === 'ff-reserved') { 255 | fF[$name] = function($loop, $ended) { 256 | fF.play($name, $loop, $ended); 257 | return fF; 258 | }; 259 | } 260 | for (var f in _files) { 261 | if (!_files[f].loaded) return; 262 | } 263 | _loaded.call(fF, _files); 264 | }, 265 | ended : function($id,$name) { 266 | if ($id in _playing) { 267 | _files[$name].playing.shift(); 268 | if (_files[$name].ended && $id in _files[$name].ended) _files[$name].ended[$id].call(fF, _files[$name]); 269 | delete _playing[$id]; 270 | } 271 | }, 272 | clearPlaying : function($name) { 273 | if (!_.support || _settings.force) { 274 | if (typeof $name === 'undefined') { 275 | for (var f in _files) { 276 | _files[f].playing = []; 277 | } 278 | } else { 279 | _files[$name].playing = []; 280 | } 281 | } 282 | } 283 | }; 284 | 285 | var fF = function(obj) { 286 | _settings = _.extend(obj, _settings); 287 | _.initialise(); 288 | return fF; 289 | }; 290 | 291 | fF.registerAudio = function($name, $src, $multiple) { 292 | if (!_initialised) { 293 | _queue.push(['registerAudio', $name, $src, $multiple]); 294 | return fF; 295 | } 296 | _files[$name] = { src : $src, loaded : false, multiple : ($multiple || false), playing : [] }; 297 | fF[$name] = fF[$name] || function() { return 'ff-reserved'; }; 298 | _scope.registerAudio($name, $src); 299 | return fF; 300 | }; 301 | 302 | fF.play = function($name, $loop, $ended) { 303 | $loop = $loop || false; 304 | $ended = $ended || function() {}; 305 | if (typeof $name === 'undefined') return; 306 | if (_files[$name].playing.length && !_files[$name].multiple) { 307 | _.warn('[Fifer] Audio: ' + $name + ' is already playing.'); 308 | return fF; 309 | } 310 | if (!_files[$name].loaded) return; 311 | if (!($name in _files)) { 312 | _.warn('[Fifer] No audio registered with the name: ' + $name); 313 | return; 314 | } 315 | var p = _scope.playAudio($name, $loop, $ended); 316 | if (p) { 317 | _files[$name].playing = p.playing; 318 | _playing[p.id] = _scope; 319 | if ($ended) { 320 | _files[$name].ended = _files[$name].ended || {}; 321 | _files[$name].ended[p.id] = $ended; 322 | } 323 | } 324 | return fF; 325 | }; 326 | 327 | fF.stop = function($name) { 328 | if (typeof $name === 'undefined') { 329 | _internals.clearPlaying(); 330 | _scope.stopAll(); 331 | } else { 332 | if (!($name in _files)) { 333 | _.warn('[Fifer] No audio registered with the name: ' + $name); 334 | return fF; 335 | } 336 | if (!_files[$name].playing.length) { 337 | _.warn('[Fifer] The audio: ' + $name + ' is not currently playing.'); 338 | return fF; 339 | } 340 | _internals.clearPlaying($name); 341 | _scope.stopAudio($name); 342 | } 343 | return fF; 344 | }; 345 | 346 | fF.stopAll = function() { 347 | _internals.clearPlaying(); 348 | _scope.stopAll(); 349 | return fF; 350 | }; 351 | 352 | fF.mute = function($name) { 353 | if (typeof $name === 'undefined') { 354 | _scope.muteAll(); 355 | } else { 356 | if (!($name in _files)) { 357 | _.warn('[Fifer] No audio registered with the name: ' + $name); 358 | return; 359 | } 360 | _scope.mute($name); 361 | } 362 | return fF; 363 | }; 364 | 365 | fF.unmute = function($name) { 366 | if (typeof $name === 'undefined') { 367 | _scope.unmuteAll(); 368 | } else { 369 | if (!($name in _files)) { 370 | _.warn('[Fifer] No audio registered with the name: ' + $name); 371 | return; 372 | } 373 | _scope.unmute($name); 374 | } 375 | return fF; 376 | }; 377 | 378 | fF.muteAll = function() { 379 | _scope.muteAll(); 380 | return fF; 381 | }; 382 | 383 | fF.unmuteAll = function() { 384 | _scope.unmuteAll(); 385 | return fF; 386 | }; 387 | 388 | fF.muted = function() { 389 | return _scope.muted(); 390 | }; 391 | 392 | fF.isPlaying = function($name) { 393 | var count = 0; 394 | for (var id in _playing) { 395 | if (_playing.hasOwnProperty(id)) { 396 | if ($name && id.indexOf($name) !== -1) return true; 397 | count++; 398 | } 399 | } 400 | return ($name) ? false : count > 0 ? true : false; 401 | }; 402 | 403 | fF.loaded = function(fn) { 404 | _loaded = fn; 405 | return fF; 406 | }; 407 | 408 | fF.onAudioProcess = function(fn) { 409 | if (!_.support || _settings.force || _.context) { 410 | _process = fn; 411 | } else { 412 | _.warn('[Fifer] Fifer currently only supports audio processing while using the Flash fallback.'); 413 | } 414 | return fF; 415 | }; 416 | 417 | _.initialise = function() { 418 | if (!_.support || _settings.force || !_.context && _settings.forceFlashOnNoAudioContext) { 419 | _initialised = false; 420 | fF.interface = { 421 | responseInitialised : function() { 422 | _.warn('[Fifer] Fifer is using a Flash Fallback as HTML5 Audio is not supported.'); 423 | _initialised = true; 424 | while (_queue.length) { 425 | var q = _queue.shift(); 426 | var m = q.shift(); 427 | fF[m].apply(fF, q); 428 | } 429 | }, 430 | responseLoaded : function($name) { 431 | _internals.loaded($name); 432 | }, 433 | responseCompleted : function($arr) { 434 | _internals.ended($arr[0], $arr[1]); 435 | }, 436 | responseSpectrum : function($arr) { 437 | _process.call(fF, $arr); 438 | } 439 | }; 440 | _scope = _.fallback(); 441 | } else { 442 | _scope = _internals; 443 | } 444 | }; 445 | 446 | _.initialise(); 447 | 448 | w.Fifer = w.fF = fF; 449 | 450 | })(window,document); -------------------------------------------------------------------------------- /src/Fifer.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Sprite; 4 | 5 | import javascript.FiferInterface; 6 | 7 | import manager.FiferManager; 8 | 9 | public class Fifer extends Sprite 10 | { 11 | public function Fifer() 12 | { 13 | init(); 14 | } 15 | 16 | private function init() : void { 17 | 18 | FiferInterface.on(FiferInterface.CB_REGISTER_AUDIO, onRegisterAudio); 19 | FiferInterface.on(FiferInterface.CB_PLAY, onPlay); 20 | FiferInterface.on(FiferInterface.CB_STOP, onStop); 21 | FiferInterface.on(FiferInterface.CB_STOP_ALL, onStopAll); 22 | FiferInterface.on(FiferInterface.CB_MUTE, onMute); 23 | FiferInterface.on(FiferInterface.CB_UNMUTE, onUnmute); 24 | FiferInterface.on(FiferInterface.CB_MUTE_ALL, onMuteAll); 25 | FiferInterface.on(FiferInterface.CB_UNMUTE_ALL, onUnmuteAll); 26 | FiferInterface.on(FiferInterface.CB_MUTED, onMuted); 27 | 28 | FiferInterface.call(FiferInterface.RS_INITIALISED); 29 | } 30 | 31 | private function onRegisterAudio($name : String, $src : String, $multiple : Boolean = false) : void { 32 | FiferManager.sharedManager.registerAudio($name, $src, $multiple, function($n : String) : void { 33 | FiferInterface.call(FiferInterface.RS_LOADED, $n); 34 | }); 35 | } 36 | 37 | private function onPlay($name : String, $loop : Boolean = false, $ended : * = null) : Object { 38 | return FiferManager.sharedManager.play($name, $loop); 39 | } 40 | 41 | private function onStop($name : String) : void { 42 | FiferManager.sharedManager.stop($name); 43 | } 44 | 45 | private function onStopAll() : void { 46 | FiferManager.sharedManager.stopAll(); 47 | } 48 | 49 | private function onMute($name : String) : void { 50 | FiferManager.sharedManager.mute($name); 51 | } 52 | 53 | private function onUnmute($name : String) : void { 54 | FiferManager.sharedManager.unmute($name); 55 | } 56 | 57 | private function onMuteAll() : void { 58 | FiferManager.sharedManager.muteAll(); 59 | } 60 | 61 | private function onUnmuteAll() : void { 62 | FiferManager.sharedManager.unmuteAll(); 63 | } 64 | 65 | private function onMuted() : Boolean { 66 | return FiferManager.sharedManager.muted; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/javascript/FiferInterface.as: -------------------------------------------------------------------------------- 1 | package javascript 2 | { 3 | import flash.external.ExternalInterface; 4 | 5 | public class FiferInterface 6 | { 7 | public static const CB_REGISTER_AUDIO : String = "registerAudio"; 8 | public static const CB_PLAY : String = "playAudio"; 9 | public static const CB_STOP : String = "stopAudio"; 10 | public static const CB_STOP_ALL : String = "stopAll"; 11 | public static const CB_MUTE : String = "mute"; 12 | public static const CB_UNMUTE : String = "unmute"; 13 | public static const CB_MUTE_ALL : String = "muteAll"; 14 | public static const CB_UNMUTE_ALL : String = "unmuteAll"; 15 | public static const CB_MUTED : String = "muted"; 16 | 17 | public static const RS_LOADED : String = "responseLoaded"; 18 | public static const RS_INITIALISED : String = "responseInitialised"; 19 | public static const RS_COMPLETED : String = "responseCompleted"; 20 | public static const RS_SPECTRUM : String = "responseSpectrum"; 21 | 22 | public static function on(method : String, callback : Function) : void { 23 | ExternalInterface.addCallback(method, callback); 24 | } 25 | 26 | public static function call(method : String, response : * = null) : void { 27 | ExternalInterface.call('Fifer.interface.' + method, response); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/manager/FiferManager.as: -------------------------------------------------------------------------------- 1 | package manager 2 | { 3 | import flash.display.Sprite; 4 | import flash.events.Event; 5 | import flash.media.Sound; 6 | import flash.media.SoundChannel; 7 | import flash.media.SoundMixer; 8 | import flash.media.SoundTransform; 9 | import flash.net.URLRequest; 10 | import flash.utils.ByteArray; 11 | 12 | import javascript.FiferInterface; 13 | 14 | public class FiferManager extends Sprite 15 | { 16 | private static var _instance : FiferManager; 17 | private static var _files : Object = {}; 18 | private static var _playing : Object = {}; 19 | private static var _muted : Boolean = false; 20 | 21 | public function FiferManager(se : SingletonEnforcer) 22 | { 23 | if (se == null) 24 | { 25 | throw new Error("[FiferManager] FiferManager is a Singleton. Please retrieve the instance with FiferManager.sharedManager."); 26 | } 27 | } 28 | 29 | public static function get sharedManager() : FiferManager { 30 | if (_instance == null) { 31 | _instance = new FiferManager(new SingletonEnforcer()); 32 | _instance.addEventListener(Event.ENTER_FRAME, enterFrameHandler); 33 | } 34 | return _instance; 35 | } 36 | 37 | private static function enterFrameHandler(e : Event) : void 38 | { 39 | var count : int = 0; 40 | for (var o : * in _playing) count++; 41 | if (count > 0) { 42 | var array : Array = new Array(); 43 | var bytes : ByteArray = new ByteArray(); 44 | SoundMixer.computeSpectrum(bytes, true, 0); 45 | for (var i : int = 0; i < 512; i++) { 46 | array.push(bytes.readUnsignedByte()); 47 | } 48 | FiferInterface.call(FiferInterface.RS_SPECTRUM, array); 49 | } 50 | } 51 | 52 | public function registerAudio($name : String, $src : String, $multiple : Boolean, $callback : Function) : FiferManager 53 | { 54 | var _name : String = $name; 55 | var _sound : Sound = new Sound(); 56 | 57 | _sound.load(new URLRequest($src)); 58 | _sound.addEventListener(Event.COMPLETE, registerComplete); 59 | 60 | _files[_name] = { s : _sound, st : new SoundTransform(), multiple : $multiple, playing : [] }; 61 | 62 | function registerComplete(e : Event) : void { 63 | $callback(_name); 64 | } 65 | 66 | return _instance; 67 | } 68 | 69 | public function play($name : String, $loop : Boolean = false) : Object 70 | { 71 | var s : Sound = _files[$name].s; 72 | var id : String = $name + Math.random() * new Date().valueOf(); 73 | _files[$name].playing.push(id); 74 | _playing[id] = s.play(0, ($loop) ? int.MAX_VALUE : 0, _files[$name].st); 75 | _playing[id].addEventListener(Event.SOUND_COMPLETE, function(e : Event) : void { 76 | e.currentTarget.removeEventListener(Event.SOUND_COMPLETE, arguments.callee); 77 | //_files[$name].playing.shift(); 78 | FiferInterface.call(FiferInterface.RS_COMPLETED, [id, $name]); 79 | stop($name); 80 | }); 81 | return { playing : _files[$name].playing, id : id }; 82 | } 83 | 84 | public function stop($name : String) : FiferManager 85 | { 86 | while (_files[$name].playing.length) { 87 | var p : String = _files[$name].playing.shift(); 88 | var sc : SoundChannel = _playing[p]; 89 | sc.stop(); 90 | delete _playing[p]; 91 | } 92 | return _instance; 93 | } 94 | 95 | public function stopAll() : FiferManager 96 | { 97 | for (var s : String in _playing) { 98 | var sc : SoundChannel = _playing[s]; 99 | sc.stop(); 100 | delete _playing[s]; 101 | } 102 | return _instance; 103 | } 104 | 105 | public function mute($name : String) : FiferManager 106 | { 107 | if (_files.hasOwnProperty($name)) { 108 | var st : SoundTransform = _files[$name].st; 109 | st.volume = 0; 110 | for (var i : int = 0, j : int = _files[$name].playing.length; i < j; i++) { 111 | var p : String = _files[$name].playing[i]; 112 | var sc : SoundChannel = _playing[p]; 113 | sc.soundTransform = new SoundTransform(0); 114 | } 115 | } 116 | return _instance; 117 | } 118 | 119 | public function muteAll() : FiferManager 120 | { 121 | for (var p : String in _playing) { 122 | var sc : SoundChannel = _playing[p]; 123 | sc.soundTransform = new SoundTransform(0); 124 | } 125 | 126 | for (var s : String in _files) { 127 | var st : SoundTransform = _files[s].st; 128 | st.volume = 0; 129 | } 130 | 131 | _muted = true; 132 | return _instance; 133 | } 134 | 135 | public function unmute($name : String) : FiferManager 136 | { 137 | if (_files.hasOwnProperty($name)) { 138 | var st : SoundTransform = _files[$name].st; 139 | st.volume = 1; 140 | for (var i : int = 0, j : int = _files[$name].playing.length; i < j; i++) { 141 | var p : String = _files[$name].playing[i]; 142 | var sc : SoundChannel = _playing[p]; 143 | sc.soundTransform = new SoundTransform(1); 144 | } 145 | } 146 | return _instance; 147 | } 148 | 149 | public function unmuteAll() : FiferManager 150 | { 151 | for (var p : String in _playing) { 152 | var sc : SoundChannel = _playing[p]; 153 | sc.soundTransform = new SoundTransform(1); 154 | } 155 | 156 | for (var s : String in _files) { 157 | var st : SoundTransform = _files[s].st; 158 | st.volume = 1; 159 | } 160 | 161 | _muted = false; 162 | return _instance; 163 | } 164 | 165 | public function get muted() : Boolean 166 | { 167 | return _muted; 168 | } 169 | } 170 | } 171 | 172 | 173 | class SingletonEnforcer { 174 | 175 | } --------------------------------------------------------------------------------