X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F00_XAudio.js;h=b12e679e9190911344bcb060a67b8a0ed7c4c5bc;hb=e28511741c97176b8ffe67bb1ea0660da37f754b;hp=5572486fee13fcc3201bcd22ab7aa46852f1894f;hpb=b8b45541169da1fad8e5454c266f837ea3c52961;p=pettanr%2FclientJs.git diff --git a/0.6.x/js/07_audio/00_XAudio.js b/0.6.x/js/07_audio/00_XAudio.js index 5572486..b12e679 100644 --- a/0.6.x/js/07_audio/00_XAudio.js +++ b/0.6.x/js/07_audio/00_XAudio.js @@ -1,5 +1,5 @@  -X.Audio = { +/* WebAudio : 1, HTML5 : 2, Flash : 3, @@ -8,279 +8,456 @@ X.Audio = { WMP : 6, RealPlayer : 7, QuickTime : 8, - - create : function( sourceList, opt_option ){ - return new X_AudioProxy( X.Type.isArray( sourceList ) ? X_Object_cloneArray( sourceList ) : [ sourceList ], opt_option || {} ); + */ + +var X_Audio_BACKENDS = []; // Array. + +X_TEMP.onSystemReady.push( + function(){ + var canPlay = X[ 'Audio' ][ 'canPlay' ] = {}, + i = X_Audio_BACKENDS.length; + for( ; i; ){ + X_Object_override( canPlay, X_Audio_BACKENDS[ --i ].canPlay ); + }; + }); + +/** + *

複数のバックエンドから、与えられた音声を再生可能なものを見つけ、音声を再生します。 + *

HTMLAudio の動作・機能がブラウザ毎にバラバラなのに業を煮やし、メソッドやイベントは独自に定義しています。 + *

バックエンドの種類

+ *

HTMLAudio, WebAudio, Silverlight + *

イベント

+ *
+ *
X.Event.BACKEND_READY
音声(src リスト)を再生可能なバックエンドが見つかった。 + *
X.Event.BACKEND_NONE
音声を再生可能なバックエンドが見つからなかった。 + *
X.Event.READY
再生可能、実際の状態は canplay から loadeddata まで様々、、、 + *
X.Event.ERROR
    + *
  • 1 : ユーザーによってメディアの取得が中断された + *
  • 2 : ネットワークエラー + *
  • 3 : メディアのデコードエラー + *
  • 4 : メディアがサポートされていない + *
+ *
X.Event.MEDIA_PLAYING
再生中に1秒以下のタイミングで発生.currentTime が取れる? + *
X.Event.MEDIA_LOOP
ループ直前に発生、キャンセル可能 + *
X.Event.MEDIA_LOOPED
ループ時に発生 + *
X.Event.MEDIA_ENDED
再生位置の(音声の)最後についた + *
X.Event.MEDIA_PAUSED
ポーズした + *
X.Event.MEDIA_WAITING
再生中に音声が待機状態に。間もなく X.Event.MEDIA_PLAYING に移行。 + *
X.Event.MEDIA_SEEKING
シーク中に音声が待機状態に。間もなく X.Event.MEDIA_PLAYING に移行。 + *
+ * + * @alias X.Audio + * @class 各種オーディオ機能をラップしインターフェイスを共通化する。 + * @constructs Audio + * @extends {EventDispatcher} + * @param {array|string} sourceList + * @param {object=} opt_option + * @example // + * var audio = X.Audio( [ 'etc/special.mp3', 'etc/special.ogg', 'etc/special.wav' ] ) + .listenOnce( X.Event.READY, onReady ); + */ +X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( + 'X.Audio', + X_Class.POOL_OBJECT, + { + /** + * 音声の url。X.Event.BACKEND_READY で設定される。 + * @alias Audio.prototype.source + * @type {string} + */ + 'source' : '', + /** + * 音声再生バックエンドの名前。X.Event.BACKEND_READY で設定される。 + * @alias Audio.prototype.backendName + * @type {string} + */ + 'backendName' : '', + + 'Constructor' : function( sourceList, opt_option ){ + X_Audio_startDetectionBackend( + X_Audio_BACKENDS[ 0 ], this, + X_Type_isArray( sourceList ) ? X_Array_copy( sourceList ) : [ sourceList ], + opt_option || {} ); + this[ 'listenOnce' ]( [ X_EVENT_BACKEND_READY, X_EVENT_BACKEND_NONE, X_EVENT_KILL_INSTANCE ], X_Audio_handleEvent ); + }, + + /** + * 再生。開始位置・終了位置、ループの有無、ループ以降の開始位置、ループ以降の終了位置 + * @alias Audio.prototype.play + */ + 'play' : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ + var pair = X_Pair_get( this ); + pair && pair.play( startTime, endTime, loop, loopStartTime, loopEndTime ); + return this; + }, + /** + * シーク + * @alias Audio.prototype.seek + */ + 'seek' : function( seekTime ){ + var pair = X_Pair_get( this ); + pair && pair.seek( seekTime ); + return this; + }, + /** + * ポーズ + * @alias Audio.prototype.pause + */ + 'pause' : function(){ + var pair = X_Pair_get( this ); + pair && pair.pause(); + return this; + }, + /** + * 状態の getter と setter + * @alias Audio.prototype.state + */ + 'state' : function( obj ){ + var pair = X_Pair_get( this ); + if( obj === undefined ){ + return pair ? pair.getState() : + { + 'startTime' : -1, + 'endTime' : -1, + 'loopStartTime' : -1, + 'loopEndTime' : -1, + 'currentTime' : -1, + 'loop' : false, + 'looded' : false, + 'error' : false, + 'playing' : false, + 'source' : this[ 'source' ] || '', + 'duration' : 0 + }; + }; + pair && pair.setState( obj ); + return this; + }, + /** + * ループの getter と setter + * @alias Audio.prototype.loop + */ + 'loop' : function( v ){ + var pair = X_Pair_get( this ); + pair && pair.loop( v ); + return this; + }, + /** + * ボリュームの getter と setter 実装不十分! + * @alias Audio.prototype.volume + */ + 'volume' : function( v ){ + var pair = X_Pair_get( this ); + pair && pair.volume( v ); + return this; + }, + /** + * 再生位置。 + * @alias Audio.prototype.currentTime + */ + 'currentTime' : function( v ){ + var pair = X_Pair_get( this ); + pair && pair.currentTime( v ); + return this; + }, + /** + * 再生中か? + * @alias Audio.prototype.isPlaying + */ + 'isPlaying' : function(){ + var pair = X_Pair_get( this ); + return pair && pair.playing; + } + } +); + +function X_Audio_handleEvent( e ){ + var backend; + + switch( e.type ){ + case X_EVENT_BACKEND_READY : + backend = X_Audio_BACKENDS[ e[ 'backendID' ] ]; + + this[ 'unlisten' ]( X_EVENT_BACKEND_NONE, X_Audio_handleEvent ); + this[ 'source' ] = e[ 'source' ]; + this[ 'backendName' ] = backend.backendName; + X_Pair_create( this, backend.klass( this, e[ 'source' ], e[ 'option' ] ) ); + break; + + case X_EVENT_BACKEND_NONE : + this[ 'kill' ](); + break; + + case X_EVENT_KILL_INSTANCE : + backend = X_Pair_get( this ); + backend && backend[ 'kill' ](); + X_Pair_release( this, backend ); + break; + }; }; -var X_Audio_BACKENDS = [], - X_Audio_WRAPPER_LIST = []; /* * TODO preplayerror play してみたら error が出た、backend の変更。 */ -function X_Audio_detectBackend( proxy, sourceList, option ){ - var source = sourceList.shift() || '', - parts = X_URL_cleanup( source ).split( '.' ), - ext = parts[ parts.length - 1 ], - backend = X_Audio_BACKENDS[ 0 ], - ext, sup; +function X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ){ + var source = sourceList[ 0 ] || '', + ext = X_URL_getEXT( source ), + sup; if( source && backend ){ - sup = [ proxy, option, sourceList, source, ext ]; + sup = [ xaudio, sourceList, option, source, ext ]; sup[ 5 ] = sup; - proxy.listenOnce( [ 'support', 'nosupport' ], backend, X_Audio_detectComplete, sup ); - backend.detect( proxy, source, ext ); + xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup ); + backend.detect( xaudio, source, ext ); } else { - proxy.asyncDispatch( 'nobackend' ); + xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); }; }; -function X_Audio_detectComplete( e, proxy, option, sourceList, source, ext, sup ){ +function X_Audio_onEndedDetection( e, xaudio, sourceList, option, source, ext, sup ){ var i = X_Audio_BACKENDS.indexOf( this ), backend; - proxy.unlisten( [ 'support', 'nosupport' ], backend, X_Audio_detectComplete, sup ); - - switch( e.type ){ - case 'support' : - proxy._backend = i; - proxy.asyncDispatch( { - type : 'backendfound', - option : option, - source : source, - backendName : this.backendName - } ); - break; - case 'nosupport' : - if( backend = X_Audio_BACKENDS[ i + 1 ] ){ - proxy.listenOnce( [ 'support', 'nosupport' ], backend, X_Audio_detectComplete, sup ); - backend.detect( proxy, source, ext ); - } else - if( sourceList.length ){ - X_Audio_detectBackend( proxy, sourceList, option ); - } else { - proxy.asyncDispatch( 'nobackend' ); - }; - break; + if( e.canPlay ){ + xaudio[ 'asyncDispatch' ]( { + type : X_EVENT_BACKEND_READY, + 'option' : option, + 'source' : source, + 'backendName' : this[ 'backendName' ], + 'backendID' : i + } ); + } else { + console.log( 'No ' + source + ' ' + this[ 'backendName' ] ); + if( sup[ 3 ] = source = sourceList[ sourceList.indexOf( source ) + 1 ] ){ + sup[ 4 ] = ext = X_URL_getEXT( source ); + xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, this, X_Audio_onEndedDetection, sup ); + this.detect( xaudio, source, ext ); + } else + if( backend = X_Audio_BACKENDS[ i + 1 ] ){ + X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ); + } else { + xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); + }; }; }; -function X_AudioProxy_getAudioWrapper( proxy ){ - var i = X_Audio_WRAPPER_LIST.length; - for( ; i; ){ - if( X_Audio_WRAPPER_LIST[ --i ].proxy === proxy ) return X_Audio_WRAPPER_LIST[ i ]; - }; -}; -var X_AudioProxy = X.EventDispatcher.inherits( - 'X.AV.AudioProxy', - X.Class.POOL_OBJECT, + +var X_Audio_AbstractAudioBackend = X_EventDispatcher[ 'inherits' ]( + 'X.AbstractAudioBackend', + X_Class.ABSTRACT, { - source : '', - backendName : '', - _backend : -1, - Constructor : function( sourceList, option ){ - X_Audio_detectBackend( this, sourceList, option ); - this.listenOnce( [ 'backendfound', 'nobackend', X.Event.KILL_INSTANCE ], X_AudioProxy_handleEvent ); - }, + url : '', + target : null, - close : function(){ - return this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ).close(); - }, + startTime : 0, + endTime : -1, + loopStartTime : -1, + loopEndTime : -1, + seekTime : -1, + duration : 0, + + playing : false, + error : 0, + autoLoop : false, + looped : false, + autoplay : false, + gain : 0.5, play : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ - var state, duration; if( 0 <= startTime ){ - this.state( { - currentTime : startTime, - startTime : startTime, - endTime : endTime, - loop : loop, - loopStartTime : loopStartTime, - loopEndTime : loopEndTime + this.setState( { + 'currentTime' : startTime, + 'startTime' : startTime, + 'endTime' : endTime, + 'loop' : loop, + 'loopStartTime' : loopStartTime, + 'loopEndTime' : loopEndTime } ); }; - this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ).play(); - return this; + this.actualPlay(); }, seek : function( seekTime ){ - var state = this.state(), - end = X_AudioWrapper_getEndTime( X_AudioProxy_getAudioWrapper( this ) ); - if( seekTime < end ){ - this.state( { currentTime : seekTime } ); + if( seekTime < X_AudioWrapper_getEndTime( this ) ){ + this.setState( { 'currentTime' : seekTime } ); }; - return this; }, pause : function(){ - this.state().playing && X_AudioProxy_getAudioWrapper( this ).pause(); - return this; - }, - - state : function( obj ){ - var backend = this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ); - - if( obj === undefined ){ - return backend ? - backend.state() : - { - startTime : -1, - endTime : -1, - loopStartTime : -1, - loopEndTime : -1, - currentTime : -1, - loop : false, - looded : false, - error : false, - playing : false, - - source : this.source || '', - duration : 0 - }; - }; - backend && backend.state( obj ); - return this; + this.playing && this.actualPause(); }, loop : function( v ){ - var backend = this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ); if( v === undefined ){ - return backend && backend.state().loop; + return this.autoLoop; }; - backend && backend.state( { loop : v } ); - return this; + this.setState( { 'loop' : v } ); }, volume : function( v ){ - var backend = this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ); if( v === undefined ){ - return backend && backend.state().volume; + return this.gain; }; - backend && backend.state( { volume : v } ); - return this; + this.setState( { 'volume' : v } ); }, currentTime : function( v ){ - var backend = this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ); if( v === undefined ){ - return backend && backend.state().currentTime; + return this.playing ? this.getActualCurrentTime() : this.seekTime; }; - backend && backend.state( { currentTime : v } ); - return this; + this.setState( { 'currentTime' : v } ); }, - - isPlaying : function(){ - return this._backend !== -1 && X_AudioProxy_getAudioWrapper( this ).state().playing; - } - } -); - -function X_AudioProxy_handleEvent( e ){ - switch( e.type ){ - case 'backendfound' : - this.unlisten( 'nobackend', X_AudioProxy_handleEvent ); - this.source = e.source; - this.backendName = X_Audio_BACKENDS[ this._backend ].backendName; - X_Audio_WRAPPER_LIST.push( new X_Audio_BACKENDS[ this._backend ].klass( this, e.source, e.option ) ); - break; - - case 'nobackend' : - this.kill(); - break; + getState : function(){ + + 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.autoLoop, + 'looped' : this.looped, + 'volume' : this.gain, + 'playing' : this.playing, + 'duration' : this.duration, + + 'currentTime' : this.playing ? this.getActualCurrentTime() : this.seekTime, + 'error' : this.getActualError ? this.getActualError() : this.error + }; + }, - case X.Event.KILL_INSTANCE : - this.close(); - break; - }; -}; - -function X_AudioWrapper_updateStates( audioWrapper, obj ){ - var playing = audioWrapper.playing, - k, v, - end = 0, seek = 0, volume = 0; - - for( k in obj ){ - v = obj[ k ]; - switch( k ){ - case 'currentTime' : - v = X_AudioWrapper_timeStringToNumber( v ); - if( X.Type.isNumber( v ) ){ - if( playing ){ - if( audioWrapper.state().currentTime !== v ){ - audioWrapper.seekTime = v; - seek = 2; + setState : function( obj ){ + var playing = this.playing, + k, v, + end = 0, seek = 0, volume = 0; + + for( k in obj ){ + v = obj[ k ]; + switch( k ){ + case 'currentTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( X_Type_isNumber( v ) ){ + if( playing ){ + if( this.getActualCurrentTime() !== v ){ + seek = 2; + this.seekTime = v; + }; + } else { + this.seekTime = v; + }; + } else { + continue; }; - } else { - audioWrapper.seekTime = v; - }; - } else { - continue; - }; - break; + break; + + case 'startTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.startTime !== v ){ + this.startTime = v; + }; + } else { + delete this.startTime; + }; + break; - case 'startTime' : - case 'endTime' : - case 'loopStartTime' : - case 'loopEndTime' : - v = X_AudioWrapper_timeStringToNumber( v ); - console.log( k + ' ' + v ); - if( v || v === 0 ){ - if( audioWrapper[ k ] !== v ){ - audioWrapper[ k ] = v; + case 'endTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.endTime !== v ){ + this.endTime = v; + if( playing ) end = 1; + }; + } else { + delete this.endTime; + if( playing ) end = 1; + }; + break; - // 再生中の endTime の変更 - if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1; - }; - } else { - delete audioWrapper[ k ]; - if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1; - }; - break; - - case 'looped' : - if( playing ) seek = 2; - case 'loop' : - case 'autoplay' : - if( X.Type.isBoolean( v ) && audioWrapper[ k ] !== v ){ - audioWrapper[ k ] = v; + case 'loopStartTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.loopStartTime !== v ){ + this.loopStartTime = v; + }; + } else { + delete this.loopStartTime; + }; + break; + + case 'loopEndTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.loopEndTime !== v ){ + this.loopEndTime = v; + if( playing ) end = 1; + }; + } else { + delete this.loopEndTime; + if( playing ) end = 1; + }; + break; + + case 'looped' : + if( X_Type_isBoolean( v ) && this.looped !== v ){ + this.looped = v; + if( playing ) seek = 2; + }; + break; + + case 'loop' : + if( X_Type_isBoolean( v ) && this.autoLoop !== v ){ + this.autoLoop = v; + }; + break; + + case 'autoplay' : + if( X_Type_isBoolean( v ) && this.autoplay !== v ){ + this.autoplay = v; + }; + break; + + case 'volume' : + if( X_Type_isNumber( v ) ){ + v = v < 0 ? 0 : 1 < v ? 1 : v; + if( this.gain !== v ){ + this.gain = v; + // if playing -> update + if( playing ) volume = 4; + }; + }; + break; + default : + throw 'bad arg'; }; - break; + }; + + if( this.endTime < this.startTime || + ( this.loopEndTime < 0 ? this.endTime : this.loopEndTime ) < ( this.loopStartTime < 0 ? this.startTime : this.loopStartTime ) || + X_AudioWrapper_getEndTime( this ) < this.seekTime// || + //this.duration < this.endTime + ){ + console.log( 'setState 0:' + this.startTime + ' -> ' + this.endTime + ' d:' + this.duration + ' 1:' + this.loopStartTime + ' -> ' + this.loopEndTime ); + return; + }; + + v = end + seek + volume; + return v && this.afterUpdateState( v ); + } + + } +); - case 'volume' : - if( X.Type.isNumber( v ) ){ - v = v < 0 ? 0 : 1 < v ? 1 : v; - if( audioWrapper[ k ] !== v ){ - audioWrapper[ k ] = v; - // if playing -> update - if( playing ) volume = 4; - }; - }; - break; - }; - }; - - if( audioWrapper.endTime < audioWrapper.startTime || - ( audioWrapper.loopEndTime < 0 ? audioWrapper.endTime : audioWrapper.loopEndTime ) < ( audioWrapper.loopStartTime < 0 ? audioWrapper.startTime : audioWrapper.loopStartTime ) || - X_AudioWrapper_getEndTime( audioWrapper ) < audioWrapper.seekTime// || - //audioWrapper.duration < audioWrapper.endTime - ){ - console.log( 'error @updateStateObject() begin:' + audioWrapper.startTime + ' end:' + audioWrapper.endTime + ' d:' + audioWrapper.duration + ' ls:' + audioWrapper.loopStartTime ); - return 0; - }; - - return end + seek + volume; -}; function X_AudioWrapper_timeStringToNumber( time ){ var ary, ms, s = 0, m = 0, h = 0; - if( X.Type.isNumber( time ) ) return time; - if( !X.Type.isString( time ) || !time.length ) return; + if( X_Type_isNumber( time ) ) return time; + if( !X_Type_isString( time ) || !time.length ) return; ary = time.split( '.' ); ms = parseInt( ( ary[ 1 ] + '000' ).substr( 0, 3 ) ) || 0;