OSDN Git Service

Version 0.6.121, iScroll5 is working on iPhone touch 1G, update X.Audio.
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 01_XWebAudio.js
index 8299229..84216ed 100644 (file)
@@ -1,76 +1,52 @@
 
-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.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,
                                                
                        _startTime      : 0,
+                       _endTime        : 0,
                        _playTime       : 0,
             _timerID        : 0,
             _interval       : 0,
                buffer          : null,
+               source          : null,
             gainNode        : null,
             _onended        : null,
             
@@ -78,13 +54,25 @@ if( X_Audio_WebAudio_context ){
             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 ){
@@ -96,9 +84,23 @@ if( X_Audio_WebAudio_context ){
                                                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 ) );
+                                               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 の処理がキャンセルされることがある(エラーやコールバックの発火もなく、ただ処理が消滅する)。
+                                               if( X_Audio_WebAudio_context.createBuffer && X_UA.iOS ){
+                                                       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 :
@@ -108,7 +110,7 @@ if( X_Audio_WebAudio_context ){
 
                                        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 );
@@ -116,39 +118,58 @@ if( X_Audio_WebAudio_context ){
                        },
                        
                                _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.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();
        
@@ -158,48 +179,54 @@ if( X_Audio_WebAudio_context ){
                        },
                        
                                _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._() でエラーになる。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._endTime   = end;
                    this._playTime  = X_Audio_WebAudio_context.currentTime * 1000;
                    this._interval  = this._interval || X.Timer.add( 1000, 0, this, this._onInterval );
                        },
@@ -209,21 +236,36 @@ if( X_Audio_WebAudio_context ){
                                                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._playTime - this._endTime + this._startTime | 0;
+                               //console.log( '> onEnd ' + ( this.playing && ( X_Audio_WebAudio_context.currentTime * 1000 - this._playTime ) ) + ' < ' + ( this._endTime - this._startTime ) );
+                               if( this._onended ){
+                                       // Firefox 用の対策,,,
+                                       if( time < 0 ) return;
+                               } else {
+                                       if( time < 0 ){
+                                               console.log( '> onEnd ' + ( -time ) + ' start:' + this._startTime + '-' + this._endTime );
+                                               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 );
                                };
                            };
                                },
@@ -233,6 +275,8 @@ if( X_Audio_WebAudio_context ){
                                
                                console.log( '[WebAudio] pause' );
                                
+                               this.seekTime = this.state().currentTime;
+                               
                    this._timerID && X.Timer.remove( this._timerID );
                                delete this._timerID;
                                delete this.playing;
@@ -246,31 +290,29 @@ if( X_Audio_WebAudio_context ){
                        },
        
                        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._playTime + this._startTime | 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;
@@ -280,4 +322,17 @@ if( X_Audio_WebAudio_context ){
                }
        );
 
+
+       X_Audio_BACKENDS.push(
+               {
+                       backendName : 'Web Audio',
+
+                       // 
+                       detect : function( proxy, source, ext ){
+                               proxy.asyncDispatch( X_Audio_codecs[ ext ] ? X_Audio_CAN_PLAY : X_Audio_NOT_PLAY );
+                       },
+                       
+                       klass : X_Audio_WebAudioWrapper
+               }
+       );
 };