X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F01_XWebAudio.js;h=5e9729983e94853f23eb2e1e65ef42c14fd2e184;hb=512e08f4d38eab417f9651277e8a50c08535cb07;hp=82992296e331ec7077e1341c0eb1110aa3e20f51;hpb=9052863126b700b4a60d22b95532b4efedd055d0;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 8299229..5e97299 100644 --- a/0.6.x/js/07_audio/01_XWebAudio.js +++ b/0.6.x/js/07_audio/01_XWebAudio.js @@ -1,283 +1,466 @@ +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' ], -var X_Audio_WebAudio_context = window.webkitAudioContext || window.AudioContext, - X_Audio_WebAudio_LIVE_LIST = [], - X_Audio_WebAudio_POOL_LIST = [], - X_Audio_WebAudio, X_Audio_WebAudioWrapper, X_Audio_rawAudio; + X_Audio_codecs; -if( X_Audio_WebAudio_context ){ - - X_Audio_WebAudio_context = new X_Audio_WebAudio_context; +if( X_Audio_constructor ){ + //http://himaxoff.blog111.fc2.com/blog-entry-97.html + //引数なしで new Audio() とすると、Operaでエラーになるそうなので注意。 + X_TEMP.rawAudio = new X_Audio_constructor( '' ); - function getWebAudioWrapper( proxy ){ - var i = X_Audio_WebAudio_LIVE_LIST.length; - for( ; i; ){ - if( X_Audio_WebAudio_LIVE_LIST[ --i ].proxy === proxy ) return X_Audio_WebAudio_LIVE_LIST[ i ]; + // 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 ); }; - X_Audio_WebAudio = - { - backendName : 'Web Audio', + if( X_Audio_blinkOperaFix ){ + X_Audio_constructor = null; + delete X_TEMP.rawAudio; + }; +}; - detect : function( proxy, source, ext ){ - var ok = ext === 'mp3' || ext === 'ogg'; - - proxy.asyncDispatch( ok ? 'support' : 'nosupport' ); - }, - - register : function( proxy, source, option ){ - X_Audio_WebAudio_LIVE_LIST.push( new X_Audio_WebAudioWrapper( proxy, source, option ) ); - }, - - close : function( proxy ){ - return getWebAudioWrapper( proxy ).close(); - }, - - play : function( proxy, startTime, endTime, loop, loopStartTime ){ - return getWebAudioWrapper( proxy ).play( startTime, endTime, loop, loopStartTime ); - }, - - pause : function( proxy ){ - return getWebAudioWrapper( proxy ).pause(); - }, - - state : function( proxy, obj ){ - return getWebAudioWrapper( proxy ).state( obj ); - } - }; + +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' ] && + // Android2 + Gecko で WebAudio が極めて不安定 + !( X_UA[ 'Fennec' ] && X_UA[ 'Android' ] < 3 ) && + // AOSP でも WebAudio を不完全に実装するものがある, touch の有無も不明のため一律に切ってしまう + !X_UA[ 'AOSP' ] && !( X_UA[ 'ChromeWV' ] < 5 ) && + // Blink HTMLAudio 調査用 + //!X_UA[ 'Blink' ] && + // Firefox40.0.5 + Windows8 で音声が途中から鳴らなくなる + // Firefox41.0.1 + Windows8 で音声が途中から鳴らなくなる + !( 40 <= X_UA[ 'Gecko' ] && X_UA[ 'Gecko' ] < 45 && X_UA[ 'Windows' ] ) && + ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] ), + X_WebAudio_BUFFER_LIST = [], + X_WebAudio_need1stTouch = X_UA[ 'iOS' ], + X_WebAudio_touchState = X_WebAudio_need1stTouch, + X_WebAudio, + X_WebAudio_BufferLoader, + X_WebAudio_fpsFix; + +/* + * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可 + */ +if( X_WebAudio_context ){ - X_Audio_BACKENDS.push( X_Audio_WebAudio ); + X_WebAudio_context = new X_WebAudio_context; - X_Audio_WebAudioWrapper = X.EventDispatcher.inherits( - 'X.AV.WebAudioWrapper', - X.Class.POOL_OBJECT, + X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ]( + 'X.WebAudio.BufferLoader', + X_Class.POOL_OBJECT, { - - proxy : null, - - startTime : 0, - endTime : 0, - loopStartTime : 0, - seekTime : 0, - duration : 0, - - playing : false, - error : 0, - loop : false, - volume : 0.5, - - _startTime : 0, - _playTime : 0, - _timerID : 0, - _interval : 0, - buffer : null, - gainNode : null, - _onended : null, - + audioUrl : '', xhr : null, onDecodeSuccess : null, onDecodeError : null, - Constructor : function( proxy, source, option ){ - this.closed = false; - this.proxy = proxy; - this.xhr = X.Net.xhrGet( source, 'arraybuffer' ) - .listen( X.Event.PROGRESS, this ) - .listenOnce( [ X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this ); - X_AudioWrapper_updateStates( this, option ); + audioBuffer : null, + errorState : 0, + webAudioList : null, + + 'Constructor' : function( webAudio, url ){ + this.webAudioList = [ webAudio ]; + 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 ){ + var i, l; + 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 : + for( i = 0, l = this.webAudioList.length; i < l; ++i ){ + this.webAudioList[ i ][ 'dispatch' ]( { type : X_EVENT_PROGRESS, 'percent' : e[ 'percent' ] } ); + }; return; - case X.Event.SUCCESS : - X_Audio_WebAudio_context.decodeAudioData( e.data, - this.callbackDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ), - this.callbackDecodeError = X_Callback_create( this, this._onDecodeError ) ); - break; - - case X.Event.CANCELED : - this.error = 1; - this.proxy.dispatch( 'aborted' ); + case X_EVENT_SUCCESS : + // 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_UA[ 'iOS' ] < 8 || !X_WebAudio_context[ 'decodeAudioData' ] ){ + this._onDecodeSuccess( X_WebAudio_context[ 'createBuffer' ]( e.response, false ) ); + } else + 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 = 2; - this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } ); + case X_EVENT_COMPLETE : + this.errorState = 1; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); 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 ], this ); delete this.xhr; }, _onDecodeSuccess : function( buffer ){ - this._onDecodeComplete(); + this.onDecodeSuccess && this._onDecodeComplete(); - if ( !buffer ) { - this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } ); + if( !buffer ){ + this.errorState = 2; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); return; }; - - this.buffer = buffer; - this.duration = buffer.duration * 1000; - this.endTime = this.endTime || this.duration; - this.proxy.asyncDispatch( 'loadedmetadata' ); - this.proxy.asyncDispatch( 'loadeddata' ); - this.proxy.asyncDispatch( 'canplay' ); - this.proxy.asyncDispatch( 'canplaythrough' ); + console.log( 'WebAudio decode success!' ); + + this.audioBuffer = buffer; + + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); + + console.log( 'WebAudio decoded!' ); }, _onDecodeError : function(){ + console.log( 'WebAudio decode error!' ); this._onDecodeComplete(); - this.error = 3; - this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } ); + this.errorState = 2; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); }, _onDecodeComplete : function(){ - X_Callback_correct( this.callbackDecodeSuccess ); - delete this.callbackDecodeSuccess; - X_Callback_correct( this.callbackDecodeError ); - delete this.callbackDecodeError; + X_Closure_correct( this.onDecodeSuccess ); + delete this.onDecodeSuccess; + X_Closure_correct( this.onDecodeError ); + delete this.onDecodeError; }, - close : function(){ - delete this.buffer; - - this.playing && this.pause(); - this.source && this._sourceDispose(); + unregister : function( webAudio ){ + var list = this.webAudioList, + i = list.indexOf( webAudio ); + + if( 0 < i ){ + list.splice( i, 1 ); + if( !list.length ){ + this.xhr && this.xhr[ 'kill' ](); + this[ 'kill' ](); + }; + }; + } + + } + ); - this._onended && X_Callback_correct( this._onended ); - this.gainNode && this.gainNode.disconnect(); + X_WebAudio = X_AudioBase[ 'inherits' ]( + 'X.WebAudio', + X_Class.POOL_OBJECT, + { + + loader : null, + + _startPos : 0, + _endPosition : 0, + _startTime : 0, + _timerID : 0, + _interval : 0, + audioBuffer : null, + bufferSource : null, + gainNode : null, + _onended : null, + + 'Constructor' : function( disatcher, url, option ){ + var i = 0, + l = X_WebAudio_BUFFER_LIST.length, + loader; + + /* + * http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1 + * L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。 + * 描画レート(描画 FPS)が下がるとノイズが混ざり始め、レートを上げると再生結果が正常になるというもので、オーディオ処理が描画スレッドに巻き込まれているような動作を見せる。 + */ + if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] && !X_WebAudio_fpsFix ){ + X_Node_systemNode.create( 'div', { id : 'fps-slowdown-make-sound-noisy' } ); + X_WebAudio_fpsFix = true; + }; + + for( ; i < l; ++i ){ + loader = X_WebAudio_BUFFER_LIST[ i ]; + if( loader.audioUrl === url ){ + this.loader = loader; + loader.webAudioList.push( this ); + break; + }; + }; + + if( !this.loader ){ + this.loader = loader = X_WebAudio_BufferLoader( this, url ); + }; + + this.disatcher = disatcher || this; + this.setState( option ); + + this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, this.onKill ); + + if( loader.audioBuffer || loader.errorState ){ + this._onLoadBufferComplete(); + } else { + loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); + }; }, - _sourceDispose : function(){ - this.source && this.source.disconnect(); - delete this.source.onended; - delete this.source; - }, + onKill : function(){ + this.loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ) + .unregister( this ); + + delete this.audioBuffer; + + this.playing && this.actualPause(); + this.bufferSource && this._sourceDispose(); + + this._onended && X_Closure_correct( this._onended ); + + this.gainNode && this.gainNode.disconnect(); + }, + _onLoadBufferComplete : function( e ){ + var loader = this.loader, + buffer = loader.audioBuffer; + + e && loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); + + if ( !buffer ) { + this.error = loader.errorState; + this.disatcher[ 'dispatch' ]({ + type : X_EVENT_ERROR, + error : loader.errorState, + message : loader.errorState === 1 ? + 'load buffer network error' : + 'buffer decode error' + }); + this[ 'kill' ](); + return; + }; + + this.audioBuffer = buffer; + this.duration = buffer.duration * 1000; + + this.disatcher[ 'asyncDispatch' ]( X_WebAudio_touchState ? X_EVENT_MEDIA_TOUCH_FOR_LOAD : X_EVENT_READY ); + }, - play : function( seekTime ){ - var begin; + actualPlay : function(){ + var e, begin, end; - if( !this.buffer ) return this; + console.log( '[WebAudio] play abuf:' + !!this.audioBuffer ); - begin = ( seekTime || seekTime === 0 ) ? seekTime : this.playing ? this.loopStartTime : this.startTime; + if( !this.audioBuffer ){ + this._playReserved = true; + return; + }; + + if( X_WebAudio_touchState ){ + 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 ); + }; + X_WebAudio_touchState = false; - console.log( '[WebAudio] play' ); + end = X_Audio_getEndTime( this ); + begin = X_Audio_getStartTime( this, end, true ); - if( this.source ) this._sourceDispose(); - if( !this.gainNode ) this.gainNode = X_Audio_WebAudio_context.createGain(); + console.log( '[WebAudio] play ' + begin + ' -> ' + end ); - this.source = X_Audio_WebAudio_context.createBufferSource(); - this.source.buffer = this.buffer; - this.source.connect( this.gainNode ); - - this.gainNode.connect( X_Audio_WebAudio_context.destination ); - this.gainNode.gain.value = this.volume; + 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._timerID && X.Timer.remove( this._timerID ); + this.gainNode[ 'gain' ].value = this.gain; - // おかしい、stop 前に外していても呼ばれる、、、@Firefox - if( this.source.onended !== undefined ){ + // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1 + // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない + // 多くのブラウザで onended は timer を使ったカウントより遅いので使わない + //if( this.bufferSource.onended !== undefined ){ //console.log( '> use onended' ); - this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) ); - } else { - this._timerID = X.Timer.once( this.endTime - begin, this, this._onEnded ); - }; + //this.bufferSource.onended = this._onended || ( this._onended = X_Closure_create( this, this._onEnded ) ); + //} else { + this._timerID && X_Timer_remove( this._timerID ); + this._timerID = X_Timer_once( end - begin, this, this._onEnded ); + //}; - if( this.source.start ){ - this.source.start( 0, begin / 1000, this.endTime / 1000 ); + if( this.bufferSource.start ){ + this.bufferSource.start( 0, begin / 1000, end / 1000 ); } else { - this.source.noteGrainOn( 0, begin / 1000, this.endTime / 1000 ); + this.bufferSource[ 'noteGrainOn' ]( 0, begin / 1000, end / 1000 ); }; - this.playing = true; - this._startTime = begin; - 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_WebAudio_context.currentTime * 1000; + this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval ); }, + + _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; + return X_CALLBACK_UN_LISTEN; }; - this.proxy.dispatch( 'timeupdate' ); + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); }, _onEnded : function(){ + var time; delete this._timerID; + if( this.playing ){ - console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this.endTime - this._startTime ) ); - // Firefox 用の対策,,, - if( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime < this.endTime - this._startTime ) return; + 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_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.play(); + if( this.autoLoop ){ + if( !( this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){ + this.looped = true; + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.actualPlay(); + }; } else { - this.pause(); - this.proxy.dispatch( 'ended' ); + this.actualPause(); + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; }; }, - pause : function(){ - if( !this.playing ) return this; - + actualPause : function(){ console.log( '[WebAudio] pause' ); - this._timerID && X.Timer.remove( this._timerID ); + this._timerID && X_Timer_remove( this._timerID ); delete this._timerID; delete this.playing; - if( this.source ){ - if( this.source.onended ) delete this.source.onended; + if( this.bufferSource ){ + //if( this.bufferSource.onended ) delete this.bufferSource.onended; - this.source.stop ? - this.source.stop( 0 ) : this.source.noteOff( 0 ); + this.bufferSource.stop ? + this.bufferSource.stop( 0 ) : this.bufferSource[ 'noteOff' ]( 0 ); }; }, - - state : function( obj ){ - var time = this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) : 0, - result, halfway; - - if( obj === undefined ){ - return { - startTime : this.startTime, - endTime : this.endTime, - loopStartTime : this.loopStartTime, - currentTime : time + this._startTime, - loop : this.loop, - volume : this.volume, - error : this.error, - playing : this.playing, - duration : this.duration - }; - }; - result = X_AudioWrapper_updateStates( this, obj ); - - if( result & 2 ){ // seek - this.play( this.seekTime ); - delete this.seekTime; - } else - if( result & 1 ){ - this.play(); + getActualCurrentTime : function(){ + return X_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0; + }, + + afterUpdateState : function( result ){ + if( result & 2 || result & 1 ){ // seek + this.actualPlay(); } else if( result & 4 ){ - this.gainNode.gain.value = this.volume; + this.gainNode[ 'gain' ].value = this.gain; }; } } ); + X_Audio_BACKENDS.push( + { + backendID : 1, + + backendName : 'WebAudio', + + canPlay : X_Audio_codecs, + + detect : function( proxy, source, ext ){ + proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } ); + }, + + klass : X_WebAudio + } + ); };