/* * dancer - v0.4.0 - 2014-02-01 * https://github.com/jsantell/dancer.js * Copyright (c) 2014 Jordan Santell * Licensed MIT */ (function() { var Dancer = function () { this.audioAdapter = Dancer._getAdapter( this ); this.events = {}; this.sections = []; this.bind( 'update', update ); }; Dancer.version = '0.4.0'; Dancer.adapters = {}; Dancer.prototype = { load : function ( source, useMic ) { // Loading an Audio element if ( source instanceof HTMLElement ) { this.source = source; if ( Dancer.isSupported() === 'flash' ) { this.source = { src: Dancer._getMP3SrcFromAudio( source ) }; } // Loading an object with src, [codecs] } else if(source instanceof EventTarget){ this.source = source; } else { this.source = window.Audio ? new Audio() : {}; this.source.src = Dancer._makeSupportedPath( source.src, source.codecs ); } this.useMic = useMic === true; this.audio = this.audioAdapter.load(this.source, this.useMic); return this; }, /* Controls */ play : function () { this.audioAdapter.play(); return this; }, pause : function () { this.audioAdapter.pause(); return this; }, setVolume : function ( volume ) { this.audioAdapter.setVolume( volume ); return this; }, /* Actions */ createKick : function ( options ) { return new Dancer.Kick( this, options ); }, bind : function ( name, callback ) { if ( !this.events[ name ] ) { this.events[ name ] = []; } this.events[ name ].push( callback ); return this; }, unbind : function ( name ) { if ( this.events[ name ] ) { delete this.events[ name ]; } return this; }, trigger : function ( name ) { var _this = this; if ( this.events[ name ] ) { this.events[ name ].forEach(function( callback ) { callback.call( _this ); }); } return this; }, /* Getters */ getVolume : function () { return this.audioAdapter.getVolume(); }, getProgress : function () { return this.audioAdapter.getProgress(); }, getTime : function () { return this.audioAdapter.getTime(); }, // Returns the magnitude of a frequency or average over a range of frequencies getFrequency : function ( freq, endFreq ) { var sum = 0; if ( endFreq !== undefined ) { for ( var i = freq; i <= endFreq; i++ ) { sum += this.getSpectrum()[ i ]; } return sum / ( endFreq - freq + 1 ); } else { return this.getSpectrum()[ freq ]; } }, getWaveform : function () { return this.audioAdapter.getWaveform(); }, getSpectrum : function () { return this.audioAdapter.getSpectrum(); }, isLoaded : function () { return this.audioAdapter.isLoaded; }, isPlaying : function () { return this.audioAdapter.isPlaying; }, /* Sections */ after : function ( time, callback ) { var _this = this; this.sections.push({ condition : function () { return _this.getTime() > time; }, callback : callback }); return this; }, before : function ( time, callback ) { var _this = this; this.sections.push({ condition : function () { return _this.getTime() < time; }, callback : callback }); return this; }, between : function ( startTime, endTime, callback ) { var _this = this; this.sections.push({ condition : function () { return _this.getTime() > startTime && _this.getTime() < endTime; }, callback : callback }); return this; }, onceAt : function ( time, callback ) { var _this = this, thisSection = null; this.sections.push({ condition : function () { return _this.getTime() > time && !this.called; }, callback : function () { callback.call( this ); thisSection.called = true; }, called : false }); // Baking the section in the closure due to callback's this being the dancer instance thisSection = this.sections[ this.sections.length - 1 ]; return this; } }; function update () { for (var i in this.sections) { if (this.sections[i].condition && this.sections[i].condition() ) this.sections[i].callback.call(this); } } window.Dancer = Dancer; })(); (function ( Dancer ) { var CODECS = { 'mp3' : 'audio/mpeg;', 'ogg' : 'audio/ogg; codecs="vorbis"', 'wav' : 'audio/wav; codecs="1"', 'aac' : 'audio/mp4; codecs="mp4a.40.2"' }, audioEl = document.createElement( 'audio' ); Dancer.options = {}; Dancer.setOptions = function ( o ) { for ( var option in o ) { if ( o.hasOwnProperty( option ) ) { Dancer.options[ option ] = o[ option ]; } } }; Dancer.isSupported = function () { if ( !window.Float32Array || !window.Uint32Array ) { return null; } else if ( !isUnsupportedSafari() && ( window.AudioContext || window.webkitAudioContext )) { return 'webaudio'; } else if ( audioEl && audioEl.mozSetup ) { return 'audiodata'; } else if ( FlashDetect.versionAtLeast( 9 ) ) { return 'flash'; } else { return ''; } }; Dancer.canPlay = function ( type ) { var canPlay = audioEl.canPlayType; return !!( Dancer.isSupported() === 'flash' ? type.toLowerCase() === 'mp3' : audioEl.canPlayType && audioEl.canPlayType( CODECS[ type.toLowerCase() ] ).replace( /no/, '')); }; Dancer.addPlugin = function ( name, fn ) { if ( Dancer.prototype[ name ] === undefined ) { Dancer.prototype[ name ] = fn; } }; Dancer._makeSupportedPath = function ( source, codecs ) { if ( !codecs ) { return source; } for ( var i = 0; i < codecs.length; i++ ) { if ( Dancer.canPlay( codecs[ i ] ) ) { return source + '.' + codecs[ i ]; } } return source; }; Dancer._getAdapter = function ( instance ) { switch ( Dancer.isSupported() ) { case 'webaudio': return new Dancer.adapters.webaudio( instance ); case 'audiodata': return new Dancer.adapters.moz( instance ); case 'flash': return new Dancer.adapters.flash( instance ); default: return null; } }; Dancer._getMP3SrcFromAudio = function ( audioEl ) { var sources = audioEl.children; if ( audioEl.src ) { return audioEl.src; } for ( var i = sources.length; i--; ) { if (( sources[ i ].type || '' ).match( /audio\/mpeg/ )) return sources[ i ].src; } return null; }; // Browser detection is lame, but Safari 6 has Web Audio API, // but does not support processing audio from a Media Element Source // https://gist.github.com/3265344 function isUnsupportedSafari () { var isApple = !!( navigator.vendor || '' ).match( /Apple/ ), version = navigator.userAgent.match( /Version\/([^ ]*)/ ); version = version ? parseFloat( version[ 1 ] ) : 0; return isApple && version <= 6; } })( window.Dancer ); (function ( undefined ) { var Kick = function ( dancer, o ) { o = o || {}; this.dancer = dancer; this.frequency = o.frequency !== undefined ? o.frequency : [ 0, 10 ]; this.threshold = o.threshold !== undefined ? o.threshold : 0.3; this.decay = o.decay !== undefined ? o.decay : 0.02; this.onKick = o.onKick; this.offKick = o.offKick; this.isOn = false; this.currentThreshold = this.threshold; var _this = this; this.dancer.bind( 'update', function () { _this.onUpdate(); }); }; Kick.prototype = { on : function () { this.isOn = true; return this; }, off : function () { this.isOn = false; return this; }, set : function ( o ) { o = o || {}; this.frequency = o.frequency !== undefined ? o.frequency : this.frequency; this.threshold = o.threshold !== undefined ? o.threshold : this.threshold; this.decay = o.decay !== undefined ? o.decay : this.decay; this.onKick = o.onKick || this.onKick; this.offKick = o.offKick || this.offKick; }, onUpdate : function () { if ( !this.isOn ) { return; } var magnitude = this.maxAmplitude( this.frequency ); if ( magnitude >= this.currentThreshold && magnitude >= this.threshold ) { this.currentThreshold = magnitude; this.onKick && this.onKick.call( this.dancer, magnitude ); } else { this.offKick && this.offKick.call( this.dancer, magnitude ); this.currentThreshold -= this.decay; } }, maxAmplitude : function ( frequency ) { var max = 0, fft = this.dancer.getSpectrum(); // Sloppy array check if ( !frequency.length ) { return frequency < fft.length ? fft[ ~~frequency ] : null; } for ( var i = frequency[ 0 ], l = frequency[ 1 ]; i <= l; i++ ) { if ( fft[ i ] > max ) { max = fft[ i ]; } } return max; } }; window.Dancer.Kick = Kick; })(); (function() { var SAMPLE_SIZE = 2048, SAMPLE_RATE = 44100; var adapter = function ( dancer ) { var context = new AudioContext(), filter = context.createBiquadFilter(); filter.type = "lowpass"; filter.frequency.value = 440; this.dancer = dancer; this.audio = new Audio(); this.context = context; this.filter = filter; }; adapter.prototype = { load : function (_source, useMic) { var _this = this; this.audio = _source; this.useMic = useMic; this.isLoaded = false; this.progress = 0; if(this.proc){ this.proc.onaudioprocess = null; delete this.proc; } this.proc = this.context.createScriptProcessor( SAMPLE_SIZE / 2, 1, 1 ); this.proc.onaudioprocess = function ( e ) { _this.update.call( _this, e ); }; this.gain = this.context.createGain(); this.fft = new FFT( SAMPLE_SIZE / 2, SAMPLE_RATE ); this.signal = new Float32Array( SAMPLE_SIZE / 2 ); if ( this.audio.readyState < 3 ) { this.audio.addEventListener( 'canplay', function () { connectContext.call( _this ); }); } else { connectContext.call( _this ); } this.audio.addEventListener( 'progress', function ( e ) { if ( e.currentTarget.duration && e.currentTarget.duration !== Infinity ) { _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; } }); return this.audio; }, play : function () { this.audio.play(); this.isPlaying = true; }, pause : function () { this.audio.pause(); this.isPlaying = false; }, setVolume : function ( volume ) { this.gain.gain.value = volume; }, getVolume : function () { return this.gain.gain.value; }, getProgress : function() { return this.progress; }, getWaveform : function () { return this.signal; }, getSpectrum : function () { return this.fft.spectrum; }, getTime : function () { return this.audio.currentTime; }, update : function ( e ) { if ((!this.isPlaying || !this.isLoaded) && this.useMic !== true ) return; var buffers = [], channels = e.inputBuffer.numberOfChannels, resolution = SAMPLE_SIZE / channels, sum = function ( prev, curr ) { return prev[ i ] + curr[ i ]; }, i; for ( i = channels; i--; ) { buffers.push( e.inputBuffer.getChannelData( i ) ); } for ( i = 0; i < resolution; i++ ) { this.signal[ i ] = channels > 1 ? buffers.reduce( sum ) / channels : buffers[ 0 ][ i ]; } this.fft.forward( this.signal ); this.dancer.trigger( 'update' ); } }; function connectContext () { try { if(this.useMic){ this.source = this.context.createMediaStreamSource(this.audio); } else { this.source = this.context.createMediaElementSource(this.audio); } } catch (err) { console.info('Dancer: '+ err); return; } this.source.connect(this.proc); this.source.connect(this.gain); //this.source.connect( this.filter ); this.gain.connect(this.context.destination); this.proc.connect(this.context.destination); //this.filter.connect( this.context.destination ); this.isLoaded = true; this.progress = 1; this.dancer.trigger( 'loaded' ); } Dancer.adapters.webaudio = adapter; })(); (function() { var adapter = function ( dancer ) { this.dancer = dancer; this.audio = new Audio(); }; adapter.prototype = { load : function ( _source ) { var _this = this; this.audio = _source; this.isLoaded = false; this.progress = 0; if ( this.audio.readyState < 3 ) { this.audio.addEventListener( 'loadedmetadata', function () { getMetadata.call( _this ); }, false); } else { getMetadata.call( _this ); } this.audio.addEventListener( 'MozAudioAvailable', function ( e ) { _this.update( e ); }, false); this.audio.addEventListener( 'progress', function ( e ) { if ( e.currentTarget.duration && e.currentTarget.duration !== Infinity) { _this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration; } }, false); return this.audio; }, play : function () { this.audio.play(); this.isPlaying = true; }, pause : function () { this.audio.pause(); this.isPlaying = false; }, setVolume : function ( volume ) { this.audio.volume = volume; }, getVolume : function () { return this.audio.volume; }, getProgress : function () { return this.progress; }, getWaveform : function () { return this.signal; }, getSpectrum : function () { return this.fft.spectrum; }, getTime : function () { return this.audio.currentTime; }, update : function ( e ) { if ( !this.isPlaying || !this.isLoaded ) return; for ( var i = 0, j = this.fbLength / 2; i < j; i++ ) { this.signal[ i ] = ( e.frameBuffer[ 2 * i ] + e.frameBuffer[ 2 * i + 1 ] ) / 2; } this.fft.forward( this.signal ); this.dancer.trigger( 'update' ); } }; function getMetadata () { this.fbLength = this.audio.mozFrameBufferLength; this.channels = this.audio.mozChannels; this.rate = this.audio.mozSampleRate; this.fft = new FFT( this.fbLength / this.channels, this.rate ); this.signal = new Float32Array( this.fbLength / this.channels ); this.isLoaded = true; this.progress = 1; this.dancer.trigger( 'loaded' ); } Dancer.adapters.moz = adapter; })(); (function() { var SAMPLE_SIZE = 1024, SAMPLE_RATE = 44100, smLoaded = false, smLoading = false, CONVERSION_COEFFICIENT = 0.93; var adapter = function ( dancer ) { this.dancer = dancer; this.wave_L = []; this.wave_R = []; this.spectrum = []; window.SM2_DEFER = true; }; adapter.prototype = { // `source` can be either an Audio element, if supported, or an object // either way, the path is stored in the `src` property load : function ( source ) { var _this = this; this.path = source ? source.src : this.path; this.isLoaded = false; this.progress = 0; !window.soundManager && !smLoading && loadSM.call( this ); if ( window.soundManager ) { this.audio = soundManager.createSound({ id : 'dancer' + Math.random() + '', url : this.path, stream : true, autoPlay : false, autoLoad : true, whileplaying : function () { _this.update(); }, whileloading : function () { _this.progress = this.bytesLoaded / this.bytesTotal; }, onload : function () { _this.fft = new FFT( SAMPLE_SIZE, SAMPLE_RATE ); _this.signal = new Float32Array( SAMPLE_SIZE ); _this.waveform = new Float32Array( SAMPLE_SIZE ); _this.isLoaded = true; _this.progress = 1; _this.dancer.trigger( 'loaded' ); } }); this.dancer.audio = this.audio; } // Returns audio if SM already loaded -- otherwise, // sets dancer instance's audio property after load return this.audio; }, play : function () { this.audio.play(); this.isPlaying = true; }, pause : function () { this.audio.pause(); this.isPlaying = false; }, setVolume : function ( volume ) { this.audio.setVolume( volume * 100 ); }, getVolume : function () { return this.audio.volume / 100; }, getProgress : function () { return this.progress; }, getWaveform : function () { return this.waveform; }, getSpectrum : function () { return this.fft.spectrum; }, getTime : function () { return this.audio.position / 1000; }, update : function () { if ( !this.isPlaying && !this.isLoaded ) return; this.wave_L = this.audio.waveformData.left; this.wave_R = this.audio.waveformData.right; var avg; for ( var i = 0, j = this.wave_L.length; i < j; i++ ) { avg = parseFloat(this.wave_L[ i ]) + parseFloat(this.wave_R[ i ]); this.waveform[ 2 * i ] = avg / 2; this.waveform[ i * 2 + 1 ] = avg / 2; this.signal[ 2 * i ] = avg * CONVERSION_COEFFICIENT; this.signal[ i * 2 + 1 ] = avg * CONVERSION_COEFFICIENT; } this.fft.forward( this.signal ); this.dancer.trigger( 'update' ); } }; function loadSM () { var adapter = this; smLoading = true; loadScript( Dancer.options.flashJS, function () { soundManager = new SoundManager(); soundManager.flashVersion = 9; soundManager.flash9Options.useWaveformData = true; soundManager.useWaveformData = true; soundManager.useHighPerformance = true; soundManager.useFastPolling = true; soundManager.multiShot = false; soundManager.debugMode = false; soundManager.debugFlash = false; soundManager.url = Dancer.options.flashSWF; soundManager.onready(function () { smLoaded = true; adapter.load(); }); soundManager.ontimeout(function(){ console.error( 'Error loading SoundManager2.swf' ); }); soundManager.beginDelayedInit(); }); } function loadScript ( url, callback ) { var script = document.createElement( 'script' ), appender = document.getElementsByTagName( 'script' )[0]; script.type = 'text/javascript'; script.src = url; script.onload = callback; appender.parentNode.insertBefore( script, appender ); } Dancer.adapters.flash = adapter; })(); /* * DSP.js - a comprehensive digital signal processing library for javascript * * Created by Corban Brook on 2010-01-01. * Copyright 2010 Corban Brook. All rights reserved. * */ // Fourier Transform Module used by DFT, FFT, RFFT function FourierTransform(bufferSize, sampleRate) { this.bufferSize = bufferSize; this.sampleRate = sampleRate; this.bandwidth = 2 / bufferSize * sampleRate / 2; this.spectrum = new Float32Array(bufferSize/2); this.real = new Float32Array(bufferSize); this.imag = new Float32Array(bufferSize); this.peakBand = 0; this.peak = 0; /** * Calculates the *middle* frequency of an FFT band. * * @param {Number} index The index of the FFT band. * * @returns The middle frequency in Hz. */ this.getBandFrequency = function(index) { return this.bandwidth * index + this.bandwidth / 2; }; this.calculateSpectrum = function() { var spectrum = this.spectrum, real = this.real, imag = this.imag, bSi = 2 / this.bufferSize, sqrt = Math.sqrt, rval, ival, mag; for (var i = 0, N = bufferSize/2; i < N; i++) { rval = real[i]; ival = imag[i]; mag = bSi * sqrt(rval * rval + ival * ival); if (mag > this.peak) { this.peakBand = i; this.peak = mag; } spectrum[i] = mag; } }; } /** * FFT is a class for calculating the Discrete Fourier Transform of a signal * with the Fast Fourier Transform algorithm. * * @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2 * @param {Number} sampleRate The sampleRate of the buffer (eg. 44100) * * @constructor */ function FFT(bufferSize, sampleRate) { FourierTransform.call(this, bufferSize, sampleRate); this.reverseTable = new Uint32Array(bufferSize); var limit = 1; var bit = bufferSize >> 1; var i; while (limit < bufferSize) { for (i = 0; i < limit; i++) { this.reverseTable[i + limit] = this.reverseTable[i] + bit; } limit = limit << 1; bit = bit >> 1; } this.sinTable = new Float32Array(bufferSize); this.cosTable = new Float32Array(bufferSize); for (i = 0; i < bufferSize; i++) { this.sinTable[i] = Math.sin(-Math.PI/i); this.cosTable[i] = Math.cos(-Math.PI/i); } } /** * Performs a forward transform on the sample buffer. * Converts a time domain signal to frequency domain spectra. * * @param {Array} buffer The sample buffer. Buffer Length must be power of 2 * * @returns The frequency spectrum array */ FFT.prototype.forward = function(buffer) { // Locally scope variables for speed up var bufferSize = this.bufferSize, cosTable = this.cosTable, sinTable = this.sinTable, reverseTable = this.reverseTable, real = this.real, imag = this.imag, spectrum = this.spectrum; var k = Math.floor(Math.log(bufferSize) / Math.LN2); if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; } if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; } var halfSize = 1, phaseShiftStepReal, phaseShiftStepImag, currentPhaseShiftReal, currentPhaseShiftImag, off, tr, ti, tmpReal, i; for (i = 0; i < bufferSize; i++) { real[i] = buffer[reverseTable[i]]; imag[i] = 0; } while (halfSize < bufferSize) { //phaseShiftStepReal = Math.cos(-Math.PI/halfSize); //phaseShiftStepImag = Math.sin(-Math.PI/halfSize); phaseShiftStepReal = cosTable[halfSize]; phaseShiftStepImag = sinTable[halfSize]; currentPhaseShiftReal = 1; currentPhaseShiftImag = 0; for (var fftStep = 0; fftStep < halfSize; fftStep++) { i = fftStep; while (i < bufferSize) { off = i + halfSize; tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]); ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]); real[off] = real[i] - tr; imag[off] = imag[i] - ti; real[i] += tr; imag[i] += ti; i += halfSize << 1; } tmpReal = currentPhaseShiftReal; currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); } halfSize = halfSize << 1; } return this.calculateSpectrum(); }; /* Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved. Code licensed under the BSD License: http://www.featureblend.com/license.txt Version: 1.0.4 */ var FlashDetect = new function(){ var self = this; self.installed = false; self.raw = ""; self.major = -1; self.minor = -1; self.revision = -1; self.revisionStr = ""; var activeXDetectRules = [ { "name":"ShockwaveFlash.ShockwaveFlash.7", "version":function(obj){ return getActiveXVersion(obj); } }, { "name":"ShockwaveFlash.ShockwaveFlash.6", "version":function(obj){ var version = "6,0,21"; try{ obj.AllowScriptAccess = "always"; version = getActiveXVersion(obj); }catch(err){} return version; } }, { "name":"ShockwaveFlash.ShockwaveFlash", "version":function(obj){ return getActiveXVersion(obj); } } ]; /** * Extract the ActiveX version of the plugin. * * @param {Object} The flash ActiveX object. * @type String */ var getActiveXVersion = function(activeXObj){ var version = -1; try{ version = activeXObj.GetVariable("$version"); }catch(err){} return version; }; /** * Try and retrieve an ActiveX object having a specified name. * * @param {String} name The ActiveX object name lookup. * @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true. * @type Object */ var getActiveXObject = function(name){ var obj = -1; try{ obj = new ActiveXObject(name); }catch(err){ obj = {activeXError:true}; } return obj; }; /** * Parse an ActiveX $version string into an object. * * @param {String} str The ActiveX Object GetVariable($version) return value. * @return An object having raw, major, minor, revision and revisionStr attributes. * @type Object */ var parseActiveXVersion = function(str){ var versionArray = str.split(",");//replace with regex return { "raw":str, "major":parseInt(versionArray[0].split(" ")[1], 10), "minor":parseInt(versionArray[1], 10), "revision":parseInt(versionArray[2], 10), "revisionStr":versionArray[2] }; }; /** * Parse a standard enabledPlugin.description into an object. * * @param {String} str The enabledPlugin.description value. * @return An object having raw, major, minor, revision and revisionStr attributes. * @type Object */ var parseStandardVersion = function(str){ var descParts = str.split(/ +/); var majorMinor = descParts[2].split(/\./); var revisionStr = descParts[3]; return { "raw":str, "major":parseInt(majorMinor[0], 10), "minor":parseInt(majorMinor[1], 10), "revisionStr":revisionStr, "revision":parseRevisionStrToInt(revisionStr) }; }; /** * Parse the plugin revision string into an integer. * * @param {String} The revision in string format. * @type Number */ var parseRevisionStrToInt = function(str){ return parseInt(str.replace(/[a-zA-Z]/g, ""), 10) || self.revision; }; /** * Is the major version greater than or equal to a specified version. * * @param {Number} version The minimum required major version. * @type Boolean */ self.majorAtLeast = function(version){ return self.major >= version; }; /** * Is the minor version greater than or equal to a specified version. * * @param {Number} version The minimum required minor version. * @type Boolean */ self.minorAtLeast = function(version){ return self.minor >= version; }; /** * Is the revision version greater than or equal to a specified version. * * @param {Number} version The minimum required revision version. * @type Boolean */ self.revisionAtLeast = function(version){ return self.revision >= version; }; /** * Is the version greater than or equal to a specified major, minor and revision. * * @param {Number} major The minimum required major version. * @param {Number} (Optional) minor The minimum required minor version. * @param {Number} (Optional) revision The minimum required revision version. * @type Boolean */ self.versionAtLeast = function(major){ var properties = [self.major, self.minor, self.revision]; var len = Math.min(properties.length, arguments.length); for(i=0; i=arguments[i]){ if(i+10){ var type = 'application/x-shockwave-flash'; var mimeTypes = navigator.mimeTypes; if(mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description){ var version = mimeTypes[type].enabledPlugin.description; var versionObj = parseStandardVersion(version); self.raw = versionObj.raw; self.major = versionObj.major; self.minor = versionObj.minor; self.revisionStr = versionObj.revisionStr; self.revision = versionObj.revision; self.installed = true; } }else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript){ var version = -1; for(var i=0; i