X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F01_XWebAudio.js;h=bded21f07f1386854d787294afdc8aba96415371;hb=a3d03e96ad8c0392ef683eb6c64421e094b96958;hp=6d634359044d19851bb780fc20eae131c5c3f514;hpb=f24f3611a2525a2a4751a02aead9d3f69bb115d0;p=pettanr%2FclientJs.git diff --git a/0.6.x/js/07_audio/01_XWebAudio.js b/0.6.x/js/07_audio/01_XWebAudio.js index 6d63435..bded21f 100644 --- a/0.6.x/js/07_audio/01_XWebAudio.js +++ b/0.6.x/js/07_audio/01_XWebAudio.js @@ -1,41 +1,122 @@ +var X_Audio_constructor = 3.1 <= X_UA[ 'Safari' ] && X_UA[ 'Safari' ] < 4 ? + function( s, a ){ + a = document.createElement( 'audio' ); + a.src = s; + a.load(); + return a; + } : + // Android1.6 + MobileOpera12 HTMLAudio はいるが呼ぶとクラッシュする + !( X_UA[ 'Android' ] < 2 ) ? + window[ 'Audio' ] || window.HTMLAudioElement : null, + + // Blink5 Opera32 Win8 は HTMLAudio が壊れている、WebAudio は mp3 がデコードに失敗、ogg が動作 + X_Audio_blinkOperaFix = X_UA[ 'BlinkOpera' ] && X_UA[ 'Windows' ], + + X_Audio_codecs; + +if( X_Audio_constructor ){ + //http://himaxoff.blog111.fc2.com/blog-entry-97.html + //引数なしで new Audio() とすると、Operaでエラーになるそうなので注意。 + X_TEMP.rawAudio = new X_Audio_constructor( '' ); + + // https://html5experts.jp/miyuki-baba/3766/ + // TODO Chrome for Android31 で HE-AAC が低速再生されるバグ + // TODO Android4 標準ブラウザで ogg のシークが正しくない! + if( X_TEMP.rawAudio.canPlayType ){ + X_Audio_codecs = { + 'mp3' : X_TEMP.rawAudio.canPlayType('audio/mpeg'), + 'opus' : X_TEMP.rawAudio.canPlayType('audio/ogg; codecs="opus"'), + 'ogg' : X_TEMP.rawAudio.canPlayType('audio/ogg; codecs="vorbis"'), + 'wav' : X_TEMP.rawAudio.canPlayType('audio/wav; codecs="1"'), + 'aac' : X_TEMP.rawAudio.canPlayType('audio/aac'), + 'm4a' : X_TEMP.rawAudio.canPlayType('audio/x-m4a') + X_TEMP.rawAudio.canPlayType('audio/m4a') + X_TEMP.rawAudio.canPlayType('audio/aac'), + 'mp4' : X_TEMP.rawAudio.canPlayType('audio/x-mp4') + X_TEMP.rawAudio.canPlayType('audio/mp4') + X_TEMP.rawAudio.canPlayType('audio/aac'), + 'weba' : X_TEMP.rawAudio.canPlayType('audio/webm; codecs="vorbis"') + }; + (function( X_Audio_codecs, k, v ){ + for( k in X_Audio_codecs ){ + //if( X_EMPTY_OBJECT[ k ] ) continue; + v = X_Audio_codecs[ k ]; + v = v && !!( v.split( 'no' ).join( '' ) ); + if( v ){ + console.log( k + ' ' + X_Audio_codecs[ k ] ); + X_Audio_codecs[ k ] = true; + } else { + delete X_Audio_codecs[ k ]; + }; + }; + if( X_Audio_blinkOperaFix ) delete X_Audio_codecs[ 'mp3' ]; + })( X_Audio_codecs ); + } else { + // iOS3.2.3 + X_Audio_codecs = { + 'mp3' : X_UA[ 'IE' ] || X_UA[ 'Chrome' ] || ( X_UA[ 'Windows' ] && X_UA[ 'Safari' ] ), + 'ogg' : 5 <= X_UA[ 'Gecko' ] || X_UA[ 'Chrome' ] || X_UA[ 'Opera' ] , + 'wav' : X_UA[ 'Gecko' ] || X_UA[ 'Opera' ] || ( X_UA[ 'Windows' ] && X_UA[ 'Safari' ] ), + 'aac' : X_UA[ 'IE' ] || X_UA[ 'WebKit' ], + 'm4a' : X_UA[ 'IE' ] || X_UA[ 'WebKit' ], + 'mp4' : X_UA[ 'IE' ] || X_UA[ 'WebKit' ], + 'weba' : 2 <= X_UA[ 'Gecko' ] || 10.6 <= X_UA[ 'Opera' ] // firefox4+(Gecko2+) + }; + (function( X_Audio_codecs, k ){ + for( k in X_Audio_codecs ){ + //if( X_EMPTY_OBJECT[ k ] ) continue; + if( X_Audio_codecs[ k ] ){ + console.log( k + ' ' + X_Audio_codecs[ k ] ); + X_Audio_codecs[ k ] = true; + } else { + delete X_Audio_codecs[ k ]; + }; + }; + })( X_Audio_codecs ); + }; + + if( X_Audio_blinkOperaFix ){ + X_Audio_constructor = null; + delete X_TEMP.rawAudio; + }; +}; + -var X_Audio_WebAudio_context = !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] && +var X_WebAudio_context = !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] && // TODO なんで fennec を禁止? !( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) && - // Firefox40.0.5 + Windows8 で音声が鳴らない - !( X_UA[ 'Gecko' ] === 40 && X_UA[ 'Windows' ] ) && + // Firefox40.0.5 + Windows8 で音声が途中から鳴らなくなる + // Firefox41.0.1 + Windows8 で音声が途中から鳴らなくなる + !( 40 <= X_UA[ 'Gecko' ] && X_UA[ 'Gecko' ] < 42 && X_UA[ 'Windows' ] ) && ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] ), - X_Audio_BUFFER_LIST = [], - X_Audio_WebAudioWrapper, - X_Audio_BufferLoader, - X_Audio_fpsFix; + X_WebAudio_BUFFER_LIST = [], + X_WebAudio, + X_WebAudio_BufferLoader, + X_WebAudio_fpsFix; /* * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可 */ -if( X_Audio_WebAudio_context ){ +if( X_WebAudio_context ){ - X_Audio_WebAudio_context = new X_Audio_WebAudio_context; + X_WebAudio_context = new X_WebAudio_context; - X_Audio_BufferLoader = X_EventDispatcher[ 'inherits' ]( + X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ]( 'X.WebAudio.BufferLoader', X_Class.POOL_OBJECT, { - url : '', + audioUrl : '', xhr : null, onDecodeSuccess : null, onDecodeError : null, - buffer : null, - error : 0, + audioBuffer : null, + errorState : 0, webAudioList : null, 'Constructor' : function( webAudio, url ){ this.webAudioList = [ webAudio ]; - this.url = url; + this.audioUrl = url; this.xhr = X[ 'Net' ]( { 'xhr' : url, 'dataType' : 'arraybuffer' } ) [ 'listen' ]( X_EVENT_PROGRESS, this ) [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this ); + X_WebAudio_BUFFER_LIST.push( this ); }, handleEvent : function( e ){ @@ -52,18 +133,18 @@ if( X_Audio_WebAudio_context ){ // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。 // ただし iOS 8.1.2 では エラーになる - if( X_UA[ 'iOS' ] < 8 || !X_Audio_WebAudio_context[ 'decodeAudioData' ] ){ - this._onDecodeSuccess( X_Audio_WebAudio_context[ 'createBuffer' ]( e.response, false ) ); + if( X_UA[ 'iOS' ] < 8 || !X_WebAudio_context[ 'decodeAudioData' ] ){ + this._onDecodeSuccess( X_WebAudio_context[ 'createBuffer' ]( e.response, false ) ); } else - if( X_Audio_WebAudio_context[ 'decodeAudioData' ] ){ - X_Audio_WebAudio_context[ 'decodeAudioData' ]( e.response, + if( X_WebAudio_context[ 'decodeAudioData' ] ){ + X_WebAudio_context[ 'decodeAudioData' ]( e.response, this.onDecodeSuccess = X_Closure_create( this, this._onDecodeSuccess ), this.onDecodeError = X_Closure_create( this, this._onDecodeError ) ); }; break; case X_EVENT_COMPLETE : - this.error = 1; + this.errorState = 1; this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); break; }; @@ -72,17 +153,17 @@ if( X_Audio_WebAudio_context ){ }, _onDecodeSuccess : function( buffer ){ - console.log( 'WebAudio decode success!' ); - this.onDecodeSuccess && this._onDecodeComplete(); if ( !buffer ) { - this.error = 2; + this.errorState = 2; this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); return; }; + + console.log( 'WebAudio decode success!' ); - this.buffer = buffer; + this.audioBuffer = buffer; this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); @@ -92,7 +173,7 @@ if( X_Audio_WebAudio_context ){ _onDecodeError : function(){ console.log( 'WebAudio decode error!' ); this._onDecodeComplete(); - this.error = 2; + this.errorState = 2; this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); }, @@ -119,7 +200,7 @@ if( X_Audio_WebAudio_context ){ ); - X_Audio_WebAudioWrapper = X_Audio_AbstractAudioBackend[ 'inherits' ]( + X_WebAudio = X_AudioBase[ 'inherits' ]( 'X.WebAudio', X_Class.POOL_OBJECT, { @@ -131,14 +212,14 @@ if( X_Audio_WebAudio_context ){ _startTime : 0, _timerID : 0, _interval : 0, - buffer : null, + audioBuffer : null, bufferSource : null, gainNode : null, _onended : null, 'Constructor' : function( target, url, option ){ var i = 0, - l = X_Audio_BUFFER_LIST.length, + l = X_WebAudio_BUFFER_LIST.length, loader; /* @@ -146,14 +227,14 @@ if( X_Audio_WebAudio_context ){ * L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。 * 描画レート(描画 FPS)が下がるとノイズが混ざり始め、レートを上げると再生結果が正常になるというもので、オーディオ処理が描画スレッドに巻き込まれているような動作を見せる。 */ - if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] && !X_Audio_fpsFix ){ + if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] && !X_WebAudio_fpsFix ){ X_Node_systemNode.create( 'div', { id : 'fps-slowdown-make-sound-noisy' } ); - X_Audio_fpsFix = true; + X_WebAudio_fpsFix = true; }; for( ; i < l; ++i ){ - loader = X_Audio_BUFFER_LIST[ i ]; - if( loader.url === url ){ + loader = X_WebAudio_BUFFER_LIST[ i ]; + if( loader.audioUrl === url ){ this.loader = loader; loader.webAudioList.push( this ); break; @@ -161,7 +242,7 @@ if( X_Audio_WebAudio_context ){ }; if( !this.loader ){ - this.loader = loader = new X_Audio_BufferLoader( this, url ); + this.loader = loader = X_WebAudio_BufferLoader( this, url ); }; this.target = target || this; @@ -170,7 +251,7 @@ if( X_Audio_WebAudio_context ){ this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, this.onKill ); - if( loader.buffer || loader.error ){ + if( loader.audioBuffer || loader.errorState ){ this._onLoadBufferComplete(); } else { loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); @@ -181,7 +262,7 @@ if( X_Audio_WebAudio_context ){ this.loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ) .unregister( this ); - delete this.buffer; + delete this.audioBuffer; this.playing && this.actualPause(); this.bufferSource && this._sourceDispose(); @@ -192,17 +273,17 @@ if( X_Audio_WebAudio_context ){ }, _onLoadBufferComplete : function( e ){ var loader = this.loader, - buffer = loader.buffer; + buffer = loader.audioBuffer; e && loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); if ( !buffer ) { - this.error = loader.error; + this.error = loader.errorState; this.target[ 'dispatch' ]({ type : X_EVENT_ERROR, - error : loader.error, - message : loader.error === 1 ? + error : loader.errorState, + message : loader.errorState === 1 ? 'load buffer network error' : 'buffer decode error' }); @@ -210,8 +291,8 @@ if( X_Audio_WebAudio_context ){ return; }; - this.buffer = buffer; - this.duration = buffer.duration * 1000; + this.audioBuffer = buffer; + this.duration = buffer.duration * 1000; this.target[ 'asyncDispatch' ]( X_EVENT_READY ); @@ -224,23 +305,23 @@ if( X_Audio_WebAudio_context ){ actualPlay : function(){ var begin, end; - if( !this.buffer ){ + if( !this.audioBuffer ){ this.autoplay = true; return; }; - end = X_AudioWrapper_getEndTime( this ); - begin = X_AudioWrapper_getStartTime( this, end, true ); + end = X_Audio_getEndTime( this ); + begin = X_Audio_getStartTime( this, end, true ); console.log( '[WebAudio] play ' + begin + ' -> ' + end ); if( this.bufferSource ) this._sourceDispose(); if( !this.gainNode ){ - this.gainNode = X_Audio_WebAudio_context[ 'createGain' ] ? X_Audio_WebAudio_context[ 'createGain' ]() : X_Audio_WebAudio_context[ 'createGainNode' ](); - this.gainNode[ 'connect' ]( X_Audio_WebAudio_context[ 'destination' ] ); + this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ](); + this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] ); }; - this.bufferSource = X_Audio_WebAudio_context[ 'createBufferSource' ](); - this.bufferSource.buffer = this.buffer; + this.bufferSource = X_WebAudio_context[ 'createBufferSource' ](); + this.bufferSource.buffer = this.audioBuffer; this.bufferSource[ 'connect' ]( this.gainNode ); this.gainNode[ 'gain' ].value = this.gain; @@ -264,7 +345,7 @@ if( X_Audio_WebAudio_context ){ this.playing = true; this._startPos = begin; this._endPosition = end; - this._startTime = X_Audio_WebAudio_context.currentTime * 1000; + this._startTime = X_WebAudio_context.currentTime * 1000; this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval ); }, @@ -287,14 +368,14 @@ if( X_Audio_WebAudio_context ){ delete this._timerID; if( this.playing ){ - time = X_Audio_WebAudio_context.currentTime * 1000 - this._startTime - this._endPosition + this._startPos | 0; - //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime ) ) + ' < ' + ( this._endPosition - this._startPos ) ); + time = X_WebAudio_context.currentTime * 1000 - this._startTime - this._endPosition + this._startPos | 0; + //console.log( '> onEnd ' + ( this.playing && ( X_WebAudio_context.currentTime * 1000 - this._startTime ) ) + ' < ' + ( this._endPosition - this._startPos ) ); if( this._onended ){ // Firefox 用の対策,,, if( time < 0 ) return; } else { if( time < 0 ){ - //console.log( '> onEnd crt:' + ( X_Audio_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime + + //console.log( '> onEnd crt:' + ( X_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime + // ' from:' + this._startPos + ' to:' + this._endPosition ); this._timerID = X_Timer_once( -time, this, this._onEnded ); return; @@ -334,7 +415,7 @@ if( X_Audio_WebAudio_context ){ }, getActualCurrentTime : function(){ - return X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0; + return X_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0; }, afterUpdateState : function( result ){ @@ -351,16 +432,17 @@ if( X_Audio_WebAudio_context ){ X_Audio_BACKENDS.push( { - backendName : 'Web Audio', + backendID : 1, + + backendName : 'WebAudio', - canPlay : {}, // TODO HTMLAudio と同じ + canPlay : X_Audio_codecs, - // - detect : function( proxy, source, ext ){ + detect : function( proxy, source, ext ){ proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } ); }, - klass : X_Audio_WebAudioWrapper + klass : X_WebAudio } ); };