OSDN Git Service

Version 0.6.119, add ended event to X.Audio.Sprite & GPU layer off.
[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 + ' t:' + typeof e.data );
89                                         // TODO 旧api
90                                         // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
91                                         
92                                         // http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1
93                                         // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と
94                                         // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。
95                                                 if( X_Audio_WebAudio_context.createBuffer && X_UA.iOS ){
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 : '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 : '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.autoplay && X.Timer.once( 16, this, this.play );
140                         
141                         console.log( 'WebAudio decoded!' );
142                                 },
143                                 
144                                 _onDecodeError : function(){
145                                         console.log( 'WebAudio decode error!' );
146                                         this._onDecodeComplete();
147                                         this.error = 3;
148                                         this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } );
149                                 },
150                                 
151                                 _onDecodeComplete : function(){
152                                         X_Callback_correct( this.onDecodeSuccess );
153                                         delete this.onDecodeSuccess;
154                                         X_Callback_correct( this.onDecodeError );
155                                         delete this.onDecodeError;
156                                 },
157                                 
158                                 _onBufferReady : function( e ){
159                                         var audio = X_Audio_WebAudio_getBuffer( this.url );
160                                         this._onDecodeSuccess( audio.buffer );
161                                 },
162                         
163                         close : function(){     
164                     delete this.buffer;
165         
166                                 if( this.xhr ) this.xhr.close();
167                                 
168                                 if( this.onDecodeSuccess ){
169                                         // 回収はあきらめる、、、
170                                 };
171         
172                                 this.playing  && this.pause();
173                     this.source   && this._sourceDispose();
174         
175                     this._onended && X_Callback_correct( this._onended );       
176         
177                     this.gainNode && this.gainNode.disconnect();
178                         },
179                         
180                                 _sourceDispose : function(){
181                             this.source.disconnect();
182                             delete this.source.onended;
183                             delete this.source;
184                         },
185                         
186                         play : function(){
187                                 var begin, end;
188                                 
189                     if( !this.buffer ){
190                         this.autoplay = true;
191                         return;
192                     };
193                                 
194                                 end   = X_AudioWrapper_getEndTime( this );
195                                 begin = X_AudioWrapper_getStartTime( this, end, true );
196                                 
197                                 console.log( '[WebAudio] play ' + begin + ' -> ' + end );
198                                 
199                                 if( this.source ) this._sourceDispose();
200                                 if( !this.gainNode ){
201                                         this.gainNode = X_Audio_WebAudio_context.createGain ? X_Audio_WebAudio_context.createGain() : X_Audio_WebAudio_context.createGainNode();
202                         this.gainNode.connect( X_Audio_WebAudio_context.destination );
203                                 };
204                     this.source        = X_Audio_WebAudio_context.createBufferSource();
205                     this.source.buffer = this.buffer;
206                     this.source.connect( this.gainNode );
207                     
208                     this.gainNode.gain.value = this.volume;
209                     
210                     // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
211                     // 破棄された X.Callback が呼ばれて、obj._() でエラーになる。Firefox では、onended は使わない
212                 if( false && this.source.onended !== undefined ){
213                         //console.log( '> use onended' );
214                         this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) );
215                 } else {
216                         this._timerID && X.Timer.remove( this._timerID );
217                                         this._timerID = X.Timer.once( end - begin, this, this._onEnded );
218                 };
219         
220                     if( this.source.start ){
221                         this.source.start( 0, begin / 1000, end / 1000 );
222                     } else {
223                         this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
224                     };
225                     
226                     this.playing    = true;
227                     this._startTime = begin;
228                     this._endTime   = end;
229                     this._playTime  = X_Audio_WebAudio_context.currentTime * 1000;
230                     this._interval  = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
231                         },
232
233                                 _onInterval : function(){
234                                         if( !this.playing ){
235                                                 delete this._interval;
236                                                 return X_Callback_UN_LISTEN;
237                                         };
238                                         this.proxy.dispatch( 'timeupdate' );
239                                 },
240                                                 
241                                 _onEnded : function(){
242                                         var time;
243                                         delete this._timerID;
244                                         
245                             if( this.playing ){
246                                 time = X_Audio_WebAudio_context.currentTime * 1000 - this._playTime - this._endTime + this._startTime | 0;
247                                 //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this._endTime - this._startTime ) );
248                                 if( this._onended ){
249                                         // Firefox 用の対策,,,
250                                         if( time < 0 ) return;
251                                 } else {
252                                         if( time < 0 ){
253                                                 console.log( '> onEnd ' + ( -time ) + ' start:' + this._startTime + '-' + this._endTime );
254                                                 this._timerID = X.Timer.once( -time, this, this._onEnded );
255                                                 return;
256                                         };
257                                 };
258                                 
259                                 if( this.loop ){
260                                         if( !( this.proxy.dispatch( 'looped' ) & X.Callback.PREVENT_DEFAULT ) ){
261                                                 this.looped = true;
262                                                 this.play();
263                                         };
264                                 } else {
265                                         this.pause();
266                                         this.proxy.dispatch( 'ended' );
267                                 };
268                             };
269                                 },
270                         
271                         pause : function(){
272                                 if( !this.playing ) return this;
273                                 
274                                 console.log( '[WebAudio] pause' );
275                                 
276                                 this.seekTime = this.state().currentTime;
277                                 
278                     this._timerID && X.Timer.remove( this._timerID );
279                                 delete this._timerID;
280                                 delete this.playing;
281
282                     if( this.source ){
283                         if( this.source.onended ) delete this.source.onended;
284                         
285                         this.source.stop ? 
286                                 this.source.stop( 0 ) : this.source.noteOff( 0 );
287                     };
288                         },
289         
290                         state : function( obj ){
291                                 var result;
292                                 
293                                 if( obj === undefined ){
294                                     return {
295                                         startTime     : this.startTime,
296                                         endTime       : this.endTime < 0 ? this.duration : this.endTime,
297                                         loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
298                                         loopEndTime   : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
299                                         loop          : this.loop,
300                                         looped        : this.looped,
301                                         volume        : this.volume,
302                                         playing       : this.playing,                           
303                                         duration      : this.duration,
304                                         
305                                         currentTime   : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime + this._startTime | 0 ) : this.seekTime,
306                                         error         : this.error
307                                     };
308                                 };
309                         
310                                 result = X_AudioWrapper_updateStates( this, obj );
311                                 
312                                 if( result & 2 || result & 1 ){ // seek
313                         this.play();
314                                 } else
315                                 if( result & 4 ){
316                        this.gainNode.gain.value = this.volume;
317                                 };
318                         }
319
320                 }
321         );
322         
323         X_Audio_BACKENDS.push(
324                 {
325                         backendName : 'Web Audio',
326
327                         // 
328                         detect : function( proxy, source, ext ){
329                                 proxy.asyncDispatch( X_Audio_codecs[ ext ] ? 'support' : 'nosupport' );
330                         },
331                         
332                         klass : X_Audio_WebAudioWrapper
333                 }
334         );
335
336 };