X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F01_XWebAudio.js;h=8a5e1eda629ba8d52f911d3118b77a59d0518bd8;hb=d836e6243878426d4cfd7a14ceb9b77db9f92b57;hp=1a606243599c1535a1e821e87e7586839799a129;hpb=3256f11c856314a1425b8459b9d000a88caf8258;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 1a60624..8a5e1ed 100644 --- a/0.6.x/js/07_audio/01_XWebAudio.js +++ b/0.6.x/js/07_audio/01_XWebAudio.js @@ -1,8 +1,13 @@ -var X_Audio_WebAudio_context = window.webkitAudioContext || window.AudioContext, +var X_Audio_WebAudio_context = !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] && + !( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) && + ( window.AudioContext || window.webkitAudioContext ), X_Audio_WebAudioWrapper; -if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_context ){ +/* + * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可 + */ +if( X_Audio_WebAudio_context ){ X_Audio_WebAudio_context = new X_Audio_WebAudio_context; @@ -13,9 +18,9 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con }; }; - X_Audio_WebAudioWrapper = X.EventDispatcher.inherits( + X_Audio_WebAudioWrapper = X_EventDispatcher[ 'inherits' ]( 'X.AV.WebAudioWrapper', - X.Class.POOL_OBJECT, + X_Class.POOL_OBJECT, { url : '', @@ -27,7 +32,7 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con loopEndTime : -1, seekTime : -1, duration : 0, - + playing : false, error : 0, loop : false, @@ -35,9 +40,9 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con autoplay : false, volume : 0.5, + _startPos : 0, + _endPosition : 0, _startTime : 0, - _endTime : 0, - _playTime : 0, _timerID : 0, _interval : 0, buffer : null, @@ -52,9 +57,8 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con Constructor : function( proxy, url, option ){ var audio = X_Audio_WebAudio_getBuffer( url ); - this.url = url; - this.closed = false; this.proxy = proxy; + this.url = url; X_AudioWrapper_updateStates( this, option ); @@ -63,26 +67,34 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con } else if( audio ){ // TODO 当てにしていたaudioがclose 等した場合 - audio.proxy.listenOnce( 'canplaythrough', this, this._onBufferReady ); + audio.proxy[ 'listenOnce' ]( 'canplaythrough', this, this._onBufferReady ); } else { - this.xhr = X.Net.xhrGet( url, 'arraybuffer' ) - .listen( X.Event.PROGRESS, this ) - .listenOnce( [ X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this ); + this.xhr = X.Net.xhrGet( url, { type : 'arraybuffer' } ) + [ 'listen' ]( X_EVENT_PROGRESS, this ) + [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this ); }; }, handleEvent : function( e ){ switch( e.type ){ - case X.Event.PROGRESS : - e.percent ? - this.proxy.dispatch( { type : 'progress', percent : e.percent } ) : - this.proxy.dispatch( 'loadstart' ); + case X_EVENT_PROGRESS : + e[ 'percent' ] ? + this.proxy[ 'dispatch' ]( { type : 'progress', 'percent' : e[ 'percent' ] } ) : + this.proxy[ 'dispatch' ]( 'loadstart' ); return; - case X.Event.SUCCESS : - console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData ); + case X_EVENT_SUCCESS : + console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData + ' t:' + typeof e.data ); // TODO 旧api // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext + + // http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1 + // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と + // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。 + // ただし iOS 8.1.2 では エラーになる + if( X_Audio_WebAudio_context.createBuffer && X_UA[ 'iOS' ] < 8 ){ + this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) ); + } else if( X_Audio_WebAudio_context.decodeAudioData ){ X_Audio_WebAudio_context.decodeAudioData( e.data, this.onDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ), @@ -92,17 +104,17 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con }; break; - case X.Event.CANCELED : + case X_EVENT_CANCELED : this.error = 1; - this.proxy.dispatch( 'aborted' ); + this.proxy[ 'dispatch' ]( 'aborted' ); break; - case X.Event.COMPLETE : + case X_EVENT_COMPLETE : this.error = 2; - this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } ); + this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'xhr error' } ); break; }; - this.xhr.unlisten( [ X.Event.PROGRESS, X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this ); + this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this ); delete this.xhr; }, @@ -112,19 +124,21 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con this.onDecodeSuccess && this._onDecodeComplete(); if ( !buffer ) { - this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } ); + this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'buffer is ' + buffer } ); return; }; this.buffer = buffer; this.duration = buffer.duration * 1000; + /* + this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' ); + this.proxy[ 'asyncDispatch' ]( 'loadeddata' ); + this.proxy[ 'asyncDispatch' ]( 'canplay' ); + this.proxy[ 'asyncDispatch' ]( 'canplaythrough' ); + */ + this.proxy[ 'asyncDispatch' ]( X_EVENT_READY ); - this.proxy.asyncDispatch( 'loadedmetadata' ); - this.proxy.asyncDispatch( 'loadeddata' ); - this.proxy.asyncDispatch( 'canplay' ); - this.proxy.asyncDispatch( 'canplaythrough' ); - - this.autoplay && X.Timer.once( 16, this, this.play ); + this.autoplay && X_Timer_once( 16, this, this.play ); console.log( 'WebAudio decoded!' ); }, @@ -133,7 +147,7 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con console.log( 'WebAudio decode error!' ); this._onDecodeComplete(); this.error = 3; - this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } ); + this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'decode error' } ); }, _onDecodeComplete : function(){ @@ -196,13 +210,13 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con this.gainNode.gain.value = this.volume; // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1 - // 破棄された X.Callback が呼ばれて、obj._() でエラーになる。Firefox では、onended は使わない + // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない if( false && this.source.onended !== undefined ){ //console.log( '> use onended' ); this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) ); } else { - this._timerID && X.Timer.remove( this._timerID ); - this._timerID = X.Timer.once( end - begin, this, this._onEnded ); + this._timerID && X_Timer_remove( this._timerID ); + this._timerID = X_Timer_once( end - begin, this, this._onEnded ); }; if( this.source.start ){ @@ -211,11 +225,11 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con this.source.noteGrainOn( 0, begin / 1000, end / 1000 ); }; - this.playing = true; - this._startTime = begin; - this._endTime = end; - this._playTime = X_Audio_WebAudio_context.currentTime * 1000; - this._interval = this._interval || X.Timer.add( 1000, 0, this, this._onInterval ); + this.playing = true; + this._startPos = begin; + this._endPosition = end; + this._startTime = X_Audio_WebAudio_context.currentTime * 1000; + this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval ); }, _onInterval : function(){ @@ -223,7 +237,7 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con delete this._interval; return X_Callback_UN_LISTEN; }; - this.proxy.dispatch( 'timeupdate' ); + this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); }, _onEnded : function(){ @@ -231,25 +245,29 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con delete this._timerID; if( this.playing ){ - time = X_Audio_WebAudio_context.currentTime * 1000 - this._playTime - this._endTime + this._startTime | 0; - //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this._endTime - this._startTime ) ); + 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 ) ); if( this._onended ){ // Firefox 用の対策,,, if( time < 0 ) return; } else { if( time < 0 ){ - console.log( '> onEnd ' + ( -time ) + ' start:' + this._startTime + '-' + this._endTime ); - this._timerID = X.Timer.once( -time, this, this._onEnded ); + console.log( '> onEnd crt:' + ( X_Audio_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime + + ' from:' + this._startPos + ' to:' + this._endPosition ); + this._timerID = X_Timer_once( -time, this, this._onEnded ); return; }; }; if( this.loop ){ - this.looped = true; - ( this.proxy.dispatch( 'looped' ) | X.Callback.PREVENT_DEFAULT ) || this.play(); + if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + this.looped = true; + this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.play(); + }; } else { this.pause(); - this.proxy.dispatch( 'ended' ); + this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; }; }, @@ -261,7 +279,7 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con this.seekTime = this.state().currentTime; - this._timerID && X.Timer.remove( this._timerID ); + this._timerID && X_Timer_remove( this._timerID ); delete this._timerID; delete this.playing; @@ -288,7 +306,7 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con playing : this.playing, duration : this.duration, - currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime + this._startTime | 0 ) : this.seekTime, + currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0 ) : this.seekTime, error : this.error }; }; @@ -305,19 +323,26 @@ if( !X_UA.iPhone_4s && !X_UA.iPod_2Mini1 && !X_UA.iPod_4 && X_Audio_WebAudio_con } ); - + + /* + * http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1 + * L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。 + * 描画レート(描画 FPS)が下がるとノイズが混ざり始め、レートを上げると再生結果が正常になるというもので、オーディオ処理が描画スレッドに巻き込まれているような動作を見せる。 + */ + if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] ){ + X_Node_systemNode.create( 'div', { id : 'fps-slowdown-make-sound-noisy' } ); + }; + X_Audio_BACKENDS.push( { backendName : 'Web Audio', + // detect : function( proxy, source, ext ){ - var ok = ext === 'mp3' || ext === 'ogg'; - - proxy.asyncDispatch( ok ? 'support' : 'nosupport' ); + proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } ); }, klass : X_Audio_WebAudioWrapper } ); - };