OSDN Git Service

Version 0.6.175, fix X.UA & X.WebAudio.
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 01_XWebAudio.js
index 6d63435..bded21f 100644 (file)
+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();
+                                                                       return a;
+                                                               } :
+                                               // Android1.6 + MobileOpera12 HTMLAudio はいるが呼ぶとクラッシュする
+                                                 !( X_UA[ 'Android' ] < 2 ) ?
+                                                               window[ 'Audio' ] || window.HTMLAudioElement : null,
+       
+       // Blink5 Opera32 Win8 は HTMLAudio が壊れている、WebAudio は mp3 がデコードに失敗、ogg が動作
+       X_Audio_blinkOperaFix = X_UA[ 'BlinkOpera' ] && X_UA[ 'Windows' ],
+
+       X_Audio_codecs;
+
+if( X_Audio_constructor ){
+       //http://himaxoff.blog111.fc2.com/blog-entry-97.html
+       //引数なしで new Audio() とすると、Operaでエラーになるそうなので注意。
+       X_TEMP.rawAudio = new X_Audio_constructor( '' );
+       
+       // https://html5experts.jp/miyuki-baba/3766/
+       // TODO Chrome for Android31 で HE-AAC が低速再生されるバグ
+       // TODO Android4 標準ブラウザで ogg のシークが正しくない!
+       if( X_TEMP.rawAudio.canPlayType ){
+               X_Audio_codecs = {
+                 'mp3'  : X_TEMP.rawAudio.canPlayType('audio/mpeg'),
+                 'opus' : X_TEMP.rawAudio.canPlayType('audio/ogg; codecs="opus"'),
+                 'ogg'  : X_TEMP.rawAudio.canPlayType('audio/ogg; codecs="vorbis"'),
+                 'wav'  : X_TEMP.rawAudio.canPlayType('audio/wav; codecs="1"'),
+                 'aac'  : X_TEMP.rawAudio.canPlayType('audio/aac'),
+                 'm4a'  : X_TEMP.rawAudio.canPlayType('audio/x-m4a') + X_TEMP.rawAudio.canPlayType('audio/m4a') + X_TEMP.rawAudio.canPlayType('audio/aac'),
+                 'mp4'  : X_TEMP.rawAudio.canPlayType('audio/x-mp4') + X_TEMP.rawAudio.canPlayType('audio/mp4') + X_TEMP.rawAudio.canPlayType('audio/aac'),
+                 'weba' : X_TEMP.rawAudio.canPlayType('audio/webm; codecs="vorbis"')
+               };
+               (function( X_Audio_codecs, k, v ){
+                       for( k in X_Audio_codecs ){
+                               //if( X_EMPTY_OBJECT[ k ] ) continue;
+                               v = X_Audio_codecs[ k ];
+                               v = v && !!( v.split( 'no' ).join( '' ) );
+                               if( v ){
+                                       console.log( k + ' ' + X_Audio_codecs[ k ] );
+                                       X_Audio_codecs[ k ] = true;
+                               } else {
+                                       delete X_Audio_codecs[ k ];
+                               };
+                       };
+                       if( X_Audio_blinkOperaFix ) delete X_Audio_codecs[ 'mp3' ];
+               })( X_Audio_codecs );
+       } else {
+               // iOS3.2.3
+               X_Audio_codecs = {
+                 'mp3'  : X_UA[ 'IE' ] || X_UA[ 'Chrome' ] || ( X_UA[ 'Windows' ] && X_UA[ 'Safari' ]  ),
+                 'ogg'  : 5 <= X_UA[ 'Gecko' ] || X_UA[ 'Chrome' ] || X_UA[ 'Opera' ] ,
+                 'wav'  : X_UA[ 'Gecko' ] || X_UA[ 'Opera' ] || ( X_UA[ 'Windows' ] && X_UA[ 'Safari' ]  ),
+                 'aac'  : X_UA[ 'IE' ] || X_UA[ 'WebKit' ],
+                 'm4a'  : X_UA[ 'IE' ] || X_UA[ 'WebKit' ],
+                 'mp4'  : X_UA[ 'IE' ] || X_UA[ 'WebKit' ],
+                 'weba' : 2 <= X_UA[ 'Gecko' ] || 10.6 <= X_UA[ 'Opera' ] // firefox4+(Gecko2+)
+               };
+               (function( X_Audio_codecs, k ){
+                       for( k in X_Audio_codecs ){
+                               //if( X_EMPTY_OBJECT[ k ] ) continue;
+                               if( X_Audio_codecs[ k ] ){
+                                       console.log( k + ' ' + X_Audio_codecs[ k ] );
+                                       X_Audio_codecs[ k ] = true;
+                               } else {
+                                       delete X_Audio_codecs[ k ];
+                               };
+                       };
+               })( X_Audio_codecs );
+       };
+       
+       if( X_Audio_blinkOperaFix ){
+               X_Audio_constructor = null;
+               delete X_TEMP.rawAudio;
+       };
+};
+
 
