var X_Audio_WebAudio_context = !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] &&
!( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) &&
( window.AudioContext || window.webkitAudioContext ),
+ X_Audio_BUFFER_LIST = [],
X_Audio_WebAudioWrapper;
/*
X_Audio_WebAudio_context = new X_Audio_WebAudio_context;
- function X_Audio_WebAudio_getBuffer( url ){
- var i = 0, l = X_Audio_WRAPPER_LIST.length;
- for( i = 0; i < l; ++i ){
- if( X_Audio_WRAPPER_LIST[ i ].url === url ) return X_Audio_WRAPPER_LIST[ i ];
- };
- };
-
- X_Audio_WebAudioWrapper = X_EventDispatcher[ 'inherits' ](
- 'X.AV.WebAudioWrapper',
+ X_Audio_BufferLoader = X_EventDispatcher[ 'inherits' ](
+ 'X.AV.WebAudioBufferLoader',
X_Class.POOL_OBJECT,
{
-
url : '',
- proxy : null,
-
- startTime : 0,
- endTime : -1,
- loopStartTime : -1,
- loopEndTime : -1,
- seekTime : -1,
- duration : 0,
-
- playing : false,
- error : 0,
- loop : false,
- looped : false,
- autoplay : false,
- volume : 0.5,
-
- _startPos : 0,
- _endPosition : 0,
- _startTime : 0,
- _timerID : 0,
- _interval : 0,
- buffer : null,
- source : null,
- gainNode : null,
- _onended : null,
-
xhr : null,
onDecodeSuccess : null,
onDecodeError : null,
- Constructor : function( proxy, url, option ){
- var audio = X_Audio_WebAudio_getBuffer( url );
-
- this.proxy = proxy;
- this.url = url;
-
- X_AudioWrapper_updateStates( this, option );
-
- if( audio && audio.buffer ){
- this._onDecodeSuccess( audio.buffer );
- } else
- if( audio ){
- // TODO 当てにしていたaudioがclose 等した場合
- audio.proxy[ 'listenOnce' ]( 'canplaythrough', this, this._onBufferReady );
- } else {
- this.xhr = X.Net( { 'xhr' : url, 'type' : 'arraybuffer' } )
+ buffer : null,
+ error : 0,
+ webAudioList : null,
+
+ Constructor : function( webAudio, url ){
+ this.webAudioList = [ webAudio ];
+ this.url = url;
+ this.xhr = X.Net( { 'xhr' : url, 'dataType' : 'arraybuffer' } )
[ 'listen' ]( X_EVENT_PROGRESS, this )
- [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this );
- };
+ [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this );
},
handleEvent : function( e ){
switch( e.type ){
case X_EVENT_PROGRESS :
- e[ 'percent' ] ?
- this.proxy[ 'dispatch' ]( { type : 'progress', 'percent' : e[ 'percent' ] } ) :
- this.proxy[ 'dispatch' ]( 'loadstart' );
+ this[ 'dispatch' ]( { type : 'progress', 'percent' : e[ 'percent' ] } );
return;
case X_EVENT_SUCCESS :
- console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData + ' t:' + typeof e.data );
// TODO 旧api
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext
};
break;
- case X_EVENT_CANCELED :
- this.error = 1;
- this.proxy[ 'dispatch' ]( 'aborted' );
- break;
-
case X_EVENT_COMPLETE :
- this.error = 2;
- this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'xhr error' } );
+ this.error = 1;
+ this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
break;
};
- this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this );
+ this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this );
delete this.xhr;
},
this.onDecodeSuccess && this._onDecodeComplete();
if ( !buffer ) {
- this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'buffer is ' + buffer } );
+ this.error = 2;
+ this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
return;
};
this.buffer = buffer;
- this.duration = buffer.duration * 1000;
- /*
- this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' );
- this.proxy[ 'asyncDispatch' ]( 'loadeddata' );
- this.proxy[ 'asyncDispatch' ]( 'canplay' );
- this.proxy[ 'asyncDispatch' ]( 'canplaythrough' );
- */
- this.proxy[ 'asyncDispatch' ]( X_EVENT_READY );
-
- this.autoplay && X_Timer_once( 16, this, this.play );
-
+
+ this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
+
console.log( 'WebAudio decoded!' );
},
_onDecodeError : function(){
console.log( 'WebAudio decode error!' );
this._onDecodeComplete();
- this.error = 3;
- this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'decode error' } );
+ this.error = 2;
+ this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
},
_onDecodeComplete : function(){
X_Callback_correct( this.onDecodeError );
delete this.onDecodeError;
},
-
- _onBufferReady : function( e ){
- var audio = X_Audio_WebAudio_getBuffer( this.url );
- this._onDecodeSuccess( audio.buffer );
- },
- close : function(){
- delete this.buffer;
-
- if( this.xhr ) this.xhr.close();
-
- if( this.onDecodeSuccess ){
- // 回収はあきらめる、、、
+ unregister : function( webAudio ){
+ var list = this.webAudioList,
+ i = list.indexOf( webAudio );
+ if( 0 < i ){
+ list.splice( i, 1 );
+ if( list.length ){
+ this.xhr && this.xhr[ 'kill' ]();
+ this[ 'kill' ]();
+ };
};
+ }
+
+ }
+ );
- this.playing && this.pause();
- this.source && this._sourceDispose();
- this._onended && X_Callback_correct( this._onended );
-
- this.gainNode && this.gainNode.disconnect();
- },
+ X_Audio_WebAudioWrapper = X_Audio_AbstractAudioBackend[ 'inherits' ](
+ 'X.AV.WebAudioWrapper',
+ X_Class.POOL_OBJECT,
+ {
- _sourceDispose : function(){
- this.source.disconnect();
- delete this.source.onended;
- delete this.source;
- },
+ loader : null,
+
+ _startPos : 0,
+ _endPosition : 0,
+ _startTime : 0,
+ _timerID : 0,
+ _interval : 0,
+ buffer : null,
+ source : null,
+ gainNode : null,
+ _onended : null,
+
+ Constructor : function( target, url, option ){
+ var i = 0,
+ l = X_Audio_BUFFER_LIST.length,
+ loader;
+
+ for( ; i < l; ++i ){
+ loader = X_Audio_BUFFER_LIST[ i ];
+ if( loader.url === url ){
+ this.loader = loader;
+ loader.webAudioList.push( this );
+ break;
+ };
+ };
+
+ if( !this.loader ){
+ this.loader = loader = new X_Audio_BufferLoader( this, url );
+ };
+
+ this.target = target || this;
+
+ this.setState( option );
+
+ this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, X_WebAudio_handleEvent );
+
+ if( loader.buffer || loader.error ){
+ this._onLoadBufferComplete();
+ } else {
+ loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
+ };
+ },
+ _onLoadBufferComplete : function( e ){
+ var loader = this.loader,
+ buffer = loader.buffer;
+
+ e && loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
+
+ if ( !buffer ) {
+ this.error = loader.error;
+
+ this.target[ 'dispatch' ]({
+ type : X_EVENT_ERROR,
+ error : loader.error,
+ message : loader.error === 1 ?
+ 'load buffer network error' :
+ 'buffer decode error'
+ });
+ this[ 'kill' ]();
+ return;
+ };
+
+ this.buffer = buffer;
+ this.duration = buffer.duration * 1000;
+
+ this.target[ 'asyncDispatch' ]( X_EVENT_READY );
+
+ this.autoplay && X_Timer_once( 16, this, this.play );
+
+ },
- play : function(){
+ actualPlay : function(){
var begin, end;
if( !this.buffer ){
this.source.buffer = this.buffer;
this.source.connect( this.gainNode );
- this.gainNode.gain.value = this.volume;
+ this.gainNode.gain.value = this.gain;
// おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
// 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない
this._startTime = X_Audio_WebAudio_context.currentTime * 1000;
this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval );
},
+
+ _sourceDispose : function(){
+ this.source.disconnect();
+ delete this.source.onended;
+ delete this.source;
+ },
_onInterval : function(){
if( !this.playing ){
delete this._interval;
return X_Callback_UN_LISTEN;
};
- this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
+ this.target[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
},
_onEnded : function(){
};
};
- if( this.loop ){
- if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){
+ if( this.autoLoop ){
+ if( !( this.target[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){
this.looped = true;
- this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );
- this.play();
+ this.target[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );
+ this.actualPlay();
};
} else {
- this.pause();
- this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
+ this.actualPause();
+ this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
};
};
},
- pause : function(){
+ actualPause : function(){
if( !this.playing ) return this;
console.log( '[WebAudio] pause' );
- this.seekTime = this.state().currentTime;
+ this.seekTime = this.getActualCurrentTime();
this._timerID && X_Timer_remove( this._timerID );
delete this._timerID;
this.source.stop( 0 ) : this.source.noteOff( 0 );
};
},
-
- state : function( obj ){
- var result;
-
- if( obj === undefined ){
- return {
- startTime : this.startTime,
- endTime : this.endTime < 0 ? this.duration : this.endTime,
- loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
- loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
- loop : this.loop,
- looped : this.looped,
- volume : this.volume,
- playing : this.playing,
- duration : this.duration,
-
- currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0 ) : this.seekTime,
- error : this.error
- };
- };
- result = X_AudioWrapper_updateStates( this, obj );
-
+ getActualCurrentTime : function(){
+ return X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0;
+ },
+
+ afterUpdateState : function( result ){
if( result & 2 || result & 1 ){ // seek
- this.play();
+ this.actualPlay();
} else
if( result & 4 ){
- this.gainNode.gain.value = this.volume;
+ this.gainNode.gain.value = this.gain;
};
}
}
);
+ function X_WebAudio_handleEvent( e ){
+ switch( e.type ){
+
+ case X_EVENT_KILL_INSTANCE :
+ this.loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete )
+ .unregister( this );
+
+ delete this.buffer;
+
+ this.playing && this.actualPause();
+ this.source && this._sourceDispose();
+
+ this._onended && X_Callback_correct( this._onended );
+
+ this.gainNode && this.gainNode.disconnect();
+ break;
+ };
+ };
+
/*
* http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1
* L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。