OSDN Git Service

Version 0.6.118, fix X.Audio.Sprite & etc.
[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                         _startTime      : 0,
44                         _endTime        : 0,
45                         _playTime       : 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.url = url;
61                                 this.closed = false;
62                                 this.proxy  = proxy;
63                                 
64                                 X_AudioWrapper_updateStates( this, option );
65                                 
66                                 if( audio && audio.buffer ){
67                                         this._onDecodeSuccess( audio.buffer );
68                                 } else
69                                 if( audio ){
70                                         // TODO 当てにしていたaudioがclose 等した場合
71                                         audio.proxy.listenOnce( 'canplaythrough', this, this._onBufferReady );
72                                 } else {
73                                         this.xhr = X.Net.xhrGet( url, 'arraybuffer' )
74                                                                         .listen( X.Event.PROGRESS, this )
75                                                                         .listenOnce( [ X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );                                   
76                                 };
77                         },
78                         
79                         handleEvent : function( e ){
80                                 switch( e.type ){
81                                         case X.Event.PROGRESS :
82                                                 e.percent ?
83                                                         this.proxy.dispatch( { type : 'progress', percent : e.percent } ) :
84                                                         this.proxy.dispatch( 'loadstart' );
85                                                 return;
86                                         
87                                         case X.Event.SUCCESS :
88                                                 console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData );
89                                         // TODO 旧api
90                                         // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
91                                                 if( X_Audio_WebAudio_context.decodeAudioData ){
92                                                         X_Audio_WebAudio_context.decodeAudioData( e.data,
93                                                                 this.onDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ),
94                                                                 this.onDecodeError   = X_Callback_create( this, this._onDecodeError ) );
95                                                 } else {
96                                                         this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
97                                                 };
98                                                 break;
99
100                                         case X.Event.CANCELED :
101                                                 this.error = 1;
102                                                 this.proxy.dispatch( 'aborted' );
103                                                 break;
104
105                                         case X.Event.COMPLETE :
106                                                 this.error = 2;                         
107                                                 this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } );
108                                                 break;
109                                 };
110                                 this.xhr.unlisten( [ X.Event.PROGRESS, X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );
111                                 delete this.xhr;
112                         },
113                         
114                                 _onDecodeSuccess : function( buffer ){
115                                         console.log( 'WebAudio decode success!' );
116                                         
117                                         this.onDecodeSuccess && this._onDecodeComplete();
118                                         
119                         if ( !buffer ) {
120                             this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } );
121                             return;
122                         };
123         
124                         this.buffer   = buffer;
125                         this.duration = buffer.duration * 1000;
126                         
127                         this.proxy.asyncDispatch( 'loadedmetadata' );
128                         this.proxy.asyncDispatch( 'loadeddata' );
129                         this.proxy.asyncDispatch( 'canplay' );
130                         this.proxy.asyncDispatch( 'canplaythrough' );
131                         
132                         this.autoplay && X.Timer.once( 16, this, this.play );
133                         
134                         console.log( 'WebAudio decoded!' );
135                                 },
136                                 
137                                 _onDecodeError : function(){
138                                         console.log( 'WebAudio decode error!' );
139                                         this._onDecodeComplete();
140                                         this.error = 3;
141                                         this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } );
142                                 },
143                                 
144                                 _onDecodeComplete : function(){
145                                         X_Callback_correct( this.onDecodeSuccess );
146                                         delete this.onDecodeSuccess;
147                                         X_Callback_correct( this.onDecodeError );
148                                         delete this.onDecodeError;
149                                 },
150                                 
151                                 _onBufferReady : function( e ){
152                                         var audio = X_Audio_WebAudio_getBuffer( this.url );
153                                         this._onDecodeSuccess( audio.buffer );
154                                 },
155                         
156                         close : function(){     
157                     delete this.buffer;
158         
159                                 if( this.xhr ) this.xhr.close();
160                                 
161                                 if( this.onDecodeSuccess ){
162                                         // 回収はあきらめる、、、
163                                 };
164         
165                                 this.playing  && this.pause();
166                     this.source   && this._sourceDispose();
167         
168                     this._onended && X_Callback_correct( this._onended );       
169         
170                     this.gainNode && this.gainNode.disconnect();
171                         },
172                         
173                                 _sourceDispose : function(){
174                             this.source.disconnect();
175                             delete this.source.onended;
176                             delete this.source;
177                         },
178                         
179                         play : function(){
180                                 var begin, end;
181                                 
182                     if( !this.buffer ){
183                         this.autoplay = true;
184                         return;
185                     };
186                                 
187                                 end   = X_AudioWrapper_getEndTime( this );
188                                 begin = X_AudioWrapper_getStartTime( this, end, true );
189                                 
190                                 console.log( '[WebAudio] play ' + begin + ' -> ' + end );
191                                 
192                                 if( this.source ) this._sourceDispose();
193                                 if( !this.gainNode ){
194                                         this.gainNode = X_Audio_WebAudio_context.createGain ? X_Audio_WebAudio_context.createGain() : X_Audio_WebAudio_context.createGainNode();
195                         this.gainNode.connect( X_Audio_WebAudio_context.destination );
196                                 };
197                     this.source        = X_Audio_WebAudio_context.createBufferSource();
198                     this.source.buffer = this.buffer;
199                     this.source.connect( this.gainNode );
200                     
201                     this.gainNode.gain.value = this.volume;
202                     
203                     // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
204                     // 破棄された X.Callback が呼ばれて、obj._() でエラーになる。Firefox では、onended は使わない
205                 if( false && this.source.onended !== undefined ){
206                         //console.log( '> use onended' );
207                         this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) );
208                 } else {
209                         this._timerID && X.Timer.remove( this._timerID );
210                                         this._timerID = X.Timer.once( end - begin, this, this._onEnded );
211                 };
212         
213                     if( this.source.start ){
214                         this.source.start( 0, begin / 1000, end / 1000 );
215                     } else {
216                         this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
217                     };
218                     
219                     this.playing    = true;
220                     this._startTime = begin;
221                     this._endTime   = end;
222                     this._playTime  = X_Audio_WebAudio_context.currentTime * 1000;
223                     this._interval  = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
224                         },
225
226                                 _onInterval : function(){
227                                         if( !this.playing ){
228                                                 delete this._interval;
229                                                 return X_Callback_UN_LISTEN;
230                                         };
231                                         this.proxy.dispatch( 'timeupdate' );
232                                 },
233                                                 
234                                 _onEnded : function(){
235                                         var time;
236                                         delete this._timerID;
237                                         
238                             if( this.playing ){
239                                 time = X_Audio_WebAudio_context.currentTime * 1000 - this._playTime - this._endTime + this._startTime | 0;
240                                 //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this._endTime - this._startTime ) );
241                                 if( this._onended ){
242                                         // Firefox 用の対策,,,
243                                         if( time < 0 ) return;
244                                 } else {
245                                         if( time < 0 ){
246                                                 console.log( '> onEnd ' + ( -time ) + ' start:' + this._startTime + '-' + this._endTime );
247                                                 this._timerID = X.Timer.once( -time, this, this._onEnded );
248                                                 return;
249                                         };
250                                 };
251                                 
252                                 if( this.loop ){
253                                         this.looped = true;
254                                         ( this.proxy.dispatch( 'looped' ) & X.Callback.PREVENT_DEFAULT ) || this.play();
255                                 } else {
256                                         this.pause();
257                                         this.proxy.dispatch( 'ended' );
258                                 };
259                             };
260                                 },
261                         
262                         pause : function(){
263                                 if( !this.playing ) return this;
264                                 
265                                 console.log( '[WebAudio] pause' );
266                                 
267                                 this.seekTime = this.state().currentTime;
268                                 
269                     this._timerID && X.Timer.remove( this._timerID );
270                                 delete this._timerID;
271                                 delete this.playing;
272
273                     if( this.source ){
274                         if( this.source.onended ) delete this.source.onended;
275                         
276                         this.source.stop ? 
277                                 this.source.stop( 0 ) : this.source.noteOff( 0 );
278                     };
279                         },
280         
281                         state : function( obj ){
282                                 var result;
283                                 
284                                 if( obj === undefined ){
285                                     return {
286                                         startTime     : this.startTime,
287                                         endTime       : this.endTime < 0 ? this.duration : this.endTime,
288                                         loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
289                                         loopEndTime   : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
290                                         loop          : this.loop,
291                                         looped        : this.looped,
292                                         volume        : this.volume,
293                                         playing       : this.playing,                           
294                                         duration      : this.duration,
295                                         
296                                         currentTime   : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime + this._startTime | 0 ) : this.seekTime,
297                                         error         : this.error
298                                     };
299                                 };
300                         
301                                 result = X_AudioWrapper_updateStates( this, obj );
302                                 
303                                 if( result & 2 || result & 1 ){ // seek
304                         this.play();
305                                 } else
306                                 if( result & 4 ){
307                        this.gainNode.gain.value = this.volume;
308                                 };
309                         }
310
311                 }
312         );
313         
314         X_Audio_BACKENDS.push(
315                 {
316                         backendName : 'Web Audio',
317
318                         detect : function( proxy, source, ext ){
319                                 var ok = ext === 'mp3' || ext === 'ogg';
320                                 
321                                 proxy.asyncDispatch( ok ? 'support' : 'nosupport' );
322                         },
323                         
324                         klass : X_Audio_WebAudioWrapper
325                 }
326         );
327
328 };