-var X_Audio_WebAudio_context = !X_UA[ 'iPhone_4s' ]  && !X_UA[ 'iPad_2Mini1' ]  && !X_UA[ 'iPod_4' ]  &&
+var X_WebAudio_context = !X_UA[ 'iPhone_4s' ]  && !X_UA[ 'iPad_2Mini1' ]  && !X_UA[ 'iPod_4' ]  &&
                                                                // TODO なんで fennec を禁止?
                                                                !( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) &&
-                                                               // Firefox40.0.5 + Windows8 で音声が鳴らない
-                                                               !( X_UA[ 'Gecko' ] === 40 && X_UA[ 'Windows' ] ) &&
+                                                               // Firefox40.0.5 + Windows8 で音声が途中から鳴らなくなる
+                                                               // Firefox41.0.1 + Windows8 で音声が途中から鳴らなくなる
+                                                               !( 40 <= X_UA[ 'Gecko' ] && X_UA[ 'Gecko' ] < 42 && X_UA[ 'Windows' ] ) &&
                                                                ( window[ 'AudioContext' ] || window[ 'webkitAudioContext' ] ),
-       X_Audio_BUFFER_LIST      = [],
-       X_Audio_WebAudioWrapper,
-       X_Audio_BufferLoader,
-       X_Audio_fpsFix;
+       X_WebAudio_BUFFER_LIST      = [],
+       X_WebAudio,
+       X_WebAudio_BufferLoader,
+       X_WebAudio_fpsFix;
 
 /*
  * iPhone 4s 以下、iPad2以下、iPad mini 1以下, iPod touch 4G 以下は不可
  */
