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 );
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 ) );
96 this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
100 case X.Event.CANCELED :
102 this.proxy.dispatch( 'aborted' );
105 case X.Event.COMPLETE :
107 this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } );
110 this.xhr.unlisten( [ X.Event.PROGRESS, X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );
114 _onDecodeSuccess : function( buffer ){
115 console.log( 'WebAudio decode success!' );
117 this.onDecodeSuccess && this._onDecodeComplete();
120 this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } );
124 this.buffer = buffer;
125 this.duration = buffer.duration * 1000;
127 this.proxy.asyncDispatch( 'loadedmetadata' );
128 this.proxy.asyncDispatch( 'loadeddata' );
129 this.proxy.asyncDispatch( 'canplay' );
130 this.proxy.asyncDispatch( 'canplaythrough' );
132 this.autoplay && X.Timer.once( 16, this, this.play );
134 console.log( 'WebAudio decoded!' );
137 _onDecodeError : function(){
138 console.log( 'WebAudio decode error!' );
139 this._onDecodeComplete();
141 this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } );
144 _onDecodeComplete : function(){
145 X_Callback_correct( this.onDecodeSuccess );
146 delete this.onDecodeSuccess;
147 X_Callback_correct( this.onDecodeError );
148 delete this.onDecodeError;
151 _onBufferReady : function( e ){
152 var audio = X_Audio_WebAudio_getBuffer( this.url );
153 this._onDecodeSuccess( audio.buffer );
159 if( this.xhr ) this.xhr.close();
161 if( this.onDecodeSuccess ){
165 this.playing && this.pause();
166 this.source && this._sourceDispose();
168 this._onended && X_Callback_correct( this._onended );
170 this.gainNode && this.gainNode.disconnect();
173 _sourceDispose : function(){
174 this.source.disconnect();
175 delete this.source.onended;
183 this.autoplay = true;
187 end = X_AudioWrapper_getEndTime( this );
188 begin = X_AudioWrapper_getStartTime( this, end, true );
190 console.log( '[WebAudio] play ' + begin + ' -> ' + end );
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 );
197 this.source = X_Audio_WebAudio_context.createBufferSource();
198 this.source.buffer = this.buffer;
199 this.source.connect( this.gainNode );
201 this.gainNode.gain.value = this.volume;
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 ) );
209 this._timerID && X.Timer.remove( this._timerID );
210 this._timerID = X.Timer.once( end - begin, this, this._onEnded );
213 if( this.source.start ){
214 this.source.start( 0, begin / 1000, end / 1000 );
216 this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
220 this._startTime = begin;
222 this._playTime = X_Audio_WebAudio_context.currentTime * 1000;
223 this._interval = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
226 _onInterval : function(){
228 delete this._interval;
229 return X_Callback_UN_LISTEN;
231 this.proxy.dispatch( 'timeupdate' );
234 _onEnded : function(){
236 delete this._timerID;
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 ) );
243 if( time < 0 ) return;
246 console.log( '> onEnd ' + ( -time ) + ' start:' + this._startTime + '-' + this._endTime );
247 this._timerID = X.Timer.once( -time, this, this._onEnded );
254 ( this.proxy.dispatch( 'looped' ) & X.Callback.PREVENT_DEFAULT ) || this.play();
257 this.proxy.dispatch( 'ended' );
263 if( !this.playing ) return this;
265 console.log( '[WebAudio] pause' );
267 this.seekTime = this.state().currentTime;
269 this._timerID && X.Timer.remove( this._timerID );
270 delete this._timerID;
274 if( this.source.onended ) delete this.source.onended;
277 this.source.stop( 0 ) : this.source.noteOff( 0 );
281 state : function( obj ){
284 if( obj === undefined ){
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,
291 looped : this.looped,
292 volume : this.volume,
293 playing : this.playing,
294 duration : this.duration,
296 currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime + this._startTime | 0 ) : this.seekTime,
301 result = X_AudioWrapper_updateStates( this, obj );
303 if( result & 2 || result & 1 ){ // seek
307 this.gainNode.gain.value = this.volume;
314 X_Audio_BACKENDS.push(
316 backendName : 'Web Audio',
318 detect : function( proxy, source, ext ){
319 var ok = ext === 'mp3' || ext === 'ogg';
321 proxy.asyncDispatch( ok ? 'support' : 'nosupport' );
324 klass : X_Audio_WebAudioWrapper