OSDN Git Service

Fix the bug of X.NodeAnime.
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 01_XWebAudio.js
1 var X_Audio_constructor = 3.1 <= X_UA[ 'Safari' ] && X_UA[ 'Safari' ] < 4 ?
2                                                                 function( s, a ){
3                                                                         //a = document.createElement( 'audio' );
4                                                                         //a.src = s;
5                                                                         //a.load();
6                                                                         return a;
7                                                                 } :
8                                                 // Android1.6 + MobileOpera12 HTMLAudio はいるが呼ぶとクラッシュする
9                                                   !( X_UA[ 'Android' ] < 2 ) ?
10                                                                 window[ 'Audio' ] || window.HTMLAudioElement : null,
11         
12         // Blink5 Opera32 Win8 は HTMLAudio が壊れている、WebAudio は mp3 がデコードに失敗、ogg が動作
13         X_Audio_blinkOperaFix = X_UA[ 'BlinkOpera' ] && X_UA[ 'Windows' ],
14
15         X_Audio_codecs;
16
17 // WebAudioAPIを使っているはずなのに、マナーモードで音が出る!?
18 // http://qiita.com/gonshi_com/items/e41dbb80f5eb4c176108
19 // HTML Audio、もしくはHTML Videoをページ内で1つでも使用していた場合、そのページでは WebAudioAPI の音がマナーモード時にも鳴ってしまう
20
21 if( X_Audio_constructor ){
22         //http://himaxoff.blog111.fc2.com/blog-entry-97.html
23         //引数なしで new Audio() とすると、Operaでエラーになるそうなので注意。
24         X_TEMP.rawAudio = new X_Audio_constructor( '' );
25         
26         // https://html5experts.jp/miyuki-baba/3766/
27         // TODO Chrome for Android31 で HE-AAC が低速再生されるバグ
28         // TODO Android4 標準ブラウザで ogg のシークが正しくない!
29         if( X_TEMP.rawAudio.canPlayType ){
30                 X_Audio_codecs = {
31                   'mp3'  : X_TEMP.rawAudio.canPlayType('audio/mpeg'),
32                   'opus' : X_TEMP.rawAudio.canPlayType('audio/ogg; codecs="opus"'),
33                   'ogg'  : X_TEMP.rawAudio.canPlayType('audio/ogg; codecs="vorbis"'),
34                   'wav'  : X_TEMP.rawAudio.canPlayType('audio/wav; codecs="1"'),
35                   'aac'  : X_TEMP.rawAudio.canPlayType('audio/aac'),
36                   'm4a'  : X_TEMP.rawAudio.canPlayType('audio/x-m4a') + X_TEMP.rawAudio.canPlayType('audio/m4a') + X_TEMP.rawAudio.canPlayType('audio/aac'),
37                   'mp4'  : X_TEMP.rawAudio.canPlayType('audio/x-mp4') + X_TEMP.rawAudio.canPlayType('audio/mp4') + X_TEMP.rawAudio.canPlayType('audio/aac'),
38                   'weba' : X_TEMP.rawAudio.canPlayType('audio/webm; codecs="vorbis"')
39                 };
40                 (function( X_Audio_codecs, k, v ){
41                         for( k in X_Audio_codecs ){
42                                 //if( X_EMPTY_OBJECT[ k ] ) continue;
43                                 v = X_Audio_codecs[ k ];
44                                 v = v && !!( v.split( 'no' ).join( '' ) );
45                                 if( v ){
46                                         console.log( k + ' ' + X_Audio_codecs[ k ] );
47                                         X_Audio_codecs[ k ] = true;
48                                 } else {
49                                         delete X_Audio_codecs[ k ];
50                                 };
51                         };
52                         if( X_Audio_blinkOperaFix ) delete X_Audio_codecs[ 'mp3' ];
53                 })( X_Audio_codecs );
54         } else {
55                 // iOS3.2.3
56                 X_Audio_codecs = {
57                   'mp3'  : X_UA[ 'IE' ] || X_UA[ 'Chrome' ] || ( X_UA[ 'Windows' ] && X_UA[ 'Safari' ]  ),
58                   'ogg'  : 5 <= X_UA[ 'Gecko' ] || X_UA[ 'Chrome' ] || X_UA[ 'Opera' ] ,
59                   'wav'  : X_UA[ 'Gecko' ] || X_UA[ 'Opera' ] || ( X_UA[ 'Windows' ] && X_UA[ 'Safari' ]  ),
60                   'aac'  : X_UA[ 'IE' ] || X_UA[ 'WebKit' ],
61                   'm4a'  : X_UA[ 'IE' ] || X_UA[ 'WebKit' ],
62                   'mp4'  : X_UA[ 'IE' ] || X_UA[ 'WebKit' ],
63                   'weba' : 2 <= X_UA[ 'Gecko' ] || 10.6 <= X_UA[ 'Opera' ] // firefox4+(Gecko2+)
64                 };
65                 (function( X_Audio_codecs, k ){
66                         for( k in X_Audio_codecs ){
67                                 //if( X_EMPTY_OBJECT[ k ] ) continue;
68                                 if( X_Audio_codecs[ k ] ){
69                                         console.log( k + ' ' + X_Audio_codecs[ k ] );
70                                         X_Audio_codecs[ k ] = true;
71                                 } else {
72                                         delete X_Audio_codecs[ k ];
73                                 };
74                         };
75                 })( X_Audio_codecs );
76         };
77         
78         if( X_Audio_blinkOperaFix ){
79                 X_Audio_constructor = null;
80                 delete X_TEMP.rawAudio;
81         };
82 };
83
84
85 var X_WebAudio_Context      =   // 4s 以下ではない iPad 2G または iPad mini 1G 以下ではない, iPod touch 4G 以下ではない
86                                                                 !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] &&
87                                                                 // iOS7 以上で HTML Audio が鳴らない問題を見ていくよ
88                                                                 // !X_UA[ 'iOS' ] &&
89                                                                 // Android2 + Gecko で WebAudio が極めて不安定
90                                                                 !( X_UA[ 'Fennec' ] && X_UA[ 'Android' ] < 3 ) &&
91                                                                 // AOSP でも WebAudio を不完全に実装するものがある, touch の有無も不明のため一律に切ってしまう
92                                                                 !X_UA[ 'AOSP' ] && !( X_UA[ 'ChromeWV' ] < 5 ) &&
93                                                                 // Blink HTMLAudio 調査用
94                                                                 //!X_UA[ 'Blink' ] &&
95                                                                 // Firefox40.0.5 + Windows8 で音声が途中から鳴らなくなる
96                                                                 // Firefox41.0.1 + Windows8 で音声が途中から鳴らなくなる
97                                                                 !( 40 <= X_UA[ 'Gecko' ] && /* X_UA[ 'Gecko' ] < 48 && */ X_UA[ 'Windows' ] ) &&
98                                                                 ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] || window[ 'mozAudioContext' ] ),          
99         X_WebAudio_context,
100         X_WebAudio_BUFFER_LIST  = [],
101         X_WebAudio_need1stTouch = X_UA[ 'iOS' ],
102         X_WebAudio_isNoTouch    = X_WebAudio_need1stTouch,
103         X_WebAudio_needRateFix  = X_WebAudio_need1stTouch,
104         X_WebAudio,
105         X_WebAudio_BufferLoader,
106         X_WebAudio_fpsFix;
107
108 /*
109  * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可
110  */
111 if( X_WebAudio_Context ){
112         
113         X_WebAudio_context = new X_WebAudio_Context;
114         
115         // http://lilting.ch/3323.html
116         // 【間に合わせ】iOS9系でのWebAudioの音割れ対処について
117         /*
118         if( X_WebAudio_needRateFix ){
119                 X_WebAudio_context.close();
120                 X_WebAudio_context = new X_WebAudio_Context;
121         }; */
122
123         /*
124          * TODO X_TEMP へ
125          * http://qiita.com/simiraaaa/items/79a9ac972cc76fb58d93
126          * [WebAudio API] iOS9で音が歪む、遅い、低い、割れる等の回避方法
127          */
128         if( X_WebAudio_needRateFix ){
129                 X_TEMP.webAudioSampleRateFix = function( sampleRate ){
130                     X_TEMP.webAudioDummyPlay( sampleRate );
131
132                         if( true || X_WebAudio_context[ 'sampleRate' ] !== sampleRate ){
133                                 // alert( '[debug]iOSで音割れを検知、修復コードを実施 ctxSR:' + X_WebAudio_context[ 'sampleRate' ] + ' abfSR:' + sampleRate );
134                                 
135                                 X_WebAudio_context.close && X_WebAudio_context.close();
136                                 X_WebAudio_context = new X_WebAudio_Context;
137                                 
138                                 X_TEMP.webAudioDummyPlay( sampleRate );
139                         };
140                         
141                         delete X_TEMP.webAudioSampleRateFix;
142                         delete X_TEMP.webAudioDummyPlay;
143                 };
144                 X_TEMP.webAudioDummyPlay = function( sampleRate, source ){
145                     source = X_WebAudio_context[ 'createBufferSource' ]();
146                     source.buffer = X_WebAudio_context[ 'createBuffer' ]( 1, 1, sampleRate );
147                     source[ 'connect' ]( X_WebAudio_context[ 'destination' ] );
148                     source.start ? source.start( 0 ) : source[ 'noteOn' ] ? source[ 'noteOn' ]( 0 ) : source[ 'noteGrainOn' ]( 0 );
149                 };
150         };
151         
152         X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ](
153                 'X.WebAudio.BufferLoader',
154                 X_Class.POOL_OBJECT,
155                 {
156                         audioUrl        : '',
157             xhr             : null,
158             onDecodeSuccess : null,
159             onDecodeError   : null,
160             
161             audioBuffer     : null,
162             errorState      : 0,
163             webAudioList    : null,
164             
165                         'Constructor' : function( webAudio, url ){
166                                 this.webAudioList = [ webAudio ];
167                                 this.audioUrl     = url;
168                                 this.xhr = X[ 'Net' ]( { 'xhr' : url, 'dataType' : 'arraybuffer' } )
169                                                                         [ 'listen' ]( X_EVENT_PROGRESS, this )
170                                                                         [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this );
171                                 X_WebAudio_BUFFER_LIST.push( this );
172                         },
173                         
174                         handleEvent : function( e ){
175                                 var i, l;
176                                 
177                                 switch( e.type ){
178                                         case X_EVENT_PROGRESS :
179                                                 for( i = 0, l = this.webAudioList.length; i < l; ++i ){
180                                                         this.webAudioList[ i ][ 'dispatch' ]( { type : X_EVENT_PROGRESS, 'percent' : e[ 'percent' ] } );
181                                                 };
182                                                 return;
183                                         
184                                         case X_EVENT_SUCCESS :
185                                         // TODO 旧api
186                                         // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
187                                         
188                                         // http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1
189                                         // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と
190                                         // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。
191                                         // ただし iOS 8.1.2 では エラーになる
192                                                 if( X_UA[ 'iOS' ] < 8 || !X_WebAudio_context[ 'decodeAudioData' ] ){
193                                                         this._onDecodeSuccess( X_WebAudio_context[ 'createBuffer' ]( e.response, false ) );
194                                                 } else
195                                                 if( X_WebAudio_context[ 'decodeAudioData' ] ){
196                                                         X_WebAudio_context[ 'decodeAudioData' ]( e.response,
197                                                                 this.onDecodeSuccess = X_Closure_create( this, this._onDecodeSuccess ),
198                                                                 this.onDecodeError   = X_Closure_create( this, this._onDecodeError ) );
199                                                 };
200                                                 break;
201
202                                         case X_EVENT_COMPLETE :
203                                                 this.errorState = 1;                            
204                                                 this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
205                                                 break;
206                                 };
207                                 this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this );
208                                 delete this.xhr;
209                         },
210                         
211                                 _onDecodeSuccess : function( buffer ){
212                                         this.onDecodeSuccess && this._onDecodeComplete();
213                                         
214                         if( !buffer ){
215                                 this.errorState = 2;
216                             this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
217                             return;
218                         };
219                         
220                         console.log( 'WebAudio decode success!' );
221         
222                         this.audioBuffer = buffer;
223
224                                         this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
225
226                         console.log( 'WebAudio decoded!' );
227                                 },
228                                 
229                                 _onDecodeError : function(){
230                                         console.log( 'WebAudio decode error!' );
231                                         this._onDecodeComplete();
232                                         this.errorState = 2;
233                                         this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
234                                 },
235                                 
236                                 _onDecodeComplete : function(){
237                                         X_Closure_correct( this.onDecodeSuccess );
238                                         delete this.onDecodeSuccess;
239                                         X_Closure_correct( this.onDecodeError );
240                                         delete this.onDecodeError;
241                                 },
242                         
243                         unregister : function( webAudio ){
244                                 var list = this.webAudioList,
245                                         i    = list.indexOf( webAudio );
246
247                                 if( 0 < i ){
248                                         list.splice( i, 1 );
249                                         if( !list.length ){
250                                                 this.xhr && this.xhr[ 'kill' ]();
251                                                 this[ 'kill' ]();
252                                         };
253                                 };
254                         }
255                         
256                 }
257         );
258
259         X_WebAudio = X_AudioBase[ 'inherits' ](
260                 'X.WebAudio',
261                 X_Class.POOL_OBJECT,
262                 {
263                         
264                         loader          : null,
265                                                 
266                         _startPos       : 0,
267                         _endPosition    : 0,
268                         _startTime      : 0,
269             //_timerID        : 0,
270             _interval       : 0,
271                 audioBuffer     : null,
272                 bufferSource    : null,
273             gainNode        : null,
274             
275             bufferPlay      : '',
276             bufferStop      : '',
277             //_onended        : null,
278             
279                         'Constructor' : function( dispatcher, url, option ){                            
280                                 var i = 0,
281                                         l = X_WebAudio_BUFFER_LIST.length,
282                                         loader;
283
284                                 /*
285                                  * http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1
286                                  * L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。
287                                  * 描画レート(描画 FPS)が下がるとノイズが混ざり始め、レートを上げると再生結果が正常になるというもので、オーディオ処理が描画スレッドに巻き込まれているような動作を見せる。
288                                  */
289                                 if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] && !X_WebAudio_fpsFix ){
290                                         X_Node_systemNode.create( 'div', { id : 'fps-slowdown-make-sound-noisy' } );
291                                         X_WebAudio_fpsFix = true;
292                                 };
293
294                                 for( ; i < l; ++i ){
295                                         loader = X_WebAudio_BUFFER_LIST[ i ];
296                                         if( loader.audioUrl === url ){
297                                                 this.loader = loader;
298                                                 loader.webAudioList.push( this );
299                                                 break;
300                                         };
301                                 };
302                                 
303                                 if( !this.loader ){
304                                         this.loader = loader = X_WebAudio_BufferLoader( this, url );
305                                 };
306                                 
307                                 this.dispatcher = dispatcher || this;
308                                 this.setState( option );
309                                 
310                                 this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, this.onKill );
311                                 
312                                 if( loader.audioBuffer || loader.errorState ){
313                                         this._onLoadBufferComplete();
314                                 } else {
315                                         loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
316                                 };
317                                 
318                                 if( X_WebAudio_isNoTouch ){
319                                         X_TEMP.xWebAudioInstances = X_TEMP.xWebAudioInstances || [];
320                                         X_TEMP.xWebAudioInstances.push( this );
321                                 };
322                         },
323                         
324                         onKill : function(){
325                                 this.loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete )
326                                         .unregister( this );
327
328                                 delete this.audioBuffer;
329                                 
330                                 this.playing      && this.actualPause();
331                     this.bufferSource && this._sourceDispose();
332         
333                     //this._onended     && X_Closure_correct( this._onended );  
334         
335                     this.gainNode     && this.gainNode.disconnect();
336                     
337                                 if( X_WebAudio_isNoTouch ){
338                                         X_TEMP.xWebAudioInstances.splice( X_TEMP.xWebAudioInstances.indexOf( this ), 1 );
339                                 };
340                         },
341                                 _onLoadBufferComplete : function( e ){
342                                         var loader = this.loader,
343                                                 buffer = loader.audioBuffer;
344                                         
345                                         e && loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
346                                         
347                         if ( !buffer ) {
348                                 this.error = loader.errorState;
349                             this.dispatcher[ 'dispatch' ]({
350                                                                 type    : X_EVENT_ERROR,
351                                                                 error   : loader.errorState,
352                                                                 message : loader.errorState === 1 ?
353                                                                                         'load buffer network error' :
354                                                                                         'buffer decode error'
355                                                         });
356                                                 this[ 'kill' ]();
357                             return;
358                         };
359         
360                         this.audioBuffer = buffer;
361                         this.duration    = buffer.duration * 1000;
362
363                                         this.dispatcher[ 'asyncDispatch' ]( X_WebAudio_isNoTouch ? X_EVENT_MEDIA_WAIT_FOR_TOUCH : X_EVENT_READY );
364                                 },
365                         
366                         actualPlay : function(){
367                                 var xWebAudio, begin, end;
368                                 
369                                 console.log( '[WebAudio] play abuf:' + !!this.audioBuffer );
370                                 
371                     if( !this.audioBuffer ){
372                         this._playReserved = true;
373                         return;
374                     };
375
376                                 if( X_WebAudio_isNoTouch ){
377                                         //@dev{
378                                         var e = X_EventDispatcher_CURRENT_EVENTS[ X_EventDispatcher_CURRENT_EVENTS.length - 1 ];
379                                         if( !e || !e[ 'pointerType' ] ){
380                                                 // alert( 'タッチイベント以外での play! ' + ( e ? e.type : '' ) );
381                                                 return;
382                                         };
383                                         //@}
384                                         
385                                         // http://qiita.com/uupaa/items/e5856e3cb2a9fc8c5507
386                                         // iOS9 + touchstart で呼んでいた場合、 X_ViewPort['listenOnce']('pointerup',this,this.actualPlay())
387                                         this.dispatcher[ 'asyncDispatch' ]( X_EVENT_READY );
388                                         
389                                         // Web Audio インスタンスが複数生成された場合、一つの Web Audio に対してタッチによる play が開始されたら、
390                                         //  1. 同時に生成された他の X.Audio インスタンスに対して READY イベントを発する
391                                         //  2. 以降の X.Audio インスタンス生成時に needTouchForPlay フラグは false
392                                         // ちなみに HTML Audio インスタンスは各々にタッチが必要     
393                                         //X_WebAudio_isNoTouch = false;
394                                         
395                                         while( xWebAudio = X_TEMP.xWebAudioInstances.pop() ){
396                                                 xWebAudio !== this && xWebAudio[ 'asyncDispatch' ]( X_EVENT_READY );
397                                         };
398                                         delete X_TEMP.xWebAudioInstances;
399
400                                         X_WebAudio_isNoTouch = false;
401
402                                         X_TEMP.webAudioSampleRateFix && X_TEMP.webAudioSampleRateFix( this.audioBuffer[ 'sampleRate' ] );
403                                 };
404                                 
405                                 end   = X_Audio_getEndTime( this );
406                                 begin = X_Audio_getStartTime( this, end, true );
407                                 
408                                 console.log( '[WebAudio] play ' + begin + ' -> ' + end + ' loop: ' + this.autoLoop + ' :' + this.loopStartTime + ' -> ' + this.loopEndTime );
409                                 this._createTree( begin, end );
410                     
411                     /* win8.1 Firefox45, win8.1 Chrome48 で動かなくなる...
412                     if( this.bufferSource[ 'loop' ] = this.autoLoop ){
413                         this.bufferSource[ 'loopStart' ] = 0 <= this.loopStartTime ? this.loopStartTime / 1000 : begin / 1000;
414                         this.bufferSource[ 'loopEnd'   ] = 0 <= this.loopEndTime   ? this.loopEndTime   / 1000 : end   / 1000;
415                     }; */
416
417                     // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
418                     // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない
419                     // 多くのブラウザで onended は timer を使ったカウントより遅いので使わない
420                 //if( this.bufferSource.onended !== undefined ){
421                         //console.log( '> use onended' );
422                         //this.bufferSource.onended = this._onended || ( this._onended = X_Closure_create( this, this._onEnded ) );
423                 //} else {
424                         //this._timerID && X_Timer_remove( this._timerID );
425                                         //this._timerID = X_Timer_once( end - begin, this, this._onEnded );
426                 //};
427
428                     this.playing      = true;
429                     this._startPos    = begin;
430                     this._endPosition = end;
431                     this._startTime   = X_WebAudio_context.currentTime * 1000;
432                     this._interval    = this._interval || X_Timer_add( 100, 0, this, this._onInterval );
433                         },
434                         
435                                 _createTree : function( begin, end ){
436                                         if( this.bufferSource ) this._sourceDispose();
437                                         
438                                         if( !this.gainNode ){
439                                                 this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ]();
440                                                 this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] );
441                                         };
442                                         
443                             this.bufferSource        = X_WebAudio_context[ 'createBufferSource' ]();
444                             this.bufferSource.buffer = this.audioBuffer;
445                             this.bufferSource[ 'connect' ]( this.gainNode );
446                             
447                             this.gainNode[ 'gain' ].value = this.gain;
448                             
449                             if( !this.bufferPlay ){
450                                 this.bufferPlay = this.bufferSource.start ? 'start' : this.bufferSource[ 'noteOn'  ] ? 'noteOn'  : 'noteGrainOn';
451                                 this.bufferStop = this.bufferSource.stop  ? 'stop'  : 'noteOff';
452                             };
453                                         // https://developer.mozilla.org/ja/docs/Web/API/AudioBufferSourceNode
454                                         // AudioBufferSourceNode.start()の呼び出しは一度しかできません。
455                             this.bufferSource[ this.bufferPlay ]( 0, begin / 1000, ( end - begin ) / 1000 );
456                                 },
457                         
458                                 _sourceDispose : function(){
459                             this.bufferSource.disconnect();
460                             //delete this.bufferSource.onended;
461                             delete this.bufferSource;
462                         },
463                                         
464                                 _onInterval : function(){
465                                         var time;
466                                         
467                             if( this.playing ){
468                                 // TODO 再生中に終了時間だけ変えた場合!
469                                 time = X_WebAudio_context.currentTime * 1000 - this._startTime - this._endPosition + this._startPos | 0;
470                                 //console.log( '> onEnd ' + ( this.playing && ( X_WebAudio_context.currentTime * 1000 - this._startTime ) ) + ' < ' + ( this._endPosition - this._startPos ) );
471
472                                 if( time < 0 ){
473                                         this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
474                                         return;
475                                 };
476                                 
477                                 if( this.autoLoop ){
478                                         if( !( this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){
479                                                 this.looped = true;
480                                                 this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );
481                                                 this.actualPlay();
482                                         } else {
483                                                                 delete this._interval;
484                                                                 return X_CALLBACK_UN_LISTEN;
485                                         };
486                                 } else {
487                                         this.actualPause();
488                                         this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
489                                 };
490                             };
491                                 },
492                         
493                         actualPause : function(){
494                                 console.log( '[WebAudio] pause' );
495                                 
496                                 this._interval && X_Timer_remove( this._interval );
497                                 delete this._interval;
498                                 delete this.playing;
499
500                     if( this.bufferSource ){
501                         this.bufferSource[ this.bufferStop ]( 0 );
502                     };
503                         },
504                         
505                         getActualCurrentTime : function(){
506                                 return X_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0;
507                         },
508                         
509                         afterUpdateState : function( result ){
510                                 if( result & 2 || result & 1 ){ // seek
511                         this.actualPlay();
512                                 } else
513                                 if( result & 4 ){
514                        this.gainNode[ 'gain' ].value = this.gain;
515                                 };
516                         }
517
518                 }
519         );
520
521         X_Audio_BACKENDS.push(
522                 {
523                         backendID   : 1,
524                         
525                         backendName : 'WebAudio',
526
527                         canPlay     : X_Audio_codecs,
528
529                         detect      : function( proxy, ext /* hash */ ){
530                                 proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } );
531                         },
532                         
533                         klass : X_WebAudio
534                 }
535         );
536 };