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_WebAudio_context      =	// 4s 以下ではない iPad 2G または iPad mini 1G 以下ではない, iPod touch 4G 以下ではない
								!X_UA[ 'iPhone_4s' ]  && !X_UA[ 'iPad_2Mini1' ]  && !X_UA[ 'iPod_4' ]  &&
								// 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' ] < 43 && 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_WebAudio_context = new X_WebAudio_context;
	
	X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ](
		'X.WebAudio.BufferLoader',
		X_Class.POOL_OBJECT,
		{
			audioUrl        : '',
            xhr             : null,
            onDecodeSuccess : null,
            onDecodeError   : null,
            
            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 :
						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 :
					// 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.errorState = 1;				
						this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
						break;
				};
				this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this );
				delete this.xhr;
			},
			
				_onDecodeSuccess : function( buffer ){
					this.onDecodeSuccess && this._onDecodeComplete();
					
	                if ( !buffer ) {
	                	this.errorState = 2;
	                    this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
	                    return;
	                };
	                
	                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.errorState = 2;
					this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
				},
				
				_onDecodeComplete : function(){
					X_Closure_correct( this.onDecodeSuccess );
					delete this.onDecodeSuccess;
					X_Closure_correct( this.onDecodeError );
					delete this.onDecodeError;
				},
			
			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' ]();
					};
				};
			}
			
		}
	);
	
	
	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 );
				};
			},
			
			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 );
				},
			
			actualPlay : function(){
				var e, begin, end;
				
				console.log( '[WebAudio] play abuf:' + !!this.audioBuffer );
				
	            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;
				
				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_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;
	            
	            // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
	            // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない
	            // 多くのブラウザで onended は timer を使ったカウントより遅いので使わない
                //if( this.bufferSource.onended !== undefined ){
                	//console.log( '> use 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.bufferSource.start ){
	                this.bufferSource.start( 0, begin / 1000, end / 1000 );
	            } else {
	                this.bufferSource[ 'noteGrainOn' ]( 0, begin / 1000, end / 1000 );
	            };
	            
	            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;
					};
					this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
				},
						
				_onEnded : function(){
					var time;
					delete this._timerID;
					
		            if( this.playing ){
		            	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.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.actualPause();
		            		this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
		            	};
		            };
				},
			
			actualPause : function(){
				console.log( '[WebAudio] pause' );
				
	            this._timerID && X_Timer_remove( this._timerID );
				delete this._timerID;
				delete this.playing;

	            if( this.bufferSource ){
	            	//if( this.bufferSource.onended ) delete this.bufferSource.onended;
	            	
	                this.bufferSource.stop ? 
	                	this.bufferSource.stop( 0 ) : this.bufferSource[ 'noteOff' ]( 0 );
	            };
			},
			
			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.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
		}
	);
};
