var X_Audio_constructor = 3.1 <= X_UA[ 'Safari' ] && X_UA[ 'Safari' ] < 4 ?
function( s, a ){
- a = document.createElement( 'audio' );
- a.src = s;
- a.load();
+ //a = document.createElement( 'audio' );
+ //a.src = s;
+ //a.load();
return a;
} :
// Android1.6 + MobileOpera12 HTMLAudio はいるが呼ぶとクラッシュする
X_Audio_codecs;
+// WebAudioAPIを使っているはずなのに、マナーモードで音が出る!?
+// http://qiita.com/gonshi_com/items/e41dbb80f5eb4c176108
+// HTML Audio、もしくはHTML Videoをページ内で1つでも使用していた場合、そのページでは WebAudioAPI の音がマナーモード時にも鳴ってしまう
+
if( X_Audio_constructor ){
//http://himaxoff.blog111.fc2.com/blog-entry-97.html
//引数なしで new Audio() とすると、Operaでエラーになるそうなので注意。
};
-var X_WebAudio_context = !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] &&
+var X_WebAudio_Context = // 4s 以下ではない iPad 2G または iPad mini 1G 以下ではない, iPod touch 4G 以下ではない
+ !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] &&
+ // iOS7 以上で HTML Audio が鳴らない問題を見ていくよ
+ // !X_UA[ 'iOS' ] &&
// Android2 + Gecko で WebAudio が極めて不安定
- !X_UA[ 'Fennec' ] &&
+ !( X_UA[ 'Fennec' ] && X_UA[ 'Android' ] < 3 ) &&
+ // AOSP でも WebAudio を不完全に実装するものがある, touch の有無も不明のため一律に切ってしまう
+ !X_UA[ 'AOSP' ] && !( X_UA[ 'ChromeWV' ] < 5 ) &&
+ // Blink HTMLAudio 調査用
+ //!X_UA[ 'Blink' ] &&
// Firefox40.0.5 + Windows8 で音声が途中から鳴らなくなる
// Firefox41.0.1 + Windows8 で音声が途中から鳴らなくなる
- !( 40 <= X_UA[ 'Gecko' ] && X_UA[ 'Gecko' ] < 42 && X_UA[ 'Windows' ] ) &&
- ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] ),
+ !( 40 <= X_UA[ 'Gecko' ] && /* X_UA[ 'Gecko' ] < 48 && */ X_UA[ 'Windows' ] ) &&
+ ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] || window[ 'mozAudioContext' ] ),
+ X_WebAudio_context,
X_WebAudio_BUFFER_LIST = [],
X_WebAudio_need1stTouch = X_UA[ 'iOS' ],
+ X_WebAudio_isNoTouch = X_WebAudio_need1stTouch,
+ X_WebAudio_needRateFix = X_WebAudio_need1stTouch,
X_WebAudio,
X_WebAudio_BufferLoader,
X_WebAudio_fpsFix;
/*
* iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可
*/
-if( X_WebAudio_context ){
+if( X_WebAudio_Context ){
- X_WebAudio_context = new X_WebAudio_context;
+ X_WebAudio_context = new X_WebAudio_Context;
+
+ // http://lilting.ch/3323.html
+ // 【間に合わせ】iOS9系でのWebAudioの音割れ対処について
+ /*
+ if( X_WebAudio_needRateFix ){
+ X_WebAudio_context.close();
+ X_WebAudio_context = new X_WebAudio_Context;
+ }; */
+
+ /*
+ * TODO X_TEMP へ
+ * http://qiita.com/simiraaaa/items/79a9ac972cc76fb58d93
+ * [WebAudio API] iOS9で音が歪む、遅い、低い、割れる等の回避方法
+ */
+ if( X_WebAudio_needRateFix ){
+ X_TEMP.webAudioSampleRateFix = function( sampleRate ){
+ X_TEMP.webAudioDummyPlay( sampleRate );
+
+ if( true || X_WebAudio_context[ 'sampleRate' ] !== sampleRate ){
+ // alert( '[debug]iOSで音割れを検知、修復コードを実施 ctxSR:' + X_WebAudio_context[ 'sampleRate' ] + ' abfSR:' + sampleRate );
+
+ X_WebAudio_context.close && X_WebAudio_context.close();
+ X_WebAudio_context = new X_WebAudio_Context;
+
+ X_TEMP.webAudioDummyPlay( sampleRate );
+ };
+
+ delete X_TEMP.webAudioSampleRateFix;
+ delete X_TEMP.webAudioDummyPlay;
+ };
+ X_TEMP.webAudioDummyPlay = function( sampleRate, source ){
+ source = X_WebAudio_context[ 'createBufferSource' ]();
+ source.buffer = X_WebAudio_context[ 'createBuffer' ]( 1, 1, sampleRate );
+ source[ 'connect' ]( X_WebAudio_context[ 'destination' ] );
+ source.start ? source.start( 0 ) : source[ 'noteOn' ] ? source[ 'noteOn' ]( 0 ) : source[ 'noteGrainOn' ]( 0 );
+ };
+ };
X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ](
'X.WebAudio.BufferLoader',
},
handleEvent : function( e ){
+ var i, l;
+
switch( e.type ){
case X_EVENT_PROGRESS :
- this[ 'dispatch' ]( { type : 'progress', 'percent' : e[ 'percent' ] } );
+ for( i = 0, l = this.webAudioList.length; i < l; ++i ){
+ this.webAudioList[ i ][ 'dispatch' ]( { type : X_EVENT_PROGRESS, 'percent' : e[ 'percent' ] } );
+ };
return;
case X_EVENT_SUCCESS :
_onDecodeSuccess : function( buffer ){
this.onDecodeSuccess && this._onDecodeComplete();
- if ( !buffer ) {
+ if( !buffer ){
this.errorState = 2;
this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
return;
unregister : function( webAudio ){
var list = this.webAudioList,
i = list.indexOf( webAudio );
+
if( 0 < i ){
list.splice( i, 1 );
- if( list.length ){
+ if( !list.length ){
this.xhr && this.xhr[ 'kill' ]();
this[ 'kill' ]();
};
}
);
-
-
+
X_WebAudio = X_AudioBase[ 'inherits' ](
'X.WebAudio',
X_Class.POOL_OBJECT,
_startPos : 0,
_endPosition : 0,
_startTime : 0,
- _timerID : 0,
+ //_timerID : 0,
_interval : 0,
audioBuffer : null,
bufferSource : null,
gainNode : null,
- _onended : null,
- 'Constructor' : function( disatcher, url, option ){
+ bufferPlay : '',
+ bufferStop : '',
+ //_onended : null,
+
+ 'Constructor' : function( dispatcher, url, option ){
var i = 0,
l = X_WebAudio_BUFFER_LIST.length,
loader;
this.loader = loader = X_WebAudio_BufferLoader( this, url );
};
- this.disatcher = disatcher || this;
+ this.dispatcher = dispatcher || this;
this.setState( option );
this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, this.onKill );
} else {
loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
};
+
+ if( X_WebAudio_isNoTouch ){
+ X_TEMP.xWebAudioInstances = X_TEMP.xWebAudioInstances || [];
+ X_TEMP.xWebAudioInstances.push( this );
+ };
},
onKill : function(){
this.playing && this.actualPause();
this.bufferSource && this._sourceDispose();
- this._onended && X_Closure_correct( this._onended );
+ //this._onended && X_Closure_correct( this._onended );
this.gainNode && this.gainNode.disconnect();
+
+ if( X_WebAudio_isNoTouch ){
+ X_TEMP.xWebAudioInstances.splice( X_TEMP.xWebAudioInstances.indexOf( this ), 1 );
+ };
},
_onLoadBufferComplete : function( e ){
var loader = this.loader,
if ( !buffer ) {
this.error = loader.errorState;
-
- this.disatcher[ 'dispatch' ]({
+ this.dispatcher[ 'dispatch' ]({
type : X_EVENT_ERROR,
error : loader.errorState,
message : loader.errorState === 1 ?
this.audioBuffer = buffer;
this.duration = buffer.duration * 1000;
- this.disatcher[ 'asyncDispatch' ]( X_EVENT_READY );
-
- console.log( 'WebAudio buffer ready' );
-
- this.autoplay && X_Timer_once( 16, this, this.play );
-
+ this.dispatcher[ 'asyncDispatch' ]( X_WebAudio_isNoTouch ? X_EVENT_MEDIA_WAIT_FOR_TOUCH : X_EVENT_READY );
},
actualPlay : function(){
- var begin, end;
+ var xWebAudio, begin, end;
- console.log( '[WebAudio] play abuf:' + !!this.audioBuffe );
+ console.log( '[WebAudio] play abuf:' + !!this.audioBuffer );
if( !this.audioBuffer ){
- this.autoplay = true;
+ this._playReserved = true;
return;
};
+
+ if( X_WebAudio_isNoTouch ){
+ //@dev{
+ var e = X_EventDispatcher_CURRENT_EVENTS[ X_EventDispatcher_CURRENT_EVENTS.length - 1 ];
+ if( !e || !e[ 'pointerType' ] ){
+ // alert( 'タッチイベント以外での play! ' + ( e ? e.type : '' ) );
+ return;
+ };
+ //@}
+
+ // http://qiita.com/uupaa/items/e5856e3cb2a9fc8c5507
+ // iOS9 + touchstart で呼んでいた場合、 X_ViewPort['listenOnce']('pointerup',this,this.actualPlay())
+ this.dispatcher[ 'asyncDispatch' ]( X_EVENT_READY );
+
+ // Web Audio インスタンスが複数生成された場合、一つの Web Audio に対してタッチによる play が開始されたら、
+ // 1. 同時に生成された他の X.Audio インスタンスに対して READY イベントを発する
+ // 2. 以降の X.Audio インスタンス生成時に needTouchForPlay フラグは false
+ // ちなみに HTML Audio インスタンスは各々にタッチが必要
+ //X_WebAudio_isNoTouch = false;
+
+ while( xWebAudio = X_TEMP.xWebAudioInstances.pop() ){
+ xWebAudio !== this && xWebAudio[ 'asyncDispatch' ]( X_EVENT_READY );
+ };
+ delete X_TEMP.xWebAudioInstances;
+
+ X_WebAudio_isNoTouch = false;
+
+ X_TEMP.webAudioSampleRateFix && X_TEMP.webAudioSampleRateFix( this.audioBuffer[ 'sampleRate' ] );
+ };
end = X_Audio_getEndTime( this );
begin = X_Audio_getStartTime( this, end, true );
- console.log( '[WebAudio] play ' + begin + ' -> ' + end );
-
- if( this.bufferSource ) this._sourceDispose();
- if( !this.gainNode ){
- this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ]();
- this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] );
- };
- this.bufferSource = X_WebAudio_context[ 'createBufferSource' ]();
- this.bufferSource.buffer = this.audioBuffer;
- this.bufferSource[ 'connect' ]( this.gainNode );
-
- this.gainNode[ 'gain' ].value = this.gain;
+ console.log( '[WebAudio] play ' + begin + ' -> ' + end + ' loop: ' + this.autoLoop + ' :' + this.loopStartTime + ' -> ' + this.loopEndTime );
+ this._createTree( begin, end );
+ /* win8.1 Firefox45, win8.1 Chrome48 で動かなくなる...
+ if( this.bufferSource[ 'loop' ] = this.autoLoop ){
+ this.bufferSource[ 'loopStart' ] = 0 <= this.loopStartTime ? this.loopStartTime / 1000 : begin / 1000;
+ this.bufferSource[ 'loopEnd' ] = 0 <= this.loopEndTime ? this.loopEndTime / 1000 : end / 1000;
+ }; */
+
// おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1
// 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない
// 多くのブラウザで onended は timer を使ったカウントより遅いので使わない
//console.log( '> use onended' );
//this.bufferSource.onended = this._onended || ( this._onended = X_Closure_create( this, this._onEnded ) );
//} else {
- this._timerID && X_Timer_remove( this._timerID );
- this._timerID = X_Timer_once( end - begin, this, this._onEnded );
+ //this._timerID && X_Timer_remove( this._timerID );
+ //this._timerID = X_Timer_once( end - begin, this, this._onEnded );
//};
-
- if( this.bufferSource.start ){
- this.bufferSource.start( 0, begin / 1000, end / 1000 );
- } else {
- this.bufferSource[ 'noteGrainOn' ]( 0, begin / 1000, end / 1000 );
- };
-
+
this.playing = true;
this._startPos = begin;
this._endPosition = end;
this._startTime = X_WebAudio_context.currentTime * 1000;
- this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval );
+ this._interval = this._interval || X_Timer_add( 100, 0, this, this._onInterval );
},
+ _createTree : function( begin, end ){
+ if( this.bufferSource ) this._sourceDispose();
+
+ if( !this.gainNode ){
+ this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ]();
+ this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] );
+ };
+
+ this.bufferSource = X_WebAudio_context[ 'createBufferSource' ]();
+ this.bufferSource.buffer = this.audioBuffer;
+ this.bufferSource[ 'connect' ]( this.gainNode );
+
+ this.gainNode[ 'gain' ].value = this.gain;
+
+ if( !this.bufferPlay ){
+ this.bufferPlay = this.bufferSource.start ? 'start' : this.bufferSource[ 'noteOn' ] ? 'noteOn' : 'noteGrainOn';
+ this.bufferStop = this.bufferSource.stop ? 'stop' : 'noteOff';
+ };
+ // https://developer.mozilla.org/ja/docs/Web/API/AudioBufferSourceNode
+ // AudioBufferSourceNode.start()の呼び出しは一度しかできません。
+ this.bufferSource[ this.bufferPlay ]( 0, begin / 1000, ( end - begin ) / 1000 );
+ },
+
_sourceDispose : function(){
this.bufferSource.disconnect();
- delete this.bufferSource.onended;
+ //delete this.bufferSource.onended;
delete this.bufferSource;
},
-
+
_onInterval : function(){
- if( !this.playing ){
- delete this._interval;
- return X_CALLBACK_UN_LISTEN;
- };
- this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
- },
-
- _onEnded : function(){
var time;
- delete this._timerID;
if( this.playing ){
+ // TODO 再生中に終了時間だけ変えた場合!
time = X_WebAudio_context.currentTime * 1000 - this._startTime - this._endPosition + this._startPos | 0;
//console.log( '> onEnd ' + ( this.playing && ( X_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_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime +
- // ' from:' + this._startPos + ' to:' + this._endPosition );
- this._timerID = X_Timer_once( -time, this, this._onEnded );
- return;
- };
- };
+
+ if( time < 0 ){
+ this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );
+ return;
+ };
if( this.autoLoop ){
- if( !( this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){
+ if( !( this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){
this.looped = true;
- this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );
+ this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );
this.actualPlay();
+ } else {
+ delete this._interval;
+ return X_CALLBACK_UN_LISTEN;
};
} else {
this.actualPause();
- this.disatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
+ this.dispatcher[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );
};
};
},
actualPause : function(){
- //if( !this.playing ) return this;
-
console.log( '[WebAudio] pause' );
- this.seekTime = this.getActualCurrentTime();
-
- this._timerID && X_Timer_remove( this._timerID );
- delete this._timerID;
+ this._interval && X_Timer_remove( this._interval );
+ delete this._interval;
delete this.playing;
if( this.bufferSource ){
- if( this.bufferSource.onended ) delete this.bufferSource.onended;
-
- this.bufferSource.stop ?
- this.bufferSource.stop( 0 ) : this.bufferSource[ 'noteOff' ]( 0 );
+ this.bufferSource[ this.bufferStop ]( 0 );
};
},
canPlay : X_Audio_codecs,
- detect : function( proxy, source, ext ){
+ detect : function( proxy, ext /* hash */ ){
proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } );
},