X-Git-Url: http://git.osdn.jp/view?a=blobdiff_plain;f=0.6.x%2Fjs%2F07_audio%2F00_XAudio.js;h=60bc56e5929ea90716df048815592944988f9c22;hb=HEAD;hp=33faebfd120242f08991eaf416177b79ca589bd3;hpb=a3d03e96ad8c0392ef683eb6c64421e094b96958;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 33faebf..60bc56e 100644 --- a/0.6.x/js/07_audio/00_XAudio.js +++ b/0.6.x/js/07_audio/00_XAudio.js @@ -25,29 +25,36 @@ X_TEMP.onSystemReady.push( }); /** - *

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

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

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

バックエンドの種類

- *

HTMLAudio, WebAudio, Silverlight + *

HTMLAudio, WebAudio, Silverlight, WMP *

イベント

*
- *
X.Event.BACKEND_READY
音声(src リスト)を再生可能なバックエンドが見つかった。 - *
X.Event.BACKEND_NONE
音声を再生可能なバックエンドが見つからなかった。 - *
X.Event.READY
再生可能、実際の状態は canplay から loadeddata まで様々、、、 - *
X.Event.ERROR
+ *

ソースリストに与える url 文字列

+ *

ハッシュフラグメント以下にデータを書くことで、各オーディオバックエンドが再生可能性の判断にあたって参考にするデータを渡すことができます。 + *

