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;
8 * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可
10 if( X_Audio_WebAudio_context ){
12 X_Audio_WebAudio_context = new X_Audio_WebAudio_context;
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 ];
21 X_Audio_WebAudioWrapper = X.EventDispatcher.inherits(
22 'X.AV.WebAudioWrapper',
54 onDecodeSuccess : null,
57 Constructor : function( proxy, url, option ){
58 var audio = X_Audio_WebAudio_getBuffer( url );
64 X_AudioWrapper_updateStates( this, option );
66 if( audio && audio.buffer ){
67 this._onDecodeSuccess( audio.buffer );
70 // TODO 当てにしていたaudioがclose 等した場合
71 audio.proxy.listenOnce( 'canplaythrough', this, this._onBufferReady );
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 );
79 handleEvent : function( e ){
81 case X.Event.PROGRESS :
83 this.proxy.dispatch( { type : 'progress', percent : e.percent } ) :
84 this.proxy.dispatch( 'loadstart' );
87 case X.Event.SUCCESS :
88 console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData + ' t:' + typeof e.data );
90 // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
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 ) );
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 ) );
103 this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
107 case X.Event.CANCELED :
109 this.proxy.dispatch( 'aborted' );
112 case X.Event.COMPLETE :
114 this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } );
117 this.xhr.unlisten( [ X.Event.PROGRESS, X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );
121 _onDecodeSuccess : function( buffer ){
122 console.log( 'WebAudio decode success!' );
124 this.onDecodeSuccess && this._onDecodeComplete();
127 this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } );
131 this.buffer = buffer;
132 this.duration = buffer.duration * 1000;
134 this.proxy.asyncDispatch( 'loadedmetadata' );
135 this.proxy.asyncDispatch( 'loadeddata' );
136 this.proxy.asyncDispatch( 'canplay' );
137 this.proxy.asyncDispatch( 'canplaythrough' );
139 this.autoplay && X.Timer.once( 16, this, this.play );
141 console.log( 'WebAudio decoded!' );
144 _onDecodeError : function(){
145 console.log( 'WebAudio decode error!' );
146 this._onDecodeComplete();
148 this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } );
151 _onDecodeComplete : function(){
152 X_Callback_correct( this.onDecodeSuccess );
153 delete this.onDecodeSuccess;
154 X_Callback_correct( this.onDecodeError );
155 delete this.onDecodeError;
158 _onBufferReady : function( e ){
159 var audio = X_Audio_WebAudio_getBuffer( this.url );
160 this._onDecodeSuccess( audio.buffer );
166 if( this.xhr ) this.xhr.close();
168 if( this.onDecodeSuccess ){
172 this.playing && this.pause();
173 this.source && this._sourceDispose();
175 this._onended && X_Callback_correct( this._onended );
177 this.gainNode && this.gainNode.disconnect();
180 _sourceDispose : function(){
181 this.source.disconnect();
182 delete this.source.onended;
190 this.autoplay = true;
194 end = X_AudioWrapper_getEndTime( this );
195 begin = X_AudioWrapper_getStartTime( this, end, true );
197 console.log( '[WebAudio] play ' + begin + ' -> ' + end );
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 );
204 this.source = X_Audio_WebAudio_context.createBufferSource();
205 this.source.buffer = this.buffer;
206 this.source.connect( this.gainNode );
208 this.gainNode.gain.value = this.volume;
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 ) );
216 this._timerID && X.Timer.remove( this._timerID );
217 this._timerID = X.Timer.once( end - begin, this, this._onEnded );
220 if( this.source.start ){
221 this.source.start( 0, begin / 1000, end / 1000 );
223 this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
227 this._startTime = begin;
229 this._playTime = X_Audio_WebAudio_context.currentTime * 1000;
230 this._interval = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
233 _onInterval : function(){
235 delete this._interval;
236 return X_Callback_UN_LISTEN;
238 this.proxy.dispatch( 'timeupdate' );
241 _onEnded : function(){
243 delete this._timerID;
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 ) );
250 if( time < 0 ) return;
253 console.log( '> onEnd ' + ( -time ) + ' start:' + this._startTime + '-' + this._endTime );
254 this._timerID = X.Timer.once( -time, this, this._onEnded );
260 if( !( this.proxy.dispatch( 'looped' ) & X.Callback.PREVENT_DEFAULT ) ){
266 this.proxy.dispatch( 'ended' );
272 if( !this.playing ) return this;
274 console.log( '[WebAudio] pause' );
276 this.seekTime = this.state().currentTime;
278 this._timerID && X.Timer.remove( this._timerID );
279 delete this._timerID;
283 if( this.source.onended ) delete this.source.onended;
286 this.source.stop( 0 ) : this.source.noteOff( 0 );
290 state : function( obj ){
293 if( obj === undefined ){
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,
300 looped : this.looped,
301 volume : this.volume,
302 playing : this.playing,
303 duration : this.duration,
305 currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime + this._startTime | 0 ) : this.seekTime,
310 result = X_AudioWrapper_updateStates( this, obj );
312 if( result & 2 || result & 1 ){ // seek
316 this.gainNode.gain.value = this.volume;
323 X_Audio_BACKENDS.push(
325 backendName : 'Web Audio',
328 detect : function( proxy, source, ext ){
329 proxy.asyncDispatch( X_Audio_codecs[ ext ] ? 'support' : 'nosupport' );
332 klass : X_Audio_WebAudioWrapper