X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F00_XAudio.js;h=017f79e376787fcfe7d980e52bad6cd1fc00385d;hb=0a4e04fb0af6e1b2e452d1a8c0822e723d32a0ee;hp=4675b135a7b1d514d0f557ae6fd6ed1aa685b24f;hpb=7f26e99d39211b5749c4ad62a84855404c7390a3;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 4675b13..017f79e 100644 --- a/0.6.x/js/07_audio/00_XAudio.js +++ b/0.6.x/js/07_audio/00_XAudio.js @@ -1,139 +1,514 @@  -X.Audio = { - HTML5 : 1, - Flash : 2, - Silverlight : 3, - Unity : 4, - WMP : 5, - RealPlayer : 6, - QuickTime : 7, - - create : function( sourceList, opt_option ){ - return new X_AudioProxy( X.Type.isArray( sourceList ) ? X_Object_cloneArray( sourceList ) : [ sourceList ], opt_option || {} ); +/* + WebAudio : 1, + HTML5 : 2, + Flash : 3, + Silverlight : 4, + Unity : 5, + WMP : 6, + RealPlayer : 7, + QuickTime : 8, + */ + +var X_Audio_BACKENDS = []; // Array. + +/** + *

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

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_Object_cloneArray( 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; + } + } +); + +// TODO +X[ 'Audio' ][ 'canPlay' ] = {}; + +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 = []; /* * TODO preplayerror play してみたら error が出た、backend の変更。 */ -function X_Audio_detectBackend( proxy, sourceList, option ){ - var source = sourceList.shift() || '', - parts = source.split( '?' )[ 0 ].split( '#' )[ 0 ].split( '.' ), - ext = parts[ parts.length - 1 ], - backend, ext, sup; +function X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ){ + var source = sourceList[ 0 ] || '', + ext = X_URL_getEXT( source ), + sup; - if( source && X_Audio_BACKENDS.length ){ - sup = [ proxy, option, sourceList, source, ext ]; + if( source && backend ){ + sup = [ xaudio, sourceList, option, source, ext ]; sup[ 5 ] = sup; - X_Audio_BACKENDS[ 0 ] - .detect( source, ext ) - .listenOnce( [ 'support', 'nosupport' ], X_Audio_detectComplete, sup ); + + xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup ); + backend.detect( xaudio, source, ext ); } else { - proxy.asyncDispatch( 0, '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; - this.unlisten( [ 'support', 'nosupport' ], X_Audio_detectComplete, sup ); - - switch( e.type ){ - case 'support' : - proxy._backend = i; - proxy.asyncDispatch( 0, { type : 'backendfound', option : option, source : source } ); - break; - case 'nosupport' : - if( backend = X_Audio_BACKENDS[ i + 1 ] ){ - backend.detect( source, ext ).listen( [ 'support', 'nosupport' ], X_Audio_detectComplete, sup ); - } else - if( sourceList.length ){ - X_Audio_detectBackend( proxy, sourceList, option ); - } else { - proxy.asyncDispatch( 0, '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 ); + }; }; }; -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_Audio_BACKENDS[ this._backend ].close.call( this ); - }, + 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( position ){ - //console.log( 'proxy play ' + this._backend ); - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].play.call( this, position ); + play : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ + if( 0 <= startTime ){ + this.setState( { + currentTime : startTime, + startTime : startTime, + endTime : endTime, + loop : loop, + loopStartTime : loopStartTime, + loopEndTime : loopEndTime + } ); + }; + this.actualPlay(); }, - pause : function(){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].pause.call( this ); + seek : function( seekTime ){ + if( seekTime < X_AudioWrapper_getEndTime( this ) ){ + this.setState( { currentTime : seekTime } ); + }; }, - stop : function(){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].stop.call( this ); - }, + pause : function(){ + this.playing && this.actualPause(); + }, loop : function( v ){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].loop.call( this, v ); - }, - - state : function(){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].state.call( this ); + if( v === undefined ){ + return this.autoLoop; + }; + this.setState( { loop : v } ); }, volume : function( v ){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].volume.call( this, v ); + if( v === undefined ){ + return this.gain; + }; + this.setState( { volume : v } ); }, - startTime : function( time ){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].startTime.call( this, time ); + currentTime : function( v ){ + if( v === undefined ){ + return this.playing ? this.getActualCurrentTime() : this.seekTime; + }; + this.setState( { currentTime : v } ); }, - - currentTime : function( time ){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].currentTime.call( this, time ); + + 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 + }; }, - - isPlaying : function(){ - return this._backend !== -1 && X_Audio_BACKENDS[ this._backend ].isPlaying.call( this ); + + 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; + }; + 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 '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; + + 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; + }; + }; + + 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 ); } } ); -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_BACKENDS[ this._backend ].register( this, e.source, e.option ); + +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; + + ary = time.split( '.' ); + ms = parseInt( ( ary[ 1 ] + '000' ).substr( 0, 3 ) ) || 0; + + ary = ary[ 0 ].split( ':' ); + if( 3 < ary.length ) return; + + switch( ary.length ){ + case 0 : break; - - case 'nobackend' : - this.kill(); + case 1 : + s = parseInt( ary[ 0 ] ) || 0; break; - - case X.Event.KILL_INSTANCE : - this.close(); + case 2 : + m = parseInt( ary[ 0 ] ) || 0; + s = parseInt( ary[ 1 ] ) || 0; + if( 60 <= s ) alert( 'invalid time string ' + time ); + break; + case 3 : + h = parseInt( ary[ 0 ] ) || 0; + m = parseInt( ary[ 1 ] ) || 0; + s = parseInt( ary[ 2 ] ) || 0; + if( 60 <= s ) alert( 'invalid time string ' + time ); + if( 60 <= m ) alert( 'invalid time string ' + time ); break; + default : + alert( 'invalid time string ' + time ); }; + ms = ( h * 3600 + m * 60 + s ) * 1000 + ms; + return ms < 0 ? 0 : ms; }; +function X_AudioWrapper_getStartTime( audioWrapper, endTime, delSeekTime ){ + var seek = audioWrapper.seekTime; + if( delSeekTime ) delete audioWrapper.seekTime; + + if( 0 <= seek ){ + if( audioWrapper.duration <= seek || endTime < seek ) return 0; + return seek; + }; + + if( audioWrapper.looped && 0 <= audioWrapper.loopStartTime ){ + if( audioWrapper.duration <= audioWrapper.loopStartTime || endTime < audioWrapper.loopStartTime ) return 0; + return audioWrapper.loopStartTime; + }; + + if( audioWrapper.startTime < 0 || audioWrapper.duration <= audioWrapper.startTime ) return 0; + return audioWrapper.startTime; +}; + +function X_AudioWrapper_getEndTime( audioWrapper ){ + var duration = audioWrapper.duration; + + if( audioWrapper.looped && 0 <= audioWrapper.loopEndTime ){ + if( duration <= audioWrapper.loopEndTime ) return duration; + return audioWrapper.loopEndTime; + }; + + if( audioWrapper.endTime < 0 || duration <= audioWrapper.endTime ) return duration; + return audioWrapper.endTime; +};