-var X_Audio_WebAudio_context = window.webkitAudioContext || window.AudioContext,
- X_Audio_WebAudio_LIVE_LIST = [],
- X_Audio_WebAudio_POOL_LIST = [],
- X_Audio_WebAudio, X_Audio_WebAudioWrapper, X_Audio_rawAudio;
+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_WebAudioWrapper;
+/*
+ * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可
+ */
if( X_Audio_WebAudio_context ){
X_Audio_WebAudio_context = new X_Audio_WebAudio_context;
- function getWebAudioWrapper( proxy ){
- var i = X_Audio_WebAudio_LIVE_LIST.length;
- for( ; i; ){
- if( X_Audio_WebAudio_LIVE_LIST[ --i ].proxy === proxy ) return X_Audio_WebAudio_LIVE_LIST[ i ];
+ 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_WebAudio =
- {
- backendName : 'Web Audio',
-
- detect : function( proxy, source, ext ){
- var ok = ext === 'mp3' || ext === 'ogg';
-
- proxy.asyncDispatch( ok ? 'support' : 'nosupport' );
- },
-
- register : function( proxy, source, option ){
- X_Audio_WebAudio_LIVE_LIST.push( new X_Audio_WebAudioWrapper( proxy, source, option ) );
- },
-
- close : function( proxy ){
- return getWebAudioWrapper( proxy ).close();
- },
-
- play : function( proxy, startTime, endTime, loop, loopStartTime ){
- return getWebAudioWrapper( proxy ).play( startTime, endTime, loop, loopStartTime );
- },
-
- pause : function( proxy ){
- return getWebAudioWrapper( proxy ).pause();
- },
-
- state : function( proxy, obj ){
- return getWebAudioWrapper( proxy ).state( obj );
- }
- };
-
- X_Audio_BACKENDS.push( X_Audio_WebAudio );
-
- X_Audio_WebAudioWrapper = X.EventDispatcher.inherits(
+ X_Audio_WebAudioWrapper = X_EventDispatcher[ 'inherits' ](
'X.AV.WebAudioWrapper',
X.Class.POOL_OBJECT,
{
+ url : '',
proxy : null,
startTime : 0,
- endTime : 0,
- loopStartTime : 0,
- seekTime : 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,
- _playTime : 0,
_timerID : 0,
_interval : 0,
buffer : null,
+ source : null,
gainNode : null,
_onended : null,
onDecodeSuccess : null,
onDecodeError : null,
- Constructor : function( proxy, source, option ){
- this.closed = false;
+ Constructor : function( proxy, url, option ){
+ var audio = X_Audio_WebAudio_getBuffer( url );
+
this.proxy = proxy;
- this.xhr = X.Net.xhrGet( source, 'arraybuffer' )
- .listen( X.Event.PROGRESS, this )
- .listenOnce( [ X.Event.SUCCESS, X.Event.COMPLETE, X.Event.CANCELED ], this );
+ 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.xhrGet( url, 'arraybuffer' )
+ [ 'listen' ]( X_EVENT_PROGRESS, this )
+ [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this );
+ };
},
handleEvent : function( e ){
switch( e.type ){
- case X.Event.PROGRESS :
+ case X_EVENT_PROGRESS :
e.percent ?
- this.proxy.dispatch( { type : 'progress', percent : e.percent } ) :
- this.proxy.dispatch( 'loadstart' );
+ this.proxy[ 'dispatch' ]( { type : 'progress', percent : e.percent } ) :
+ this.proxy[ 'dispatch' ]( 'loadstart' );
return;
- case X.Event.SUCCESS :
- X_Audio_WebAudio_context.decodeAudioData( e.data,
- this.callbackDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ),
- this.callbackDecodeError = X_Callback_create( this, this._onDecodeError ) );
+ 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
+
+ // http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1
+ // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と
+ // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。
+ // ただし iOS 8.1.2 では エラーになる
+ if( X_Audio_WebAudio_context.createBuffer && X_UA[ 'iOS' ] < 8 ){
+ this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
+ } else
+ if( X_Audio_WebAudio_context.decodeAudioData ){
+ X_Audio_WebAudio_context.decodeAudioData( e.data,
+ this.onDecodeSuccess = X_Callback_create( this, this._onDecodeSuccess ),
+ this.onDecodeError = X_Callback_create( this, this._onDecodeError ) );
+ } else {
+ this._onDecodeSuccess( X_Audio_WebAudio_context.createBuffer( e.data, false ) );
+ };
break;
- case X.Event.CANCELED :
+ case X_EVENT_CANCELED :
this.error = 1;
- this.proxy.dispatch( 'aborted' );
+ this.proxy[ 'dispatch' ]( 'aborted' );
break;
- case X.Event.COMPLETE :
+ case X_EVENT_COMPLETE :
this.error = 2;
- this.proxy.asyncDispatch( { type : 'error', message : 'xhr error' } );
+ this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'xhr error' } );
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, X_EVENT_CANCELED ], this );
delete this.xhr;
},
_onDecodeSuccess : function( buffer ){
- this._onDecodeComplete();
+ console.log( 'WebAudio decode success!' );
+
+ this.onDecodeSuccess && this._onDecodeComplete();
if ( !buffer ) {
- this.proxy.asyncDispatch( { type : 'error', message : 'buffer is ' + buffer } );
+ this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'buffer is ' + buffer } );
return;
};
this.buffer = buffer;
this.duration = buffer.duration * 1000;
- this.endTime = this.endTime || this.duration;
+ /*
+ this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' );
+ this.proxy[ 'asyncDispatch' ]( 'loadeddata' );
+ this.proxy[ 'asyncDispatch' ]( 'canplay' );
+ this.proxy[ 'asyncDispatch' ]( 'canplaythrough' );
+ */
+ this.proxy[ 'asyncDispatch' ]( X_EVENT_READY );
- this.proxy.asyncDispatch( 'loadedmetadata' );
- this.proxy.asyncDispatch( 'loadeddata' );
- this.proxy.asyncDispatch( 'canplay' );
- this.proxy.asyncDispatch( 'canplaythrough' );
+ this.autoplay && X_Timer_once( 16, this, this.play );
+
+ console.log( 'WebAudio decoded!' );
},
_onDecodeError : function(){
+ console.log( 'WebAudio decode error!' );
this._onDecodeComplete();
this.error = 3;
- this.proxy.asyncDispatch( { type : 'error', message : 'decode error' } );
+ this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'decode error' } );
},
_onDecodeComplete : function(){
- X_Callback_correct( this.callbackDecodeSuccess );
- delete this.callbackDecodeSuccess;
- X_Callback_correct( this.callbackDecodeError );
- delete this.callbackDecodeError;
+ X_Callback_correct( this.onDecodeSuccess );
+ delete this.onDecodeSuccess;
+ 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 ){
+ // 回収はあきらめる、、、
+ };
+
this.playing && this.pause();
this.source && this._sourceDispose();
},
_sourceDispose : function(){
- this.source && this.source.disconnect();
+ this.source.disconnect();
delete this.source.onended;
delete this.source;
},
- play : function( seekTime ){
- var begin;
+ play : function(){
+ var begin, end;
- if( !this.buffer ) return this;
+ if( !this.buffer ){
+ this.autoplay = true;
+ return;
+ };
- begin = ( seekTime || seekTime === 0 ) ? seekTime : this.playing ? this.loopStartTime : this.startTime;
+ end = X_AudioWrapper_getEndTime( this );
+ begin = X_AudioWrapper_getStartTime( this, end, true );
- console.log( '[WebAudio] play' );
+ console.log( '[WebAudio] play ' + begin + ' -> ' + end );
if( this.source ) this._sourceDispose();
- if( !this.gainNode ) this.gainNode = X_Audio_WebAudio_context.createGain();
-
+ if( !this.gainNode ){
+ this.gainNode = X_Audio_WebAudio_context.createGain ? X_Audio_WebAudio_context.createGain() : X_Audio_WebAudio_context.createGainNode();
+ this.gainNode.connect( X_Audio_WebAudio_context.destination );
+ };
this.source = X_Audio_WebAudio_context.createBufferSource();
this.source.buffer = this.buffer;
this.source.connect( this.gainNode );
- this.gainNode.connect( X_Audio_WebAudio_context.destination );
this.gainNode.gain.value = this.volume;
- this._timerID && X.Timer.remove( this._timerID );
-
- // おかしい、stop 前に外していても呼ばれる、、、@Firefox
- if( this.source.onended !== undefined ){
+ // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
+ // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない
+ if( false && this.source.onended !== undefined ){
//console.log( '> use onended' );
this.source.onended = this._onended || ( this._onended = X_Callback_create( this, this._onEnded ) );
} else {
- this._timerID = X.Timer.once( this.endTime - begin, this, this._onEnded );
+ this._timerID && X_Timer_remove( this._timerID );
+ this._timerID = X_Timer_once( end - begin, this, this._onEnded );
};
if( this.source.start ){
- this.source.start( 0, begin / 1000, this.endTime / 1000 );
+ this.source.start( 0, begin / 1000, end / 1000 );
} else {
- this.source.noteGrainOn( 0, begin / 1000, this.endTime / 1000 );
+ this.source.noteGrainOn( 0, begin / 1000, end / 1000 );
};
- this.playing = true;
- this._startTime = begin;
- this._playTime = X_Audio_WebAudio_context.currentTime * 1000;
- this._interval = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
+ this.playing = true;
+ this._startPos = begin;
+ this._endPosition = end;
+ this._startTime = X_Audio_WebAudio_context.currentTime * 1000;
+ this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval );
},
_onInterval : function(){
delete this._interval;
return X_Callback_UN_LISTEN;
};
- this.proxy.dispatch( 'timeupdate' );
+ this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
},
_onEnded : function(){
+ var time;
delete this._timerID;
+
if( this.playing ){
- console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this.endTime - this._startTime ) );
- // Firefox 用の対策,,,
- if( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime < this.endTime - this._startTime ) return;
+ time = X_Audio_WebAudio_context.currentTime * 1000 - this._startTime - this._endPosition + this._startPos | 0;
+ //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime ) ) + ' < ' + ( this._endPosition - this._startPos ) );
+ if( this._onended ){
+ // Firefox 用の対策,,,
+ if( time < 0 ) return;
+ } else {
+ if( time < 0 ){
+ console.log( '> onEnd crt:' + ( X_Audio_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime +
+ ' from:' + this._startPos + ' to:' + this._endPosition );
+ this._timerID = X_Timer_once( -time, this, this._onEnded );
+ return;
+ };
+ };
if( this.loop ){
- this.play();
+ if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){
+ this.looped = true;
+ this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );
+ this.play();
+ };
} else {
this.pause();
- this.proxy.dispatch( 'ended' );
+ this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
};
};
},
console.log( '[WebAudio] pause' );
- this._timerID && X.Timer.remove( this._timerID );
+ this.seekTime = this.state().currentTime;
+
+ this._timerID && X_Timer_remove( this._timerID );
delete this._timerID;
delete this.playing;
},
state : function( obj ){
- var time = this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) : 0,
- result, halfway;
+ var result;
if( obj === undefined ){
return {
startTime : this.startTime,
- endTime : this.endTime,
- loopStartTime : this.loopStartTime,
- currentTime : time + 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,
- error : this.error,
playing : this.playing,
- duration : this.duration
- };
+ 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 );
- if( result & 2 ){ // seek
- this.play( this.seekTime );
- delete this.seekTime;
- } else
- if( result & 1 ){
- this.play();
+ if( result & 2 || result & 1 ){ // seek
+ this.play();
} else
if( result & 4 ){
this.gainNode.gain.value = this.volume;
}
);
+
+ X_Audio_BACKENDS.push(
+ {
+ backendName : 'Web Audio',
+
+ //
+ detect : function( proxy, source, ext ){
+ proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } );
+ },
+
+ klass : X_Audio_WebAudioWrapper
+ }
+ );
};