13 var X_Audio_BACKENDS = []; // Array.<Hash>
\r
16 * X_EVENT_BACKEND_READY
\r
17 * X_EVENT_BACKEND_NONE
\r
19 * X_EVENT_READY 再生可能、実際の状態は canplay から loadeddata まで様々、、、
\r
21 * 1 : ユーザーによってメディアの取得が中断された
\r
24 * 4 : メディアがサポートされていない
\r
26 * X_EVENT_MEDIA_PLAYING 再生中に1秒以下のタイミングで発生.currentTime が取れる?
\r
27 * X_EVENT_MEDIA_LOOP ループ直前に発生、キャンセル可能
\r
28 * X_EVENT_MEDIA_LOOPED ループ時に発生
\r
29 * X_EVENT_MEDIA_ENDED 再生位置の(音声の)最後についた
\r
30 * X_EVENT_MEDIA_PAUSED ポーズした
\r
31 * X_EVENT_MEDIA_WAITING 再生中に音声が待機状態に。間もなく X_EVENT_MEDIA_PLAYING に移行。
\r
32 * X_EVENT_MEDIA_SEEKING シーク中に音声が待機状態に。間もなく X_EVENT_MEDIA_PLAYING に移行。
\r
35 // TODO この内容は、AudioBackend の Abstract クラスにする。AudioSprite は Audio ではなく AudioBackend をマネージする
\r
36 X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ](
\r
38 X_Class.POOL_OBJECT,
\r
43 'Constructor' : function( sourceList, opt_option ){
\r
44 X_Audio_startDetectionBackend(
\r
45 X_Audio_BACKENDS[ 0 ], this,
\r
46 X_Type_isArray( sourceList ) ? X_Object_cloneArray( sourceList ) : [ sourceList ],
\r
48 this[ 'listenOnce' ]( [ X_EVENT_BACKEND_READY, X_EVENT_BACKEND_NONE, X_EVENT_KILL_INSTANCE ], X_Audio_handleEvent );
\r
51 'play' : function( startTime, endTime, loop, loopStartTime, loopEndTime ){
\r
52 var pair = X_Pair_get( this );
\r
53 pair && pair.play( startTime, endTime, loop, loopStartTime, loopEndTime );
\r
57 'seek' : function( seekTime ){
\r
58 var pair = X_Pair_get( this );
\r
59 pair && pair.seek( seekTime );
\r
63 'pause' : function(){
\r
64 var pair = X_Pair_get( this );
\r
65 pair && pair.pause();
\r
69 'state' : function( obj ){
\r
70 var pair = X_Pair_get( this );
\r
71 if( obj === undefined ){
\r
72 return pair ? pair.getState() :
\r
76 'loopStartTime' : -1,
\r
83 'source' : this[ 'source' ] || '',
\r
87 pair && pair.setState( obj );
\r
91 'loop' : function( v ){
\r
92 var pair = X_Pair_get( this );
\r
93 pair && pair.loop( v );
\r
97 'volume' : function( v ){
\r
98 var pair = X_Pair_get( this );
\r
99 pair && pair.volume( v );
\r
103 'currentTime' : function( v ){
\r
104 var pair = X_Pair_get( this );
\r
105 pair && pair.currentTime( v );
\r
109 'isPlaying' : function(){
\r
110 var pair = X_Pair_get( this );
\r
111 return pair && pair.playing;
\r
117 function X_Audio_handleEvent( e ){
\r
121 case X_EVENT_BACKEND_READY :
\r
122 backend = X_Audio_BACKENDS[ e[ 'backendID' ] ];
\r
124 this[ 'unlisten' ]( X_EVENT_BACKEND_NONE, X_Audio_handleEvent );
\r
125 this[ 'source' ] = e[ 'source' ];
\r
126 this[ 'backendName' ] = backend.backendName;
\r
127 X_Pair_create( this, backend.klass( this, e[ 'source' ], e[ 'option' ] ) );
\r
130 case X_EVENT_BACKEND_NONE :
\r
134 case X_EVENT_KILL_INSTANCE :
\r
135 backend = X_Pair_get( this );
\r
136 backend && backend[ 'kill' ]();
\r
137 X_Pair_release( this, backend );
\r
144 * TODO preplayerror play してみたら error が出た、backend の変更。
\r
147 function X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ){
\r
148 var source = sourceList[ 0 ] || '',
\r
149 ext = X_URL_getEXT( source ),
\r
152 if( source && backend ){
\r
153 sup = [ xaudio, sourceList, option, source, ext ];
\r
156 xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup );
\r
157 backend.detect( xaudio, source, ext );
\r
159 xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE );
\r
163 function X_Audio_onEndedDetection( e, xaudio, sourceList, option, source, ext, sup ){
\r
164 var i = X_Audio_BACKENDS.indexOf( this ), backend;
\r
167 xaudio._backend = i;
\r
168 xaudio[ 'asyncDispatch' ]( {
\r
169 type : X_EVENT_BACKEND_READY,
\r
172 'backendName' : this[ 'backendName' ],
\r
176 console.log( 'No ' + source + ' ' + this[ 'backendName' ] );
\r
177 if( sup[ 3 ] = source = sourceList[ sourceList.indexOf( source ) + 1 ] ){
\r
178 sup[ 4 ] = ext = X_URL_getEXT( source );
\r
179 xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, this, X_Audio_onEndedDetection, sup );
\r
180 this.detect( xaudio, source, ext );
\r
182 if( backend = X_Audio_BACKENDS[ i + 1 ] ){
\r
183 X_Audio_startDetectionBackend( backend, xaudio, sourceList, option );
\r
185 xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE );
\r
192 var X_Audio_AbstractAudioBackend = X_EventDispatcher[ 'inherits' ](
\r
193 'X.AbstractAudioBackend',
\r
202 loopStartTime : -1,
\r
214 play : function( startTime, endTime, loop, loopStartTime, loopEndTime ){
\r
215 if( 0 <= startTime ){
\r
217 currentTime : startTime,
\r
218 startTime : startTime,
\r
221 loopStartTime : loopStartTime,
\r
222 loopEndTime : loopEndTime
\r
228 seek : function( seekTime ){
\r
229 if( seekTime < X_AudioWrapper_getEndTime( this ) ){
\r
230 this.setState( { currentTime : seekTime } );
\r
234 pause : function(){
\r
235 this.playing && this.actualPause();
\r
238 loop : function( v ){
\r
239 if( v === undefined ){
\r
240 return this.autoLoop;
\r
242 this.setState( { loop : v } );
\r
245 volume : function( v ){
\r
246 if( v === undefined ){
\r
249 this.setState( { volume : v } );
\r
252 currentTime : function( v ){
\r
253 if( v === undefined ){
\r
254 return this.playing ? this.getActualCurrentTime() : this.seekTime;
\r
256 this.setState( { currentTime : v } );
\r
259 getState : function(){
\r
262 'startTime' : this.startTime,
\r
263 'endTime' : this.endTime < 0 ? this.duration : this.endTime,
\r
264 'loopStartTime' : this.loopStartTime < 0 ? this.startTime : this.loopStartTime,
\r
265 'loopEndTime' : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime,
\r
266 'loop' : this.autoLoop,
\r
267 'looped' : this.looped,
\r
268 'volume' : this.gain,
\r
269 'playing' : this.playing,
\r
270 'duration' : this.duration,
\r
272 'currentTime' : this.playing ? this.getActualCurrentTime() : this.seekTime,
\r
273 'error' : this.getActualError ? this.getActualError() : this.error
\r
277 setState : function( obj ){
\r
278 var playing = this.playing,
\r
280 end = 0, seek = 0, volume = 0;
\r
285 case 'currentTime' :
\r
286 v = X_AudioWrapper_timeStringToNumber( v );
\r
287 if( X_Type_isNumber( v ) ){
\r
289 if( this.getActualCurrentTime() !== v ){
\r
302 v = X_AudioWrapper_timeStringToNumber( v );
\r
303 if( v || v === 0 ){
\r
304 if( this.startTime !== v ){
\r
305 this.startTime = v;
\r
308 delete this.startTime;
\r
313 v = X_AudioWrapper_timeStringToNumber( v );
\r
314 if( v || v === 0 ){
\r
315 if( this.endTime !== v ){
\r
317 if( playing ) end = 1;
\r
320 delete this.endTime;
\r
321 if( playing ) end = 1;
\r
325 case 'loopStartTime' :
\r
326 v = X_AudioWrapper_timeStringToNumber( v );
\r
327 if( v || v === 0 ){
\r
328 if( this.loopStartTime !== v ){
\r
329 this.loopStartTime = v;
\r
332 delete this.loopStartTime;
\r
336 case 'loopEndTime' :
\r
337 v = X_AudioWrapper_timeStringToNumber( v );
\r
338 if( v || v === 0 ){
\r
339 if( this.loopEndTime !== v ){
\r
340 this.loopEndTime = v;
\r
341 if( playing ) end = 1;
\r
344 delete this.loopEndTime;
\r
345 if( playing ) end = 1;
\r
350 if( X_Type_isBoolean( v ) && this.looped !== v ){
\r
352 if( playing ) seek = 2;
\r
357 if( X_Type_isBoolean( v ) && this.autoLoop !== v ){
\r
363 if( X_Type_isBoolean( v ) && this.autoplay !== v ){
\r
369 if( X_Type_isNumber( v ) ){
\r
370 v = v < 0 ? 0 : 1 < v ? 1 : v;
\r
371 if( this.gain !== v ){
\r
373 // if playing -> update
\r
374 if( playing ) volume = 4;
\r
381 if( this.endTime < this.startTime ||
\r
382 ( this.loopEndTime < 0 ? this.endTime : this.loopEndTime ) < ( this.loopStartTime < 0 ? this.startTime : this.loopStartTime ) ||
\r
383 X_AudioWrapper_getEndTime( this ) < this.seekTime// ||
\r
384 //this.duration < this.endTime
\r
386 console.log( 'setState 0:' + this.startTime + ' -> ' + this.endTime + ' d:' + this.duration + ' 1:' + this.loopStartTime + ' -> ' + this.loopEndTime );
\r
390 v = end + seek + volume;
\r
391 return v && this.afterUpdateState( v );
\r
398 function X_AudioWrapper_timeStringToNumber( time ){
\r
399 var ary, ms, s = 0, m = 0, h = 0;
\r
400 if( X_Type_isNumber( time ) ) return time;
\r
401 if( !X_Type_isString( time ) || !time.length ) return;
\r
403 ary = time.split( '.' );
\r
404 ms = parseInt( ( ary[ 1 ] + '000' ).substr( 0, 3 ) ) || 0;
\r
406 ary = ary[ 0 ].split( ':' );
\r
407 if( 3 < ary.length ) return;
\r
409 switch( ary.length ){
\r
413 s = parseInt( ary[ 0 ] ) || 0;
\r
416 m = parseInt( ary[ 0 ] ) || 0;
\r
417 s = parseInt( ary[ 1 ] ) || 0;
\r
418 if( 60 <= s ) alert( 'invalid time string ' + time );
\r
421 h = parseInt( ary[ 0 ] ) || 0;
\r
422 m = parseInt( ary[ 1 ] ) || 0;
\r
423 s = parseInt( ary[ 2 ] ) || 0;
\r
424 if( 60 <= s ) alert( 'invalid time string ' + time );
\r
425 if( 60 <= m ) alert( 'invalid time string ' + time );
\r
428 alert( 'invalid time string ' + time );
\r
430 ms = ( h * 3600 + m * 60 + s ) * 1000 + ms;
\r
431 return ms < 0 ? 0 : ms;
\r
434 function X_AudioWrapper_getStartTime( audioWrapper, endTime, delSeekTime ){
\r
435 var seek = audioWrapper.seekTime;
\r
436 if( delSeekTime ) delete audioWrapper.seekTime;
\r
439 if( audioWrapper.duration <= seek || endTime < seek ) return 0;
\r
443 if( audioWrapper.looped && 0 <= audioWrapper.loopStartTime ){
\r
444 if( audioWrapper.duration <= audioWrapper.loopStartTime || endTime < audioWrapper.loopStartTime ) return 0;
\r
445 return audioWrapper.loopStartTime;
\r
448 if( audioWrapper.startTime < 0 || audioWrapper.duration <= audioWrapper.startTime ) return 0;
\r
449 return audioWrapper.startTime;
\r
452 function X_AudioWrapper_getEndTime( audioWrapper ){
\r
453 var duration = audioWrapper.duration;
\r
455 if( audioWrapper.looped && 0 <= audioWrapper.loopEndTime ){
\r
456 if( duration <= audioWrapper.loopEndTime ) return duration;
\r
457 return audioWrapper.loopEndTime;
\r
460 if( audioWrapper.endTime < 0 || duration <= audioWrapper.endTime ) return duration;
\r
461 return audioWrapper.endTime;
\r