X-Git-Url: http://git.osdn.jp/view?p=pettanr%2FclientJs.git;a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F01_XWebAudio.js;fp=0.6.x%2Fjs%2F07_audio%2F01_XWebAudio.js;h=910271fb5672074287595be8cc16a8b2508d066f;hp=1cc3cba7b5cbe5e99845ef6b27f407bcf49d3ac2;hb=66ccef8a1fdd3994dd3c75dcfede668ea55f1d2e;hpb=4e4ab3be10850546063d4a4b93250ed142bb8cd2 diff --git a/0.6.x/js/07_audio/01_XWebAudio.js b/0.6.x/js/07_audio/01_XWebAudio.js index 1cc3cba..910271f 100644 --- a/0.6.x/js/07_audio/01_XWebAudio.js +++ b/0.6.x/js/07_audio/01_XWebAudio.js @@ -14,6 +14,10 @@ var X_Audio_constructor = 3.1 <= X_UA[ 'Safari' ] && X_UA[ 'Safari' ] < 4 ? X_Audio_codecs; +// WebAudioAPIを使っているはずなのに、マナーモードで音が出る!? +// http://qiita.com/gonshi_com/items/e41dbb80f5eb4c176108 +// HTML Audio、もしくはHTML Videoをページ内で1つでも使用していた場合、そのページでは WebAudioAPI の音がマナーモード時にも鳴ってしまう + if( X_Audio_constructor ){ //http://himaxoff.blog111.fc2.com/blog-entry-97.html //引数なしで new Audio() とすると、Operaでエラーになるそうなので注意。 @@ -78,7 +82,7 @@ if( X_Audio_constructor ){ }; -var X_WebAudio_context = // 4s 以下ではない iPad 2G または iPad mini 1G 以下ではない, iPod touch 4G 以下ではない +var X_WebAudio_Context = // 4s 以下ではない iPad 2G または iPad mini 1G 以下ではない, iPod touch 4G 以下ではない !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] && // iOS7 以上で HTML Audio が鳴らない問題を見ていくよ // !X_UA[ 'iOS' ] && @@ -90,11 +94,13 @@ var X_WebAudio_context = // 4s 以下ではない iPad 2G または iPad mi //!X_UA[ 'Blink' ] && // Firefox40.0.5 + Windows8 で音声が途中から鳴らなくなる // Firefox41.0.1 + Windows8 で音声が途中から鳴らなくなる - !( 40 <= X_UA[ 'Gecko' ] && X_UA[ 'Gecko' ] < 46 && X_UA[ 'Windows' ] ) && - ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] ), + !( 40 <= X_UA[ 'Gecko' ] && /* X_UA[ 'Gecko' ] < 48 && */ X_UA[ 'Windows' ] ) && + ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] || window[ 'mozAudioContext' ] ), + X_WebAudio_context, X_WebAudio_BUFFER_LIST = [], X_WebAudio_need1stTouch = X_UA[ 'iOS' ], - X_WebAudio_touchState = X_WebAudio_need1stTouch, + X_WebAudio_isNoTouch = X_WebAudio_need1stTouch, + X_WebAudio_needRateFix = X_WebAudio_need1stTouch, X_WebAudio, X_WebAudio_BufferLoader, X_WebAudio_fpsFix; @@ -102,9 +108,46 @@ var X_WebAudio_context = // 4s 以下ではない iPad 2G または iPad mi /* * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可 */ -if( X_WebAudio_context ){ +if( X_WebAudio_Context ){ + + X_WebAudio_context = new X_WebAudio_Context; - X_WebAudio_context = new X_WebAudio_context; + // http://lilting.ch/3323.html + // 【間に合わせ】iOS9系でのWebAudioの音割れ対処について + /* + if( X_WebAudio_needRateFix ){ + X_WebAudio_context.close(); + X_WebAudio_context = new X_WebAudio_Context; + }; */ + + /* + * TODO X_TEMP へ + * http://qiita.com/simiraaaa/items/79a9ac972cc76fb58d93 + * [WebAudio API] iOS9で音が歪む、遅い、低い、割れる等の回避方法 + */ + if( X_WebAudio_needRateFix ){ + X_TEMP.webAudioSampleRateFix = function( sampleRate ){ + X_TEMP.webAudioDummyPlay( sampleRate ); + + if( true || X_WebAudio_context[ 'sampleRate' ] !== sampleRate ){ + // alert( '[debug]iOSで音割れを検知、修復コードを実施 ctxSR:' + X_WebAudio_context[ 'sampleRate' ] + ' abfSR:' + sampleRate ); + + X_WebAudio_context.close && X_WebAudio_context.close(); + X_WebAudio_context = new X_WebAudio_Context; + + X_TEMP.webAudioDummyPlay( sampleRate ); + }; + + delete X_TEMP.webAudioSampleRateFix; + delete X_TEMP.webAudioDummyPlay; + }; + X_TEMP.webAudioDummyPlay = function( sampleRate, source ){ + source = X_WebAudio_context[ 'createBufferSource' ](); + source.buffer = X_WebAudio_context[ 'createBuffer' ]( 1, 1, sampleRate ); + source[ 'connect' ]( X_WebAudio_context[ 'destination' ] ); + source.start ? source.start( 0 ) : source[ 'noteOn' ] ? source[ 'noteOn' ]( 0 ) : source[ 'noteGrainOn' ]( 0 ); + }; + }; X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ]( 'X.WebAudio.BufferLoader', @@ -212,8 +255,7 @@ if( X_WebAudio_context ){ } ); - - + X_WebAudio = X_AudioBase[ 'inherits' ]( 'X.WebAudio', X_Class.POOL_OBJECT, @@ -229,9 +271,12 @@ if( X_WebAudio_context ){ audioBuffer : null, bufferSource : null, gainNode : null, + + bufferPlay : '', + bufferStop : '', //_onended : null, - 'Constructor' : function( disatcher, url, option ){ + 'Constructor' : function( dispatcher, url, option ){ var i = 0, l = X_WebAudio_BUFFER_LIST.length, loader; @@ -259,7 +304,7 @@ if( X_WebAudio_context ){ this.loader = loader = X_WebAudio_BufferLoader( this, url ); }; - this.disatcher = disatcher || this; + this.dispatcher = dispatcher || this; this.setState( option ); this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, this.onKill ); @@ -269,6 +314,11 @@ if( X_WebAudio_context ){ } else { loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); }; + + if( X_WebAudio_isNoTouch ){ + X_TEMP.xWebAudioInstances = X_TEMP.xWebAudioInstances || []; + X_TEMP.xWebAudioInstances.push( this ); + }; }, onKill : function(){ @@ -283,6 +333,10 @@ if( X_WebAudio_context ){ //this._onended && X_Closure_correct( this._onended ); this.gainNode && this.gainNode.disconnect(); + + if( X_WebAudio_isNoTouch ){ + X_TEMP.xWebAudioInstances.splice( X_TEMP.xWebAudioInstances.indexOf( this ), 1 ); + }; }, _onLoadBufferComplete : function( e ){ var loader = this.loader, @@ -292,7 +346,7 @@ if( X_WebAudio_context ){ if ( !buffer ) { this.error = loader.errorState; - this.disatcher[ 'dispatch' ]({ + this.dispatcher[ 'dispatch' ]({ type : X_EVENT_ERROR, error : loader.errorState, message : loader.errorState === 1 ? @@ -306,11 +360,11 @@ if( X_WebAudio_context ){ this.audioBuffer = buffer; this.duration = buffer.duration * 1000; - this.disatcher[ 'asyncDispatch' ]( X_WebAudio_touchState ? X_EVENT_MEDIA_WAIT_FOR_TOUCH : X_EVENT_READY ); + this.dispatcher[ 'asyncDispatch' ]( X_WebAudio_isNoTouch ? X_EVENT_MEDIA_WAIT_FOR_TOUCH : X_EVENT_READY ); }, actualPlay : function(){ - var e, begin, end; + var xWebAudio, begin, end; console.log( '[WebAudio] play abuf:' + !!this.audioBuffer ); @@ -318,42 +372,48 @@ if( X_WebAudio_context ){ this._playReserved = true; return; }; - - if( X_WebAudio_touchState ){ - e = X_EventDispatcher_CURRENT_EVENTS[ X_EventDispatcher_CURRENT_EVENTS.length - 1 ]; + + if( X_WebAudio_isNoTouch ){ + //@dev{ + var e = X_EventDispatcher_CURRENT_EVENTS[ X_EventDispatcher_CURRENT_EVENTS.length - 1 ]; if( !e || !e[ 'pointerType' ] ){ // alert( 'タッチイベント以外での play! ' + ( e ? e.type : '' ) ); return; }; + //@} + // http://qiita.com/uupaa/items/e5856e3cb2a9fc8c5507 // iOS9 + touchstart で呼んでいた場合、 X_ViewPort['listenOnce']('pointerup',this,this.actualPlay()) - this.disatcher[ 'asyncDispatch' ]( X_EVENT_READY ); + this.dispatcher[ 'asyncDispatch' ]( X_EVENT_READY ); + + // Web Audio インスタンスが複数生成された場合、一つの Web Audio に対してタッチによる play が開始されたら、 + // 1. 同時に生成された他の X.Audio インスタンスに対して READY イベントを発する + // 2. 以降の X.Audio インスタンス生成時に needTouchForPlay フラグは false + // ちなみに HTML Audio インスタンスは各々にタッチが必要 + //X_WebAudio_isNoTouch = false; + + while( xWebAudio = X_TEMP.xWebAudioInstances.pop() ){ + xWebAudio !== this && xWebAudio[ 'asyncDispatch' ]( X_EVENT_READY ); + }; + delete X_TEMP.xWebAudioInstances; + + X_WebAudio_isNoTouch = false; + + X_TEMP.webAudioSampleRateFix && X_TEMP.webAudioSampleRateFix( this.audioBuffer[ 'sampleRate' ] ); }; - X_WebAudio_touchState = false; end = X_Audio_getEndTime( this ); begin = X_Audio_getStartTime( this, end, true ); console.log( '[WebAudio] play ' + begin + ' -> ' + end + ' loop: ' + this.autoLoop + ' :' + this.loopStartTime + ' -> ' + this.loopEndTime ); - - if( this.bufferSource ) this._sourceDispose(); - if( !this.gainNode ){ - this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ](); - this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] ); - }; - - this.bufferSource = X_WebAudio_context[ 'createBufferSource' ](); - this.bufferSource.buffer = this.audioBuffer; + this._createTree( begin, end ); /* win8.1 Firefox45, win8.1 Chrome48 で動かなくなる... if( this.bufferSource[ 'loop' ] = this.autoLoop ){ this.bufferSource[ 'loopStart' ] = 0 <= this.loopStartTime ? this.loopStartTime / 1000 : begin / 1000; this.bufferSource[ 'loopEnd' ] = 0 <= this.loopEndTime ? this.loopEndTime / 1000 : end / 1000; }; */ - - this.bufferSource[ 'connect' ]( this.gainNode ); - this.gainNode[ 'gain' ].value = this.gain; - + // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1 // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない // 多くのブラウザで onended は timer を使ったカウントより遅いので使わない @@ -364,16 +424,7 @@ if( X_WebAudio_context ){ //this._timerID && X_Timer_remove( this._timerID ); //this._timerID = X_Timer_once( end - begin, this, this._onEnded ); //}; - - if( this.bufferSource.start ){ - this.bufferSource.start( 0, begin / 1000, ( end - begin ) / 1000 ); - } else - if( this.bufferSource[ 'noteOn' ] ){ - this.bufferSource[ 'noteOn' ]( 0, begin / 1000, ( end - begin ) / 1000 ); - } else { - this.bufferSource[ 'noteGrainOn' ]( 0, begin / 1000, ( end - begin ) / 1000 ); - }; - + this.playing = true; this._startPos = begin; this._endPosition = end; @@ -381,20 +432,34 @@ if( X_WebAudio_context ){ this._interval = this._interval || X_Timer_add( 100, 0, this, this._onInterval ); }, + _createTree : function( begin, end ){ + if( this.bufferSource ) this._sourceDispose(); + + if( !this.gainNode ){ + this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ](); + this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] ); + }; + + this.bufferSource = X_WebAudio_context[ 'createBufferSource' ](); + this.bufferSource.buffer = this.audioBuffer; + this.bufferSource[ 'connect' ]( this.gainNode ); + + this.gainNode[ 'gain' ].value = this.gain; + + if( !this.bufferPlay ){ + this.bufferPlay = this.bufferSource.start ? 'start' : this.bufferSource[ 'noteOn' ] ? 'noteOn' : 'noteGrainOn'; + this.bufferStop = this.bufferSource.stop ? 'stop' : 'noteOff'; + }; + // https://developer.mozilla.org/ja/docs/Web/API/AudioBufferSourceNode + // AudioBufferSourceNode.start()の呼び出しは一度しかできません。 + this.bufferSource[ this.bufferPlay ]( 0, begin / 1000, ( end - begin ) / 1000 ); + }, + _sourceDispose : function(){ this.bufferSource.disconnect(); //delete this.bufferSource.onended; delete this.bufferSource; }, - - /* - _onInterval : function(){ - if( !this.playing ){ - delete this._interval; - return X_CALLBACK_UN_LISTEN; - }; - this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); - }, */ _onInterval : function(){ var time; @@ -405,14 +470,14 @@ if( X_WebAudio_context ){ //console.log( '> onEnd ' + ( this.playing && ( X_WebAudio_context.currentTime * 1000 - this._startTime ) ) + ' < ' + ( this._endPosition - this._startPos ) ); if( time < 0 ){ - this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); + this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); return; }; if( this.autoLoop ){ - if( !( this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){ + if( !( this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){ this.looped = true; - this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); this.actualPlay(); } else { delete this._interval; @@ -420,7 +485,7 @@ if( X_WebAudio_context ){ }; } else { this.actualPause(); - this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; }; }, @@ -433,8 +498,7 @@ if( X_WebAudio_context ){ delete this.playing; if( this.bufferSource ){ - this.bufferSource.stop ? - this.bufferSource.stop( 0 ) : this.bufferSource[ 'noteOff' ]( 0 ); + this.bufferSource[ this.bufferStop ]( 0 ); }; },