2 var X_Audio_WebAudio_context = window.webkitAudioContext || window.AudioContext,
3 X_Audio_WebAudioWrapper;
5 if( X_Audio_WebAudio_context ){
7 X_Audio_WebAudio_context = new X_Audio_WebAudio_context;
9 function X_Audio_WebAudio_getBuffer( url ){
10 var i = 0, l = X_Audio_WRAPPER_LIST.length;
11 for( i = 0; i < l; ++i ){
12 if( X_Audio_WRAPPER_LIST[ i ].url === url ) return X_Audio_WRAPPER_LIST[ i ];
16 X_Audio_WebAudioWrapper = X.EventDispatcher.inherits(
17 'X.AV.WebAudioWrapper',
49 onDecodeSuccess : null,
52 Constructor : function( proxy, url, option ){
53 var audio = X_Audio_WebAudio_getBuffer( url );
59 X_AudioWrapper_updateStates( this, option );
61 if( audio && audio.buffer ){
62 this._onDecodeSuccess( audio.buffer );
65 // TODO 当てにしていたaudioがclose 等した場合
66 audio.proxy.listenOnce( 'canplaythrough', this, this._onBufferReady );
68 this.xhr = X.Net.xhrGet( url, 'arraybuffer' )
69 .listen( X.Event.PROGRESS, this )
70 .listenOnce( [ X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );
74 handleEvent : function( e ){
76 case X.Event.PROGRESS :
78 this.proxy.dispatch( { type : 'progress', percent : e.percent } ) :
79 this.proxy.dispatch( 'loadstart' );
82 case X.Event.SUCCESS :
83 console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData );
85 // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
86 if( X_Audio_WebAudio_context.decodeAudioData ){
87 X_Audio_WebAudio_context.decodeAudioData( e.data,
88 this.onDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ),
89 this.onDecodeError = X_Callback_create( this, this._onDecodeError ) );
91 this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
95 case X.Event.CANCELED :
97 this.proxy.dispatch( 'aborted' );
100 case X.Event.COMPLETE :
102 this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } );
105 this.xhr.unlisten( [ X.Event.PROGRESS, X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );
109 _onDecodeSuccess : function( buffer ){
110 console.log( 'WebAudio decode success!' );
112 this.onDecodeSuccess && this._onDecodeComplete();
115 this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } );
119 this.buffer = buffer;
120 this.duration = buffer.duration * 1000;
122 this.proxy.asyncDispatch( 'loadedmetadata' );
123 this.proxy.asyncDispatch( 'loadeddata' );
124 this.proxy.asyncDispatch( 'canplay' );
125 this.proxy.asyncDispatch( 'canplaythrough' );
127 this.autoplay && X.Timer.once( 16, this, this.play );
129 console.log( 'WebAudio decoded!' );
132 _onDecodeError : function(){
133 console.log( 'WebAudio decode error!' );
134 this._onDecodeComplete();
136 this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } );
139 _onDecodeComplete : function(){
140 X_Callback_correct( this.onDecodeSuccess );
141 delete this.onDecodeSuccess;
142 X_Callback_correct( this.onDecodeError );
143 delete this.onDecodeError;
146 _onBufferReady : function( e ){
147 var audio = X_Audio_WebAudio_getBuffer( this.url );
148 this._onDecodeSuccess( audio.buffer );
154 if( this.xhr ) this.xhr.close();
156 if( this.onDecodeSuccess ){
160 this.playing && this.pause();
161 this.source && this._sourceDispose();
163 this._onended && X_Callback_correct( this._onended );
165 this.gainNode && this.gainNode.disconnect();
168 _sourceDispose : function(){
169 this.source.disconnect();
170 delete this.source.onended;
178 this.autoplay = true;
182 end = X_AudioWrapper_getEndTime( this );
183 begin = X_AudioWrapper_getStartTime( this, end, true );
185 console.log( '[WebAudio] play ' + begin + ' -> ' + end );
187 if( this.source ) this._sourceDispose();
188 if( !this.gainNode ){
189 this.gainNode = X_Audio_WebAudio_context.createGain ? X_Audio_WebAudio_context.createGain() : X_Audio_WebAudio_context.createGainNode();
190 this.gainNode.connect( X_Audio_WebAudio_context.destination );
192 this.source = X_Audio_WebAudio_context.createBufferSource();
193 this.source.buffer = this.buffer;
194 this.source.connect( this.gainNode );
196 this.gainNode.gain.value = this.volume;
198 this._timerID && X.Timer.remove( this._timerID );
200 // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
201 // 破棄された X.Callback が呼ばれて、obj._() でエラーになる。Firefox では、onended は使わない
202 if( false && this.source.onended !== undefined ){
203 //console.log( '> use onended' );
204 this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) );
206 this._timerID = X.Timer.once( end - begin, this, this._onEnded );
209 if( this.source.start ){
210 this.source.start( 0, begin / 1000, end / 1000 );
212 this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
216 this._startTime = begin;
218 this._playTime = X_Audio_WebAudio_context.currentTime * 1000;
219 this._interval = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
222 _onInterval : function(){
224 delete this._interval;
225 return X_Callback_UN_LISTEN;
227 this.proxy.dispatch( 'timeupdate' );
230 _onEnded : function(){
232 delete this._timerID;
235 time = X_Audio_WebAudio_context.currentTime * 1000 - this._playTime - this._endTime + this._startTime | 0;
236 //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this._endTime - this._startTime ) );
239 if( time < 0 ) return;
242 console.log( '> onEnd ' + time );
243 this._timerID = X.Timer.once( -time, this, this._onEnded );
251 this.proxy.dispatch( 'looped' );
254 this.proxy.dispatch( 'ended' );
260 if( !this.playing ) return this;
262 console.log( '[WebAudio] pause' );
264 this._timerID && X.Timer.remove( this._timerID );
265 delete this._timerID;
269 if( this.source.onended ) delete this.source.onended;
272 this.source.stop( 0 ) : this.source.noteOff( 0 );
276 state : function( obj ){
277 var time = this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) : 0,
280 if( obj === undefined ){
282 startTime : this.startTime,
283 endTime : this.endTime < 0 ? this.duration : this.endTime,
284 loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
285 loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
287 looped : this.looped,
288 volume : this.volume,
289 playing : this.playing,
290 duration : this.duration,
292 currentTime : time + this._startTime,
297 result = X_AudioWrapper_updateStates( this, obj );
299 if( result & 2 ){ // seek
306 this.gainNode.gain.value = this.volume;
313 X_Audio_BACKENDS.push(
315 backendName : 'Web Audio',
317 detect : function( proxy, source, ext ){
318 var ok = ext === 'mp3' || ext === 'ogg';
320 proxy.asyncDispatch( ok ? 'support' : 'nosupport' );
323 klass : X_Audio_WebAudioWrapper