-if( X_Audio_WebAudio_context ){
+if( X_WebAudio_context ){
        
-       X_Audio_WebAudio_context = new X_Audio_WebAudio_context;
+       X_WebAudio_context = new X_WebAudio_context;
        
-       X_Audio_BufferLoader = X_EventDispatcher[ 'inherits' ](
+       X_WebAudio_BufferLoader = X_EventDispatcher[ 'inherits' ](
                'X.WebAudio.BufferLoader',
                X_Class.POOL_OBJECT,
                {
-                       url             : '',
+                       audioUrl        : '',
             xhr             : null,
             onDecodeSuccess : null,
             onDecodeError   : null,
             
-            buffer          : null,
-            error           : 0,
+            audioBuffer     : null,
+            errorState      : 0,
             webAudioList    : null,
             
                        'Constructor' : function( webAudio, url ){
                                this.webAudioList = [ webAudio ];
-                               this.url = url;
+                               this.audioUrl     = url;
                                this.xhr = X[ 'Net' ]( { 'xhr' : url, 'dataType' : 'arraybuffer' } )
                                                                        [ 'listen' ]( X_EVENT_PROGRESS, this )
                                                                        [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this );
+                               X_WebAudio_BUFFER_LIST.push( this );
                        },
                        
                        handleEvent : function( e ){
@@ -52,18 +133,18 @@ if( X_Audio_WebAudio_context ){
                                        // iOS 7.1 で decodeAudioData に処理が入った瞬間にスクリーンを長押しする(スクロールを繰り返す)と
                                        // decoeAudioData の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。
                                        // ただし iOS 8.1.2 では エラーになる
-                                               if( X_UA[ 'iOS' ] < 8 || !X_Audio_WebAudio_context[ 'decodeAudioData' ] ){
-                                                       this._onDecodeSuccess( X_Audio_WebAudio_context[ 'createBuffer' ]( e.response, false ) );
+                                               if( X_UA[ 'iOS' ] < 8 || !X_WebAudio_context[ 'decodeAudioData' ] ){
+                                                       this._onDecodeSuccess( X_WebAudio_context[ 'createBuffer' ]( e.response, false ) );
                                                } else
-                                               if( X_Audio_WebAudio_context[ 'decodeAudioData' ] ){
-                                                       X_Audio_WebAudio_context[ 'decodeAudioData' ]( e.response,
+                                               if( X_WebAudio_context[ 'decodeAudioData' ] ){
+                                                       X_WebAudio_context[ 'decodeAudioData' ]( e.response,
                                                                this.onDecodeSuccess = X_Closure_create( this, this._onDecodeSuccess ),
                                                                this.onDecodeError   = X_Closure_create( this, this._onDecodeError ) );
                                                };
                                                break;
 
                                        case X_EVENT_COMPLETE :
-                                               this.error = 1;                         
+                                               this.errorState = 1;                            
                                                this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
                                                break;
                                };
@@ -72,17 +153,17 @@ if( X_Audio_WebAudio_context ){
                        },
                        
                                _onDecodeSuccess : function( buffer ){
-                                       console.log( 'WebAudio decode success!' );
-                                       
                                        this.onDecodeSuccess && this._onDecodeComplete();
                                        
                        if ( !buffer ) {
-                               this.error = 2;
+                               this.errorState = 2;
                            this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
                            return;
                        };
+                       
+                       console.log( 'WebAudio decode success!' );
        
-                       this.buffer   = buffer;
+                       this.audioBuffer = buffer;
 
                                        this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
 
@@ -92,7 +173,7 @@ if( X_Audio_WebAudio_context ){
                                _onDecodeError : function(){
                                        console.log( 'WebAudio decode error!' );
                                        this._onDecodeComplete();
-                                       this.error = 2;
+                                       this.errorState = 2;
                                        this[ 'asyncDispatch' ]( X_EVENT_COMPLETE );
                                },
                                
@@ -119,7 +200,7 @@ if( X_Audio_WebAudio_context ){
        );
        
        
-       X_Audio_WebAudioWrapper = X_Audio_AbstractAudioBackend[ 'inherits' ](
+       X_WebAudio = X_AudioBase[ 'inherits' ](
                'X.WebAudio',
                X_Class.POOL_OBJECT,
                {
@@ -131,14 +212,14 @@ if( X_Audio_WebAudio_context ){
                        _startTime      : 0,
             _timerID        : 0,
             _interval       : 0,
-               buffer          : null,
+               audioBuffer     : null,
                bufferSource    : null,
             gainNode        : null,
             _onended        : null,
             
                        'Constructor' : function( target, url, option ){                                
                                var i = 0,
-                                       l = X_Audio_BUFFER_LIST.length,
+                                       l = X_WebAudio_BUFFER_LIST.length,
                                        loader;
 
                                /*
@@ -146,14 +227,14 @@ if( X_Audio_WebAudio_context ){
                                 * L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。
                                 * 描画レート(描画 FPS)が下がるとノイズが混ざり始め、レートを上げると再生結果が正常になるというもので、オーディオ処理が描画スレッドに巻き込まれているような動作を見せる。
                                 */
-                               if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] && !X_Audio_fpsFix ){
+                               if( X_UA[ 'Android' ] && X_UA[ 'Chrome' ] && !X_WebAudio_fpsFix ){
                                        X_Node_systemNode.create( 'div', { id : 'fps-slowdown-make-sound-noisy' } );
-                                       X_Audio_fpsFix = true;
+                                       X_WebAudio_fpsFix = true;
                                };
 
                                for( ; i < l; ++i ){
-                                       loader = X_Audio_BUFFER_LIST[ i ];
-                                       if( loader.url === url ){
+                                       loader = X_WebAudio_BUFFER_LIST[ i ];
+                                       if( loader.audioUrl === url ){
                                                this.loader = loader;
                                                loader.webAudioList.push( this );
                                                break;
@@ -161,7 +242,7 @@ if( X_Audio_WebAudio_context ){
                                };
                                
                                if( !this.loader ){
-                                       this.loader = loader = new X_Audio_BufferLoader( this, url );
+                                       this.loader = loader = X_WebAudio_BufferLoader( this, url );
                                };
                                
                                this.target  = target || this;
@@ -170,7 +251,7 @@ if( X_Audio_WebAudio_context ){
                                
                                this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, this.onKill );
                                
-                               if( loader.buffer || loader.error ){
+                               if( loader.audioBuffer || loader.errorState ){
                                        this._onLoadBufferComplete();
                                } else {
                                        loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
@@ -181,7 +262,7 @@ if( X_Audio_WebAudio_context ){
                                this.loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete )
                                        .unregister( this );
 
-                               delete this.buffer;
+                               delete this.audioBuffer;
                                
                                this.playing      && this.actualPause();
                    this.bufferSource && this._sourceDispose();
@@ -192,17 +273,17 @@ if( X_Audio_WebAudio_context ){
                        },
                                _onLoadBufferComplete : function( e ){
                                        var loader = this.loader,
-                                               buffer = loader.buffer;
+                                               buffer = loader.audioBuffer;
                                        
                                        e && loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete );
                                        
                        if ( !buffer ) {
-                               this.error = loader.error;
+                               this.error = loader.errorState;
                                
                            this.target[ 'dispatch' ]({
                                                                type    : X_EVENT_ERROR,
-                                                               error   : loader.error,
-                                                               message : loader.error === 1 ?
+                                                               error   : loader.errorState,
+                                                               message : loader.errorState === 1 ?
                                                                                        'load buffer network error' :
                                                                                        'buffer decode error'
                                                        });
@@ -210,8 +291,8 @@ if( X_Audio_WebAudio_context ){
                            return;
                        };
        
-                       this.buffer   = buffer;
-                       this.duration = buffer.duration * 1000;
+                       this.audioBuffer = buffer;
+                       this.duration    = buffer.duration * 1000;
 
                                        this.target[ 'asyncDispatch' ]( X_EVENT_READY );
                        
@@ -224,23 +305,23 @@ if( X_Audio_WebAudio_context ){
                        actualPlay : function(){
                                var begin, end;
                                
-                   if( !this.buffer ){
+                   if( !this.audioBuffer ){
                        this.autoplay = true;
                        return;
                    };
                                
-                               end   = X_AudioWrapper_getEndTime( this );
-                               begin = X_AudioWrapper_getStartTime( this, end, true );
+                               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_Audio_WebAudio_context[ 'createGain' ] ? X_Audio_WebAudio_context[ 'createGain' ]() : X_Audio_WebAudio_context[ 'createGainNode' ]();
-                       this.gainNode[ 'connect' ]( X_Audio_WebAudio_context[ 'destination' ] );
+                                       this.gainNode = X_WebAudio_context[ 'createGain' ] ? X_WebAudio_context[ 'createGain' ]() : X_WebAudio_context[ 'createGainNode' ]();
+                       this.gainNode[ 'connect' ]( X_WebAudio_context[ 'destination' ] );
                                };
-                   this.bufferSource        = X_Audio_WebAudio_context[ 'createBufferSource' ]();
-                   this.bufferSource.buffer = this.buffer;
+                   this.bufferSource        = X_WebAudio_context[ 'createBufferSource' ]();
+                   this.bufferSource.buffer = this.audioBuffer;
                    this.bufferSource[ 'connect' ]( this.gainNode );
                    
                    this.gainNode[ 'gain' ].value = this.gain;
@@ -264,7 +345,7 @@ if( X_Audio_WebAudio_context ){
                    this.playing      = true;
                    this._startPos    = begin;
                    this._endPosition = end;
-                   this._startTime   = X_Audio_WebAudio_context.currentTime * 1000;
+                   this._startTime   = X_WebAudio_context.currentTime * 1000;
                    this._interval    = this._interval || X_Timer_add( 1000, 0, this, this._onInterval );
                        },
                        
@@ -287,14 +368,14 @@ if( X_Audio_WebAudio_context ){
                                        delete this._timerID;
                                        
                            if( this.playing ){
-                               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 ) );
+                               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_Audio_WebAudio_context.currentTime * 1000 ) + ' startTime:' + this._startTime +
+                                               //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;
@@ -334,7 +415,7 @@ if( X_Audio_WebAudio_context ){
                        },
                        
                        getActualCurrentTime : function(){
-                               return X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0;
+                               return X_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0;
                        },
                        
                        afterUpdateState : function( result ){
@@ -351,16 +432,17 @@ if( X_Audio_WebAudio_context ){
 
        X_Audio_BACKENDS.push(
                {
-                       backendName : 'Web Audio',
+                       backendID   : 1,
+                       
+                       backendName : 'WebAudio',
 
-                       canPlay : {}, // TODO HTMLAudio と同じ
+                       canPlay     : X_Audio_codecs,
 
-                       // 
-                       detect : function( proxy, source, ext ){
+                       detect      : function( proxy, source, ext ){
                                proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : X_Audio_codecs[ ext ] } );
                        },
                        
-                       klass : X_Audio_WebAudioWrapper
+                       klass : X_WebAudio
                }
        );
 };