13 var X_Audio_BACKENDS = [], // Array.<Hash>
\r
14 X_Audio_WRAPPER_LIST = []; // Array.<AudioWrapper>
\r
16 function X_Audio_getAudioWrapper( proxy ){
\r
17 var i = X_Audio_WRAPPER_LIST.length;
\r
19 if( X_Audio_WRAPPER_LIST[ --i ].proxy === proxy ) return X_Audio_WRAPPER_LIST[ i ];
\r
24 * X_EVENT_BACKEND_READY
\r
25 * X_EVENT_BACKEND_NONE
\r
27 * X_EVENT_READY 再生可能、実際の状態は canplay から loadeddata まで様々、、、
\r
29 * 1 : ユーザーによってメディアの取得が中断された
\r
32 * 4 : メディアがサポートされていない
\r
34 * X_EVENT_MEDIA_PLAYING 再生中に1秒以下のタイミングで発生.currentTime が取れる?
\r
35 * X_EVENT_MEDIA_LOOP ループ直前に発生、キャンセル可能
\r
36 * X_EVENT_MEDIA_LOOPED ループ時に発生
\r
37 * X_EVENT_MEDIA_ENDED 再生位置の(音声の)最後についた
\r
38 * X_EVENT_MEDIA_PAUSED ポーズした
\r
39 * X_EVENT_MEDIA_WAITING 再生中に音声が待機状態に。間もなく X_EVENT_MEDIA_PLAYING に移行。
\r
40 * X_EVENT_MEDIA_SEEKING シーク中に音声が待機状態に。間もなく X_EVENT_MEDIA_PLAYING に移行。
\r
43 X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ](
\r
45 X_Class.POOL_OBJECT,
\r
51 'Constructor' : function( sourceList, opt_option ){
\r
52 X_Audio_startDetectionBackend(
\r
53 X_Audio_BACKENDS[ 0 ], this,
\r
54 X_Type_isArray( sourceList ) ? X_Object_cloneArray( sourceList ) : [ sourceList ],
\r
56 this[ 'listenOnce' ]( [ X_EVENT_BACKEND_READY, X_EVENT_BACKEND_NONE, X_EVENT_KILL_INSTANCE ], X_Audio_handleEvent );
\r
59 'play' : function( startTime, endTime, loop, loopStartTime, loopEndTime ){
\r
60 var state, duration;
\r
61 if( 0 <= startTime ){
\r
63 currentTime : startTime,
\r
64 startTime : startTime,
\r
67 loopStartTime : loopStartTime,
\r
68 loopEndTime : loopEndTime
\r
71 this._backend !== -1 && X_Audio_getAudioWrapper( this ).play();
\r
75 'seek' : function( seekTime ){
\r
76 var state = this[ 'state' ](),
\r
77 end = X_AudioWrapper_getEndTime( X_Audio_getAudioWrapper( this ) );
\r
78 if( seekTime < end ){
\r
79 this[ 'state' ]( { currentTime : seekTime } );
\r
84 'pause' : function(){
\r
85 this[ 'state' ]().playing && X_Audio_getAudioWrapper( this ).pause();
\r
89 'state' : function( obj ){
\r
90 var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );
\r
92 if( obj === undefined ){
\r
106 source : this[ 'source' ] || '',
\r
110 backend && backend.state( obj );
\r
114 'loop' : function( v ){
\r
115 var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );
\r
116 if( v === undefined ){
\r
117 return backend && backend.state().loop;
\r
119 backend && backend.state( { loop : v } );
\r
123 'volume' : function( v ){
\r
124 var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );
\r
125 if( v === undefined ){
\r
126 return backend && backend.state().volume;
\r
128 backend && backend.state( { volume : v } );
\r
132 'currentTime' : function( v ){
\r
133 var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );
\r
134 if( v === undefined ){
\r
135 return backend && backend.state().currentTime;
\r
137 backend && backend.state( { currentTime : v } );
\r
141 'isPlaying' : function(){
\r
142 return this._backend !== -1 && X_Audio_getAudioWrapper( this ).state().playing;
\r
148 function X_Audio_handleEvent( e ){
\r
150 case X_EVENT_BACKEND_READY :
\r
151 this[ 'unlisten' ]( X_EVENT_BACKEND_NONE, X_Audio_handleEvent );
\r
152 this[ 'source' ] = e.source;
\r
153 this[ 'backendName' ] = X_Audio_BACKENDS[ this._backend ].backendName;
\r
154 X_Audio_WRAPPER_LIST.push(
\r
155 new X_Audio_BACKENDS[ this._backend ]
\r
156 .klass( this, e.source, e.option ) );
\r
159 case X_EVENT_BACKEND_NONE :
\r
163 case X_EVENT_KILL_INSTANCE :
\r
164 this._backend !== -1 && X_Audio_getAudioWrapper( this ).close();
\r
171 * TODO preplayerror play してみたら error が出た、backend の変更。
\r
174 function X_Audio_startDetectionBackend( backend, proxy, sourceList, option ){
\r
175 var source = sourceList[ 0 ] || '',
\r
176 ext = X_URL_getEXT( source ),
\r
179 if( source && backend ){
\r
180 sup = [ proxy, sourceList, option, source, ext ];
\r
183 proxy[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup );
\r
184 backend.detect( proxy, source, ext );
\r
186 proxy[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE );
\r
190 function X_Audio_onEndedDetection( e, proxy, sourceList, option, source, ext, sup ){
\r
191 var i = X_Audio_BACKENDS.indexOf( this ), backend;
\r
194 proxy._backend = i;
\r
195 proxy[ 'asyncDispatch' ]( {
\r
196 type : X_EVENT_BACKEND_READY,
\r
199 'backendName' : this[ 'backendName' ]
\r
202 console.log( 'No ' + source + ' ' + this[ 'backendName' ] );
\r
203 if( sup[ 3 ] = source = sourceList[ sourceList.indexOf( source ) + 1 ] ){
\r
204 sup[ 4 ] = ext = X_URL_getEXT( source );
\r
205 proxy[ 'listenOnce' ]( X_EVENT_COMPLETE, this, X_Audio_onEndedDetection, sup );
\r
206 this.detect( proxy, source, ext );
\r
208 if( backend = X_Audio_BACKENDS[ i + 1 ] ){
\r
209 X_Audio_startDetectionBackend( backend, proxy, sourceList, option );
\r
211 proxy[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE );
\r
218 function X_AudioWrapper_updateStates( audioWrapper, obj ){
\r
219 var playing = audioWrapper.playing,
\r
221 end = 0, seek = 0, volume = 0;
\r
226 case 'currentTime' :
\r
227 v = X_AudioWrapper_timeStringToNumber( v );
\r
228 if( X_Type_isNumber( v ) ){
\r
230 if( audioWrapper.state().currentTime !== v ){
\r
231 audioWrapper.seekTime = v;
\r
235 audioWrapper.seekTime = v;
\r
244 case 'loopStartTime' :
\r
245 case 'loopEndTime' :
\r
246 v = X_AudioWrapper_timeStringToNumber( v );
\r
247 console.log( k + ' ' + v );
\r
248 if( v || v === 0 ){
\r
249 if( audioWrapper[ k ] !== v ){
\r
250 audioWrapper[ k ] = v;
\r
252 // 再生中の endTime の変更
\r
253 if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1;
\r
256 delete audioWrapper[ k ];
\r
257 if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1;
\r
262 if( playing ) seek = 2;
\r
265 if( X_Type_isBoolean( v ) && audioWrapper[ k ] !== v ){
\r
266 audioWrapper[ k ] = v;
\r
271 if( X_Type_isNumber( v ) ){
\r
272 v = v < 0 ? 0 : 1 < v ? 1 : v;
\r
273 if( audioWrapper[ k ] !== v ){
\r
274 audioWrapper[ k ] = v;
\r
275 // if playing -> update
\r
276 if( playing ) volume = 4;
\r
283 if( audioWrapper.endTime < audioWrapper.startTime ||
\r
284 ( audioWrapper.loopEndTime < 0 ? audioWrapper.endTime : audioWrapper.loopEndTime ) < ( audioWrapper.loopStartTime < 0 ? audioWrapper.startTime : audioWrapper.loopStartTime ) ||
\r
285 X_AudioWrapper_getEndTime( audioWrapper ) < audioWrapper.seekTime// ||
\r
286 //audioWrapper.duration < audioWrapper.endTime
\r
288 console.log( 'error @updateStateObject() begin:' + audioWrapper.startTime + ' end:' + audioWrapper.endTime + ' d:' + audioWrapper.duration + ' ls:' + audioWrapper.loopStartTime );
\r
292 return end + seek + volume;
\r
295 function X_AudioWrapper_timeStringToNumber( time ){
\r
296 var ary, ms, s = 0, m = 0, h = 0;
\r
297 if( X_Type_isNumber( time ) ) return time;
\r
298 if( !X_Type_isString( time ) || !time.length ) return;
\r
300 ary = time.split( '.' );
\r
301 ms = parseInt( ( ary[ 1 ] + '000' ).substr( 0, 3 ) ) || 0;
\r
303 ary = ary[ 0 ].split( ':' );
\r
304 if( 3 < ary.length ) return;
\r
306 switch( ary.length ){
\r
310 s = parseInt( ary[ 0 ] ) || 0;
\r
313 m = parseInt( ary[ 0 ] ) || 0;
\r
314 s = parseInt( ary[ 1 ] ) || 0;
\r
315 if( 60 <= s ) alert( 'invalid time string ' + time );
\r
318 h = parseInt( ary[ 0 ] ) || 0;
\r
319 m = parseInt( ary[ 1 ] ) || 0;
\r
320 s = parseInt( ary[ 2 ] ) || 0;
\r
321 if( 60 <= s ) alert( 'invalid time string ' + time );
\r
322 if( 60 <= m ) alert( 'invalid time string ' + time );
\r
325 alert( 'invalid time string ' + time );
\r
327 ms = ( h * 3600 + m * 60 + s ) * 1000 + ms;
\r
328 return ms < 0 ? 0 : ms;
\r
331 function X_AudioWrapper_getStartTime( audioWrapper, endTime, delSeekTime ){
\r
332 var seek = audioWrapper.seekTime;
\r
333 if( delSeekTime ) delete audioWrapper.seekTime;
\r
336 if( audioWrapper.duration <= seek || endTime < seek ) return 0;
\r
340 if( audioWrapper.looped && 0 <= audioWrapper.loopStartTime ){
\r
341 if( audioWrapper.duration <= audioWrapper.loopStartTime || endTime < audioWrapper.loopStartTime ) return 0;
\r
342 return audioWrapper.loopStartTime;
\r
345 if( audioWrapper.startTime < 0 || audioWrapper.duration <= audioWrapper.startTime ) return 0;
\r
346 return audioWrapper.startTime;
\r
349 function X_AudioWrapper_getEndTime( audioWrapper ){
\r
350 var duration = audioWrapper.duration;
\r
352 if( audioWrapper.looped && 0 <= audioWrapper.loopEndTime ){
\r
353 if( duration <= audioWrapper.loopEndTime ) return duration;
\r
354 return audioWrapper.loopEndTime;
\r
357 if( audioWrapper.endTime < 0 || duration <= audioWrapper.endTime ) return duration;
\r
358 return audioWrapper.endTime;
\r