OSDN Git Service

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