X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F01_XWebAudio.js;h=1cc3cba7b5cbe5e99845ef6b27f407bcf49d3ac2;hb=4e4ab3be10850546063d4a4b93250ed142bb8cd2;hp=905ffd158bcefce6dd6194c28743ee83a6b34b0a;hpb=0231a4fe0d679b2959968193b0fbeebc44531aaf;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 905ffd1..1cc3cba 100644 --- a/0.6.x/js/07_audio/01_XWebAudio.js +++ b/0.6.x/js/07_audio/01_XWebAudio.js @@ -1,90 +1,144 @@ +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' ] && - !( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) && - ( window.AudioContext || window.webkitAudioContext ), - X_Audio_WebAudioWrapper; + +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' ] < 46 && 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_Audio_WebAudio_context ){ +if( X_WebAudio_context ){ - X_Audio_WebAudio_context = new X_Audio_WebAudio_context; + X_WebAudio_context = new X_WebAudio_context; - function X_Audio_WebAudio_getBuffer( url ){ - var i = 0, l = X_Audio_WRAPPER_LIST.length; - for( i = 0; i < l; ++i ){ - if( X_Audio_WRAPPER_LIST[ i ].url === url ) return X_Audio_WRAPPER_LIST[ i ]; - }; - }; - - X_Audio_WebAudioWrapper = X_EventDispatcher[ 'inherits' ]( - 'X.AV.WebAudioWrapper', + X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ]( + 'X.WebAudio.BufferLoader', X_Class.POOL_OBJECT, { - - url : '', - proxy : null, - - startTime : 0, - endTime : -1, - loopStartTime : -1, - loopEndTime : -1, - seekTime : -1, - duration : 0, - - playing : false, - error : 0, - loop : false, - looped : false, - autoplay : false, - volume : 0.5, - - _startPos : 0, - _endPosition : 0, - _startTime : 0, - _timerID : 0, - _interval : 0, - buffer : null, - source : null, - gainNode : null, - _onended : null, - + audioUrl : '', xhr : null, onDecodeSuccess : null, onDecodeError : null, - Constructor : function( proxy, url, option ){ - var audio = X_Audio_WebAudio_getBuffer( url ); - - this.proxy = proxy; - this.url = url; - - X_AudioWrapper_updateStates( this, option ); - - if( audio && audio.buffer ){ - this._onDecodeSuccess( audio.buffer ); - } else - if( audio ){ - // TODO 当てにしていたaudioがclose 等した場合 - audio.proxy[ 'listenOnce' ]( 'canplaythrough', this, this._onBufferReady ); - } else { - this.xhr = X.Net.xhrGet( url, { type : 'arraybuffer' } ) + 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, X_EVENT_CANCELED ], 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' ); + 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 : - 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 @@ -92,249 +146,327 @@ if( X_Audio_WebAudio_context ){ // 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 ) ); + 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.data, - this.onDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ), - this.onDecodeError = X_Callback_create( this, this._onDecodeError ) ); - } else { - this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) ); + 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_CANCELED : - this.error = 1; - this.proxy[ 'dispatch' ]( 'aborted' ); - break; - case X_EVENT_COMPLETE : - this.error = 2; - this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'xhr error' } ); + 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 ){ - console.log( 'WebAudio decode success!' ); - this.onDecodeSuccess && this._onDecodeComplete(); - if ( !buffer ) { - this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_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.proxy[ 'asyncDispatch' ]( 'loadedmetadata' ); - this.proxy[ 'asyncDispatch' ]( 'loadeddata' ); - this.proxy[ 'asyncDispatch' ]( 'canplay' ); - this.proxy[ 'asyncDispatch' ]( 'canplaythrough' ); - */ - this.proxy[ 'asyncDispatch' ]( X_EVENT_READY ); - - this.autoplay && X_Timer_once( 16, this, this.play ); + 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 : X_EVENT_ERROR, message : 'decode error' } ); + this.errorState = 2; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); }, _onDecodeComplete : function(){ - X_Callback_correct( this.onDecodeSuccess ); + X_Closure_correct( this.onDecodeSuccess ); delete this.onDecodeSuccess; - X_Callback_correct( this.onDecodeError ); + X_Closure_correct( this.onDecodeError ); delete this.onDecodeError; }, - - _onBufferReady : function( e ){ - var audio = X_Audio_WebAudio_getBuffer( this.url ); - this._onDecodeSuccess( audio.buffer ); - }, - close : function(){ - delete this.buffer; + 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' ](); + }; + }; + } + + } + ); + - if( this.xhr ) this.xhr.close(); + 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.onDecodeSuccess ){ - // 回収はあきらめる、、、 + 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 ); + }; + }, + + 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.playing && this.pause(); - this.source && this._sourceDispose(); - - this._onended && X_Callback_correct( this._onended ); + //this._onended && X_Closure_correct( this._onended ); - this.gainNode && this.gainNode.disconnect(); + 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_WAIT_FOR_TOUCH : X_EVENT_READY ); + }, - _sourceDispose : function(){ - this.source.disconnect(); - delete this.source.onended; - delete this.source; - }, - - play : function(){ - var begin, end; + actualPlay : function(){ + var e, begin, end; - if( !this.buffer ){ - this.autoplay = true; + console.log( '[WebAudio] play abuf:' + !!this.audioBuffer ); + + if( !this.audioBuffer ){ + this._playReserved = true; return; }; - end = X_AudioWrapper_getEndTime( this ); - begin = X_AudioWrapper_getStartTime( this, end, true ); + 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; + + end = X_Audio_getEndTime( this ); + begin = X_Audio_getStartTime( this, end, true ); - console.log( '[WebAudio] play ' + begin + ' -> ' + end ); + console.log( '[WebAudio] play ' + begin + ' -> ' + end + ' loop: ' + this.autoLoop + ' :' + this.loopStartTime + ' -> ' + this.loopEndTime ); - if( this.source ) this._sourceDispose(); + 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.source = X_Audio_WebAudio_context.createBufferSource(); - this.source.buffer = this.buffer; - this.source.connect( this.gainNode ); + + this.bufferSource = X_WebAudio_context[ 'createBufferSource' ](); + this.bufferSource.buffer = this.audioBuffer; - this.gainNode.gain.value = this.volume; + /* 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 は使わない - if( false && this.source.onended !== undefined ){ + // 多くのブラウザで 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_remove( this._timerID ); - this._timerID = X_Timer_once( end - 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, end / 1000 ); + 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.source.noteGrainOn( 0, begin / 1000, end / 1000 ); + this.bufferSource[ 'noteGrainOn' ]( 0, begin / 1000, ( end - begin ) / 1000 ); }; 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 ); + this._startTime = X_WebAudio_context.currentTime * 1000; + this._interval = this._interval || X_Timer_add( 100, 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' ]( X_EVENT_MEDIA_PLAYING ); - }, - - _onEnded : function(){ + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); + }, */ + + _onInterval : function(){ var time; - 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 ) ); - 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 + - ' from:' + this._startPos + ' to:' + this._endPosition ); - this._timerID = X_Timer_once( -time, this, this._onEnded ); - return; - }; - }; + // TODO 再生中に終了時間だけ変えた場合! + 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( time < 0 ){ + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); + return; + }; - if( this.loop ){ - if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + if( this.autoLoop ){ + if( !( this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){ this.looped = true; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); - this.play(); + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.actualPlay(); + } else { + delete this._interval; + return X_CALLBACK_UN_LISTEN; }; } else { - this.pause(); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + this.actualPause(); + this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; }; }, - pause : function(){ - if( !this.playing ) return this; - + actualPause : function(){ console.log( '[WebAudio] pause' ); - this.seekTime = this.state().currentTime; - - this._timerID && X_Timer_remove( this._timerID ); - delete this._timerID; + this._interval && X_Timer_remove( this._interval ); + delete this._interval; delete this.playing; - if( this.source ){ - if( this.source.onended ) delete this.source.onended; - - this.source.stop ? - this.source.stop( 0 ) : this.source.noteOff( 0 ); + if( this.bufferSource ){ + this.bufferSource.stop ? + this.bufferSource.stop( 0 ) : this.bufferSource[ 'noteOff' ]( 0 ); }; }, - - state : function( obj ){ - var result; - - if( obj === undefined ){ - return { - startTime : this.startTime, - endTime : this.endTime < 0 ? this.duration : this.endTime, - loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime, - loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime, - loop : this.loop, - looped : this.looped, - volume : this.volume, - playing : this.playing, - duration : this.duration, - - currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0 ) : this.seekTime, - error : this.error - }; - }; - result = X_AudioWrapper_updateStates( this, obj ); - + getActualCurrentTime : function(){ + return X_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0; + }, + + afterUpdateState : function( result ){ if( result & 2 || result & 1 ){ // seek - this.play(); + this.actualPlay(); } else if( result & 4 ){ - this.gainNode.gain.value = this.volume; + this.gainNode[ 'gain' ].value = this.gain; }; } } ); - X_Audio_BACKENDS.push( { - backendName : 'Web Audio', + backendID : 1, + + backendName : 'WebAudio', + + canPlay : X_Audio_codecs, - // - detect : function( proxy, source, ext ){ + detect : function( proxy, ext /* hash */ ){ proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } ); }, - klass : X_Audio_WebAudioWrapper + klass : X_WebAudio } ); };