+ *
CBR=1
audio が固定ビットレートであることを示す。Android 用 Opera12- は可変ビットレートの mp3 を正しくシークできない。 + * [ 'snd.mp3', 'snd.mp3#CBR=1' ] と指定すると、Android 用 Opera12- では CBR な mp3 が、他の環境ではよりファイルサイズの小さい VBR な mp3 が使用される。(未実装) + *
ext=mp3
パスに拡張子が含まれない場合、または上書き指定したい場合に指定する * * @alias X.Audio * @class 各種オーディオ機能をラップしインターフェイスを共通化する。 @@ -56,12 +63,11 @@ X_TEMP.onSystemReady.push( * @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 ); + * 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, + X_Class.NONE, { /** * 音声の url。X.Event.BACKEND_READY で設定される。 @@ -69,24 +75,32 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( * @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 ); + X_ViewPort[ 'listenOnce' ]( X_EVENT_UNLOAD, this, X_Audio_handleEvent ); }, /** * 再生。開始位置・終了位置、ループの有無、ループ以降の開始位置、ループ以降の終了位置 * @alias Audio.prototype.play + * @param {number=} startTime 開始時間を ms で + * @param {number=} endTime 終了時間を ms で + * @param {boolean=} loop endTimeに達した際に曲をループさせるか + * @param {number=} loopStartTime ループ以後の開始時間を ms で + * @param {number=} loopEndTime ループ以後の終了時間を ms で + * @return {Audio} メソッドチェーン */ 'play' : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ var pair = X_Pair_get( this ); @@ -94,8 +108,10 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( return this; }, /** - * シーク + * シーク、再生中で無い場合は次回再生開始位置の指定のみ * @alias Audio.prototype.seek + * @param {number} seekTime シーク位置を ms で + * @return {Audio} メソッドチェーン */ 'seek' : function( seekTime ){ var pair = X_Pair_get( this ); @@ -105,6 +121,7 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( /** * ポーズ * @alias Audio.prototype.pause + * @return {Audio} メソッドチェーン */ 'pause' : function(){ var pair = X_Pair_get( this ); @@ -114,6 +131,21 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( /** * 状態の getter と setter * @alias Audio.prototype.state + * @param {object=} obj setter の場合、上書きする値を格納したobject + * @return {Audio|object} + * @example +audio.setState( + { + 'startTime' : 0, + 'endTime' : 80000, + 'loopStartTime' : 120000, + 'loopEndTime' : 200000, + 'currentTime' : 0, + 'loop' : true, + 'looded' : false, + 'volume' : 1, + 'autoplay' : true +}); */ 'state' : function( obj ){ var pair = X_Pair_get( this ); @@ -127,18 +159,22 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( 'currentTime' : -1, 'loop' : false, 'looded' : false, - 'error' : false, + 'error' : 0, + 'autoplay' : false, 'playing' : false, - 'source' : this[ 'source' ] || '', - 'duration' : 0 + 'source' : this[ 'source' ], + 'duration' : 0, + 'volume' : 0.5 }; }; pair && pair.setState( obj ); return this; }, /** - * ループの getter と setter + * ループの setter * @alias Audio.prototype.loop + * @param {boolean} v + * @return {Audio} */ 'loop' : function( v ){ var pair = X_Pair_get( this ); @@ -146,8 +182,10 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( return this; }, /** - * ボリュームの getter と setter 実装不十分! + * ボリュームの setter 実装不十分! * @alias Audio.prototype.volume + * @param {number} v 0~1 + * @return {Audio} */ 'volume' : function( v ){ var pair = X_Pair_get( this ); @@ -155,8 +193,10 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( return this; }, /** - * 再生位置。 + * 再生位置のsetter。 * @alias Audio.prototype.currentTime + * @param {number} v msで + * @return {Audio} */ 'currentTime' : function( v ){ var pair = X_Pair_get( this ); @@ -166,6 +206,7 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( /** * 再生中か? * @alias Audio.prototype.isPlaying + * @return {boolean} */ 'isPlaying' : function(){ var pair = X_Pair_get( this ); @@ -176,7 +217,7 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( ); function X_Audio_handleEvent( e ){ - var backend; + var backend, src, pair; switch( e.type ){ case X_EVENT_BACKEND_READY : @@ -185,17 +226,28 @@ function X_Audio_handleEvent( e ){ 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' ] ) ); + this[ 'listenOnce' ]( X_EVENT_READY, X_Audio_handleEvent ); + break; + + case X_EVENT_READY : // TODO AudioBase 側へ行かない? + pair = X_Pair_get( this ); + ( pair.autoplay || pair._playReserved ) && pair.actualPlay(); + delete pair._playReserved; break; case X_EVENT_BACKEND_NONE : + case X_EVENT_UNLOAD : this[ 'kill' ](); break; case X_EVENT_KILL_INSTANCE : - backend = X_Pair_get( this ); - backend && backend[ 'kill' ](); - X_Pair_release( this, backend ); + X_ViewPort[ 'unlisten' ]( X_EVENT_UNLOAD, this, X_Audio_handleEvent ); + if( backend = X_Pair_get( this ) ){ + backend[ 'kill' ](); + X_Pair_release( this, backend ); + }; break; }; }; @@ -206,8 +258,9 @@ function X_Audio_handleEvent( e ){ */ function X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ){ - var source = sourceList[ 0 ] || '', - ext = X_URL_getEXT( source ), + var source = sourceList[ 0 ] || '', + hash = X_URL_paramToObj( X_URL_getHash( source ) ), + ext = hash[ 'ext' ] || X_URL_getEXT( source ), sup; if( source && backend ){ @@ -215,29 +268,36 @@ function X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ){ sup[ 5 ] = sup; xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup ); - backend.detect( xaudio, source, ext ); + backend.detect( xaudio, ext, hash ); } else { xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); }; }; function X_Audio_onEndedDetection( e, xaudio, sourceList, option, source, ext, sup ){ - var i = X_Audio_BACKENDS.indexOf( this ), backend; + var i = X_Audio_BACKENDS.indexOf( this ), _e, hash, backend; if( e.canPlay ){ - xaudio[ 'asyncDispatch' ]( { + _e = { type : X_EVENT_BACKEND_READY, 'option' : option, 'source' : source, - 'backendName' : this[ 'backendName' ], + 'backendName' : this.backendName, 'backendID' : i - } ); + }; + // WebAudio + if( this.backendID === 1 ) _e[ 'needTouchForPlay' ] = /* X_WebAudio_need1stTouch && */ X_WebAudio_isNoTouch; + // HTMLAudio + if( this.backendID === 2 ) _e[ 'needTouchForLoad' ] = X_HTMLAudio_need1stTouch; + + xaudio[ 'asyncDispatch' ]( _e ); } else { - console.log( 'No ' + source + ' ' + this[ 'backendName' ] ); + console.log( 'No ' + source + ' ' + this.backendName ); if( sup[ 3 ] = source = sourceList[ sourceList.indexOf( source ) + 1 ] ){ - sup[ 4 ] = ext = X_URL_getEXT( source ); + hash = X_URL_paramToObj( X_URL_getHash( source ) ); + sup[ 4 ] = ext = hash[ 'ext' ] || X_URL_getEXT( source ); xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, this, X_Audio_onEndedDetection, sup ); - this.detect( xaudio, source, ext ); + this.detect( xaudio, ext, hash ); } else if( backend = X_Audio_BACKENDS[ i + 1 ] ){ X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ); @@ -253,11 +313,10 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( 'X.AudioBase', X_Class.ABSTRACT, { - url : '', // - target : null, // + dispatcher : null, - startTime : 0, // - endTime : -1, // + startTime : 0, // state_startTime + endTime : -1, // state_startTime loopStartTime : -1, loopEndTime : -1, seekTime : -1, @@ -270,6 +329,8 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( autoplay : false,// gain : 0.5, + _playReserved : false, + play : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ if( 0 <= startTime ){ this.setState( { @@ -293,6 +354,7 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( }, pause : function(){ + this.seekTime = this.getActualCurrentTime(); this.playing && this.actualPause(); // delete this.autoplay // delete this.playing @@ -331,6 +393,7 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( 'volume' : this.gain, 'playing' : this.playing, 'duration' : this.duration, + 'autoplay' : this.autoplay, 'currentTime' : this.playing ? this.getActualCurrentTime() : this.seekTime, 'error' : this.getActualError ? this.getActualError() : this.error @@ -345,7 +408,7 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( for( k in obj ){ v = obj[ k ]; switch( k ){ - case 'currentTime' : + case 'currentTime' : v = X_Audio_timeStringToNumber( v ); if( X_Type_isNumber( v ) ){ if( playing ){ @@ -441,7 +504,7 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( case 'useVideo' : break; default : - throw ( 'bad arg! ' + k ); + alert( 'bad arg! ' + k ); }; }; @@ -464,11 +527,12 @@ var X_AudioBase = X_EventDispatcher[ 'inherits' ]( function X_Audio_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; + ms = parseFloat( ( ary[ 1 ] + '000' ).substr( 0, 3 ) ) || 0; ary = ary[ 0 ].split( ':' ); if( 3 < ary.length ) return; @@ -477,17 +541,17 @@ function X_Audio_timeStringToNumber( time ){ case 0 : break; case 1 : - s = parseInt( ary[ 0 ] ) || 0; + s = parseFloat( ary[ 0 ] ) || 0; break; case 2 : - m = parseInt( ary[ 0 ] ) || 0; - s = parseInt( ary[ 1 ] ) || 0; + m = parseFloat( ary[ 0 ] ) || 0; + s = parseFloat( 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; + h = parseFloat( ary[ 0 ] ) || 0; + m = parseFloat( ary[ 1 ] ) || 0; + s = parseFloat( ary[ 2 ] ) || 0; if( 60 <= s ) alert( 'invalid time string ' + time ); if( 60 <= m ) alert( 'invalid time string ' + time ); break; @@ -498,34 +562,34 @@ function X_Audio_timeStringToNumber( time ){ return ms < 0 ? 0 : ms; }; -function X_Audio_getStartTime( audioWrapper, endTime, delSeekTime ){ - var seek = audioWrapper.seekTime; +function X_Audio_getStartTime( audioBase, endTime, delSeekTime ){ + var seek = audioBase.seekTime; - if( delSeekTime ) delete audioWrapper.seekTime; + if( delSeekTime ) delete audioBase.seekTime; if( 0 <= seek ){ - if( audioWrapper.duration <= seek || endTime < seek ) return 0; + if( audioBase.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( audioBase.looped && 0 <= audioBase.loopStartTime ){ + if( audioBase.duration <= audioBase.loopStartTime || endTime < audioBase.loopStartTime ) return 0; + return audioBase.loopStartTime; }; - if( audioWrapper.startTime < 0 || audioWrapper.duration <= audioWrapper.startTime ) return 0; - return audioWrapper.startTime; + if( audioBase.startTime < 0 || audioBase.duration <= audioBase.startTime ) return 0; + return audioBase.startTime; }; -function X_Audio_getEndTime( audioWrapper ){ - var duration = audioWrapper.duration; +function X_Audio_getEndTime( audioBase ){ + var duration = audioBase.duration; - if( audioWrapper.looped && 0 <= audioWrapper.loopEndTime ){ - if( duration <= audioWrapper.loopEndTime ) return duration; - return audioWrapper.loopEndTime; + if( audioBase.looped && 0 <= audioBase.loopEndTime ){ + if( duration <= audioBase.loopEndTime ) return duration; + return audioBase.loopEndTime; }; - if( audioWrapper.endTime < 0 || duration <= audioWrapper.endTime ) return duration; - return audioWrapper.endTime; + if( audioBase.endTime < 0 || duration <= audioBase.endTime ) return duration; + return audioBase.endTime; };