OSDN Git Service

Version 0.6.133, fix for closure compiler - ADVANCED_OPTIMIZATIONS
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 01_XWebAudio.js
1
2 var X_Audio_WebAudio_context = !X_UA[ 'iPhone_4s' ]  && !X_UA[ 'iPad_2Mini1' ]  && !X_UA[ 'iPod_4' ]  &&
3                                                                 !( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) &&
4                                                                 ( window.AudioContext || window.webkitAudioContext ),
5         X_Audio_WebAudioWrapper;
6
7 /*
8  * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可
9  */
10 if( X_Audio_WebAudio_context ){
11         
12         X_Audio_WebAudio_context = new X_Audio_WebAudio_context;
13         
14         function X_Audio_WebAudio_getBuffer( url ){
15                 var i = 0, l = X_Audio_WRAPPER_LIST.length;
16                 for( i = 0; i < l; ++i ){
17                         if( X_Audio_WRAPPER_LIST[ i ].url === url ) return X_Audio_WRAPPER_LIST[ i ];
18                 };
19         };
20         
21         X_Audio_WebAudioWrapper = X.EventDispatcher.inherits(
22                 'X.AV.WebAudioWrapper',
23                 X.Class.POOL_OBJECT,
24                 {
25                         
26                         url             : '',
27                         proxy           : null,
28                         
29                         startTime       : 0,
30                         endTime         : -1,
31                         loopStartTime   : -1,
32                         loopEndTime     : -1,
33                         seekTime        : -1,
34                         duration        : 0,
35
36                         playing         : false,
37                         error           : 0,                    
38                         loop            : false,
39                         looped          : false,
40                         autoplay        : false,
41                         volume          : 0.5,
42                                                 
43                         _startPos       : 0,
44                         _endPosition    : 0,
45                         _startTime      : 0,
46             _timerID        : 0,
47             _interval       : 0,
48                 buffer          : null,
49                 source          : null,
50             gainNode        : null,
51             _onended        : null,
52             
53             xhr             : null,
54             onDecodeSuccess : null,
55             onDecodeError   : null,
56             
57                         Constructor : function( proxy, url, option ){
58                                 var audio = X_Audio_WebAudio_getBuffer( url );
59                                 
60                                 this.proxy  = proxy;
61                                 this.url    = url;
62                                 
63                                 X_AudioWrapper_updateStates( this, option );
64                                 
65                                 if( audio && audio.buffer ){
66                                         this._onDecodeSuccess( audio.buffer );
67                                 } else
68                                 if( audio ){
69                                         // TODO 当てにしていたaudioがclose 等した場合
70                                         audio.proxy.listenOnce( 'canplaythrough', this, this._onBufferReady );
71                                 } else {
72                                         this.xhr = X.Net.xhrGet( url, 'arraybuffer' )
73                                                                         .listen( X_Event.PROGRESS, this )
74                                                                         .listenOnce( [ X_Event.SUCCESS, X_Event.COMPLETE, X_Event.CANCELED ], this );                                   
75                                 };
76                         },
77                         
78                         handleEvent : function( e ){
79                                 switch( e.type ){
80                                         case X_Event.PROGRESS :
81                                                 e.percent ?
82                                                         this.proxy.dispatch( { type : 'progress', percent : e.percent } ) :
83                                                         this.proxy.dispatch( 'loadstart' );
84                                                 return;
85                                         
86                                         case X_Event.SUCCESS :
87                                                 console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData + ' t:' + typeof e.data );
88                                         // TODO 旧api
89                                         // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
90                                         
91                                         // http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1
92                                         // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と
93                                         // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。
94                                         // ただし iOS 8.1.2 では エラーになる
95                                                 if( X_Audio_WebAudio_context.createBuffer && X_UA[ 'iOS' ] < 8 ){
96                                                         this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
97                                                 } else
98                                                 if( X_Audio_WebAudio_context.decodeAudioData ){
99                                                         X_Audio_WebAudio_context.decodeAudioData( e.data,
100                                                                 this.onDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ),
101                                                                 this.onDecodeError   = X_Callback_create( this, this._onDecodeError ) );
102                                                 } else {
103                                                         this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
104                                                 };
105                                                 break;
106
107                                         case X_Event.CANCELED :
108                                                 this.error = 1;
109                                                 this.proxy.dispatch( 'aborted' );
110                                                 break;
111
112                                         case X_Event.COMPLETE :
113                                                 this.error = 2;                         
114                                                 this.proxy.asyncDispatch( { type : X_Event.ERROR, message : 'xhr error' } );
115                                                 break;
116                                 };
117                                 this.xhr.unlisten( [ X_Event.PROGRESS, X_Event.SUCCESS, X_Event.COMPLETE, X_Event.CANCELED ], this );
118                                 delete this.xhr;
119                         },
120                         
121                                 _onDecodeSuccess : function( buffer ){
122                                         console.log( 'WebAudio decode success!' );
123                                         
124                                         this.onDecodeSuccess && this._onDecodeComplete();
125                                         
126                         if ( !buffer ) {
127                             this.proxy.asyncDispatch( { type : X_Event.ERROR, message : 'buffer is ' + buffer } );
128                             return;
129                         };
130         
131                         this.buffer   = buffer;
132                         this.duration = buffer.duration * 1000;
133                         /*
134                         this.proxy.asyncDispatch( 'loadedmetadata' );
135                         this.proxy.asyncDispatch( 'loadeddata' );
136                         this.proxy.asyncDispatch( 'canplay' );
137                         this.proxy.asyncDispatch( 'canplaythrough' );
138                         */
139                                         this.proxy.asyncDispatch( X_Event.READY );
140                         
141                         this.autoplay && X.Timer.once( 16, this, this.play );
142                         
143                         console.log( 'WebAudio decoded!' );
144                                 },
145                                 
146                                 _onDecodeError : function(){
147                                         console.log( 'WebAudio decode error!' );
148                                         this._onDecodeComplete();
149                                         this.error = 3;
150                                         this.proxy.asyncDispatch( { type : X_Event.ERROR, message : 'decode error' } );
151                                 },
152                                 
153                                 _onDecodeComplete : function(){
154                                         X_Callback_correct( this.onDecodeSuccess );
155                                         delete this.onDecodeSuccess;
156                                         X_Callback_correct( this.onDecodeError );
157                                         delete this.onDecodeError;
158                                 },
159                                 
160                                 _onBufferReady : function( e ){
161                                         var audio = X_Audio_WebAudio_getBuffer( this.url );
162                                         this._onDecodeSuccess( audio.buffer );
163                                 },
164                         
165                         close : function(){     
166                     delete this.buffer;
167         
168                                 if( this.xhr ) this.xhr.close();
169                                 
170                                 if( this.onDecodeSuccess ){
171                                         // 回収はあきらめる、、、
172                                 };
173         
174                                 this.playing  && this.pause();
175                     this.source   && this._sourceDispose();
176         
177                     this._onended && X_Callback_correct( this._onended );       
178         
179                     this.gainNode && this.gainNode.disconnect();
180                         },
181                         
182                                 _sourceDispose : function(){
183                             this.source.disconnect();
184                             delete this.source.onended;
185                             delete this.source;
186                         },
187                         
188                         play : function(){
189                                 var begin, end;
190                                 
191                     if( !this.buffer ){
192                         this.autoplay = true;
193                         return;
194                     };
195                                 
196                                 end   = X_AudioWrapper_getEndTime( this );
197                                 begin = X_AudioWrapper_getStartTime( this, end, true );
198                                 
199                                 console.log( '[WebAudio] play ' + begin + ' -> ' + end );
200                                 
201                                 if( this.source ) this._sourceDispose();
202                                 if( !this.gainNode ){
203                                         this.gainNode = X_Audio_WebAudio_context.createGain ? X_Audio_WebAudio_context.createGain() : X_Audio_WebAudio_context.createGainNode();
204                         this.gainNode.connect( X_Audio_WebAudio_context.destination );
205                                 };
206                     this.source        = X_Audio_WebAudio_context.createBufferSource();
207                     this.source.buffer = this.buffer;
208                     this.source.connect( this.gainNode );
209                     
210                     this.gainNode.gain.value = this.volume;
211                     
212                     // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
213                     // 破棄された X.Callback が呼ばれて、obj._() でエラーになる。Firefox では、onended は使わない
214                 if( false && this.source.onended !== undefined ){
215                         //console.log( '> use onended' );
216                         this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) );
217                 } else {
218                         this._timerID && X.Timer.remove( this._timerID );
219                                         this._timerID = X.Timer.once( end - begin, this, this._onEnded );
220                 };
221         
222                     if( this.source.start ){
223                         this.source.start( 0, begin / 1000, end / 1000 );
224                     } else {
225                         this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
226                     };
227                     
228                     this.playing      = true;
229                     this._startPos    = begin;
230                     this._endPosition = end;
231                     this._startTime   = X_Audio_WebAudio_context.currentTime * 1000;
232                     this._interval    = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
233                         },
234
235                                 _onInterval : function(){
236                                         if( !this.playing ){
237                                                 delete this._interval;
238                                                 return X_Callback_UN_LISTEN;
239                                         };
240                                         this.proxy.dispatch( X_Event.MEDIA_PLAYING );
241                                 },
242                                                 
243                                 _onEnded : function(){
244                                         var time;
245                                         delete this._timerID;
246                                         
247                             if( this.playing ){
248                                 time = X_Audio_WebAudio_context.currentTime * 1000 - this._startTime - this._endPosition + this._startPos | 0;
249                                 //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime ) ) + ' < ' + ( this._endPosition - this._startPos ) );
250                                 if( this._onended ){
251                                         // Firefox 用の対策,,,
252                                         if( time < 0 ) return;
253                                 } else {
254                                         if( time < 0 ){
255                                                 console.log( '> onEnd crt:' + ( X_Audio_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime +
256                                                         ' from:' + this._startPos + ' to:' + this._endPosition );
257                                                 this._timerID = X.Timer.once( -time, this, this._onEnded );
258                                                 return;
259                                         };
260                                 };
261                                 
262                                 if( this.loop ){
263                                         if( !( this.proxy.dispatch( X_Event.MEDIA_BEFORE_LOOP ) & X.Callback.PREVENT_DEFAULT ) ){
264                                                 this.looped = true;
265                                                 this.proxy.dispatch( X_Event.MEDIA_LOOPED );
266                                                 this.play();
267                                         };
268                                 } else {
269                                         this.pause();
270                                         this.proxy.dispatch( X_Event.MEDIA_ENDED );
271                                 };
272                             };
273                                 },
274                         
275                         pause : function(){
276                                 if( !this.playing ) return this;
277                                 
278                                 console.log( '[WebAudio] pause' );
279                                 
280                                 this.seekTime = this.state().currentTime;
281                                 
282                     this._timerID && X.Timer.remove( this._timerID );
283                                 delete this._timerID;
284                                 delete this.playing;
285
286                     if( this.source ){
287                         if( this.source.onended ) delete this.source.onended;
288                         
289                         this.source.stop ? 
290                                 this.source.stop( 0 ) : this.source.noteOff( 0 );
291                     };
292                         },
293         
294                         state : function( obj ){
295                                 var result;
296                                 
297                                 if( obj === undefined ){
298                                     return {
299                                         startTime     : this.startTime,
300                                         endTime       : this.endTime < 0 ? this.duration : this.endTime,
301                                         loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
302                                         loopEndTime   : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
303                                         loop          : this.loop,
304                                         looped        : this.looped,
305                                         volume        : this.volume,
306                                         playing       : this.playing,                           
307                                         duration      : this.duration,
308                                         
309                                         currentTime   : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0 ) : this.seekTime,
310                                         error         : this.error
311                                     };
312                                 };
313                         
314                                 result = X_AudioWrapper_updateStates( this, obj );
315                                 
316                                 if( result & 2 || result & 1 ){ // seek
317                         this.play();
318                                 } else
319                                 if( result & 4 ){
320                        this.gainNode.gain.value = this.volume;
321                                 };
322                         }
323
324                 }
325         );
326
327
328         X_Audio_BACKENDS.push(
329                 {
330                         backendName : 'Web Audio',
331
332                         // 
333                         detect : function( proxy, source, ext ){
334                                 proxy.asyncDispatch( { type : X_Event.COMPLETE, canPlay : X_Audio_codecs[ ext ] } );
335                         },
336                         
337                         klass : X_Audio_WebAudioWrapper
338                 }
339         );
340 };