OSDN Git Service

d33dee990210dde6b110edf9bf40c36c01ef8a53
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 00_XAudio.js
1 \r
2 /*\r
3         WebAudio    : 1,\r
4         HTML5       : 2,\r
5         Flash       : 3,\r
6         Silverlight : 4,\r
7         Unity       : 5,\r
8         WMP         : 6,\r
9         RealPlayer  : 7,\r
10         QuickTime   : 8,\r
11  */\r
12 \r
13 var X_Audio_BACKENDS     = [], // Array.<Hash>\r
14         X_Audio_WRAPPER_LIST = [], // Array.<AudioWrapper>\r
15         X_Audio_CAN_PLAY     = 1,\r
16         X_Audio_NOT_PLAY     = 2;\r
17 \r
18 function X_Audio_getAudioWrapper( proxy ){\r
19         var i = X_Audio_WRAPPER_LIST.length;\r
20         for( ; i; ){\r
21                 if( X_Audio_WRAPPER_LIST[ --i ].proxy === proxy ) return X_Audio_WRAPPER_LIST[ i ];\r
22         };\r
23 };\r
24 \r
25 /*\r
26  * X.Event.BACKEND_READY\r
27  * X.Event.BACKEND_NONE\r
28  * \r
29  * X.Event.READY   再生可能、実際の状態は canplay から loadeddata まで様々、、、\r
30  * X.Event.ERROR\r
31  *   1 : ユーザーによってメディアの取得が中断された\r
32  *   2 : ネットワークエラー\r
33  *   3 : メディアのデコードエラー\r
34  *   4 : メディアがサポートされていない\r
35  * \r
36  * X.Event.MEDIA_PLAYING 再生中に1秒以下のタイミングで発生.currentTime が取れる?\r
37  * X.Event.MEDIA_LOOP    ループ直前に発生、キャンセル可能\r
38  * X.Event.MEDIA_LOOPED  ループ時に発生\r
39  * X.Event.MEDIA_ENDED   再生位置の(音声の)最後についた\r
40  * X.Event.MEDIA_PAUSED  ポーズした\r
41  * X.Event.MEDIA_WAITING 再生中に音声が待機状態に。間もなく X.Event.MEDIA_PLAYING に移行。\r
42  * X.Event.MEDIA_SEEKING シーク中に音声が待機状態に。間もなく X.Event.MEDIA_PLAYING に移行。\r
43  */\r
44 \r
45 X.Audio = X.EventDispatcher.inherits(\r
46         'X.Audio',\r
47         X.Class.POOL_OBJECT,\r
48         {\r
49                 source      : '',\r
50                 backendName : '',\r
51                 _backend    : -1,\r
52                 \r
53                 Constructor : function( sourceList, opt_option ){\r
54                         X_Audio_startDetectionBackend(\r
55                                 X_Audio_BACKENDS[ 0 ], this,\r
56                                 X.Type.isArray( sourceList ) ? X_Object_cloneArray( sourceList ) : [ sourceList ],\r
57                                 opt_option || {} );\r
58                         this.listenOnce( [ X.Event.BACKEND_READY, X.Event.BACKEND_NONE, X.Event.KILL_INSTANCE ], X_Audio_handleEvent );\r
59                 },\r
60                 \r
61                 play : function( startTime, endTime, loop, loopStartTime, loopEndTime ){\r
62                         var state, duration;\r
63                         if( 0 <= startTime ){\r
64                                 this.state( {\r
65                                         currentTime   : startTime,\r
66                                         startTime     : startTime,\r
67                                         endTime       : endTime,\r
68                                         loop          : loop,\r
69                                         loopStartTime : loopStartTime,\r
70                                         loopEndTime   : loopEndTime\r
71                                 } );\r
72                         };\r
73                         this._backend !== -1 && X_Audio_getAudioWrapper( this ).play();\r
74                         return this;\r
75                 },\r
76                 \r
77                 seek : function( seekTime ){\r
78                         var state = this.state(),\r
79                                 end   = X_AudioWrapper_getEndTime( X_Audio_getAudioWrapper( this ) );\r
80                         if( seekTime < end ){\r
81                                 this.state( { currentTime : seekTime } );\r
82                         };\r
83                         return this;\r
84                 },\r
85                 \r
86                 pause : function(){\r
87                         this.state().playing && X_Audio_getAudioWrapper( this ).pause();\r
88                         return this;\r
89                 },\r
90                 \r
91                 state : function( obj ){\r
92                         var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );\r
93 \r
94                         if( obj === undefined ){\r
95                                 return backend ?\r
96                                         backend.state() :\r
97                                         {\r
98                                         startTime     : -1,\r
99                                         endTime       : -1,\r
100                                         loopStartTime : -1,\r
101                                         loopEndTime   : -1,\r
102                                         currentTime   : -1,\r
103                                         loop          : false,\r
104                                         looded        : false,\r
105                                         error         : false,\r
106                                         playing       : false,\r
107                                         \r
108                                         source        : this.source || '',\r
109                                         duration      : 0\r
110                                         };\r
111                         };\r
112                         backend && backend.state( obj );\r
113                         return this;\r
114                 },              \r
115                 \r
116                 loop : function( v ){\r
117                         var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );\r
118                         if( v === undefined ){\r
119                                 return backend && backend.state().loop;\r
120                         };\r
121                         backend && backend.state( { loop : v } );\r
122                         return this;\r
123                 },\r
124 \r
125                 volume : function( v ){\r
126                         var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );\r
127                         if( v === undefined ){\r
128                                 return backend && backend.state().volume;\r
129                         };\r
130                         backend && backend.state( { volume : v } );\r
131                         return this;\r
132                 },\r
133 \r
134                 currentTime : function( v ){\r
135                         var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this );\r
136                         if( v === undefined ){\r
137                                 return backend && backend.state().currentTime;\r
138                         };\r
139                         backend && backend.state( { currentTime : v } );\r
140                         return this;\r
141                 },\r
142 \r
143                 isPlaying : function(){\r
144                         return this._backend !== -1 && X_Audio_getAudioWrapper( this ).state().playing;\r
145                 }\r
146                 \r
147         }\r
148 );\r
149 \r
150 function X_Audio_handleEvent( e ){\r
151         switch( e.type ){\r
152                 case X.Event.BACKEND_READY :\r
153                         this.unlisten( X.Event.BACKEND_NONE, X_Audio_handleEvent );\r
154                         this.source = e.source;\r
155                         this.backendName = X_Audio_BACKENDS[ this._backend ].backendName;\r
156                         X_Audio_WRAPPER_LIST.push(\r
157                                 new X_Audio_BACKENDS[ this._backend ]\r
158                                 .klass( this, e.source, e.option ) );\r
159                         break;\r
160                 \r
161                 case X.Event.BACKEND_NONE :\r
162                         this.kill();\r
163                         break;\r
164                 \r
165                 case X.Event.KILL_INSTANCE :\r
166                         this._backend !== -1 && X_Audio_getAudioWrapper( this ).close();\r
167                         break;\r
168         };\r
169 };\r
170 \r
171 \r
172 /*\r
173  * TODO preplayerror play してみたら error が出た、backend の変更。\r
174  */\r
175 \r
176 function X_Audio_startDetectionBackend( backend, proxy, sourceList, option ){\r
177         var source = sourceList[ 0 ] || '', \r
178                 ext    = X_URL_getEXT( source ),\r
179                 sup;\r
180         \r
181         if( source && backend ){\r
182                 sup      = [ proxy, sourceList, option, source, ext ];\r
183                 sup[ 5 ] = sup;\r
184                 \r
185                 proxy.listenOnce( [ X_Audio_CAN_PLAY, X_Audio_NOT_PLAY ], backend, X_Audio_onEndedDetection, sup );\r
186                 backend.detect( proxy, source, ext );\r
187         } else {\r
188                 proxy.asyncDispatch( X.Event.BACKEND_NONE );\r
189         };\r
190 };\r
191 \r
192 function X_Audio_onEndedDetection( e, proxy, sourceList, option, source, ext, sup ){\r
193         var i = X_Audio_BACKENDS.indexOf( this ), backend;\r
194         \r
195         proxy.unlisten( [ X_Audio_CAN_PLAY, X_Audio_NOT_PLAY ], this, X_Audio_onEndedDetection, sup );\r
196         \r
197         switch( e.type ){\r
198                 case X_Audio_CAN_PLAY :\r
199                         proxy._backend = i;\r
200                         proxy.asyncDispatch( {\r
201                                 type        : X.Event.BACKEND_READY,\r
202                                 option      : option,\r
203                                 source      : source,\r
204                                 backendName : this.backendName\r
205                         } );\r
206                         break;\r
207                 case X_Audio_NOT_PLAY :\r
208                         console.log( 'No ' + source + ' ' + this.backendName );\r
209                         if( sup[ 3 ] = source = sourceList[ sourceList.indexOf( source ) + 1 ] ){\r
210                                 sup[ 4 ] = ext    = X_URL_getEXT( source );\r
211                                 proxy.listenOnce( [ X_Audio_CAN_PLAY, X_Audio_NOT_PLAY ], this, X_Audio_onEndedDetection, sup );\r
212                                 this.detect( proxy, source, ext );\r
213                         } else\r
214                         if( backend = X_Audio_BACKENDS[ i + 1 ] ){\r
215                                 X_Audio_startDetectionBackend( backend, proxy, sourceList, option );\r
216                         } else {\r
217                                 proxy.asyncDispatch( X.Event.BACKEND_NONE );\r
218                         };\r
219                         break;\r
220         };\r
221 };\r
222 \r
223 \r
224 \r
225 function X_AudioWrapper_updateStates( audioWrapper, obj ){\r
226         var playing = audioWrapper.playing,\r
227                 k, v,\r
228                 end = 0, seek = 0, volume = 0;\r
229         \r
230         for( k in obj ){\r
231                 v = obj[ k ];\r
232                 switch( k ){\r
233                         case 'currentTime' :\r
234                                 v = X_AudioWrapper_timeStringToNumber( v );\r
235                                 if( X.Type.isNumber( v ) ){\r
236                                         if( playing ){\r
237                                                 if( audioWrapper.state().currentTime !== v ){\r
238                                                         audioWrapper.seekTime = v;\r
239                                                         seek = 2;\r
240                                                 };\r
241                                         } else {\r
242                                                 audioWrapper.seekTime = v;\r
243                                         };\r
244                                 } else {\r
245                                         continue;\r
246                                 };\r
247                                 break;\r
248                                         \r
249                         case 'startTime'     :\r
250                         case 'endTime'       :\r
251                         case 'loopStartTime' :\r
252                         case 'loopEndTime'   :\r
253                                 v = X_AudioWrapper_timeStringToNumber( v );\r
254                                 console.log( k + ' ' + v );\r
255                                 if( v || v === 0 ){\r
256                                         if( audioWrapper[ k ] !== v ){\r
257                                                 audioWrapper[ k ] = v;\r
258                                                 \r
259                                                 // 再生中の endTime の変更\r
260                                                 if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1;                                            \r
261                                         };\r
262                                 } else {\r
263                                         delete audioWrapper[ k ];\r
264                                         if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1;\r
265                                 };\r
266                                 break;\r
267 \r
268                         case 'looped' :\r
269                                 if( playing ) seek = 2;\r
270                         case 'loop' :\r
271                         case 'autoplay' :\r
272                                 if( X.Type.isBoolean( v ) && audioWrapper[ k ] !== v ){\r
273                                         audioWrapper[ k ] = v;\r
274                                 };\r
275                                 break;\r
276 \r
277                         case 'volume' :\r
278                                 if( X.Type.isNumber( v ) ){\r
279                                         v = v < 0 ? 0 : 1 < v ? 1 : v;\r
280                                         if( audioWrapper[ k ] !== v ){\r
281                                                 audioWrapper[ k ] = v;\r
282                                                 // if playing -> update\r
283                                                 if( playing ) volume = 4;\r
284                                         };\r
285                                 };\r
286                                 break;\r
287                 };\r
288         };\r
289         \r
290         if( audioWrapper.endTime < audioWrapper.startTime ||\r
291                 ( audioWrapper.loopEndTime < 0 ? audioWrapper.endTime : audioWrapper.loopEndTime ) < ( audioWrapper.loopStartTime < 0 ? audioWrapper.startTime : audioWrapper.loopStartTime ) ||\r
292                 X_AudioWrapper_getEndTime( audioWrapper ) < audioWrapper.seekTime// ||\r
293                 //audioWrapper.duration < audioWrapper.endTime\r
294         ){\r
295                 console.log( 'error @updateStateObject() begin:' + audioWrapper.startTime + ' end:' + audioWrapper.endTime + ' d:' + audioWrapper.duration + ' ls:' + audioWrapper.loopStartTime );\r
296                 return 0;\r
297         };\r
298         \r
299         return end + seek + volume;\r
300 };\r
301 \r
302 function X_AudioWrapper_timeStringToNumber( time ){\r
303         var ary, ms, s = 0, m = 0, h = 0;\r
304         if( X.Type.isNumber( time ) ) return time;\r
305         if( !X.Type.isString( time ) || !time.length ) return;\r
306 \r
307         ary = time.split( '.' );\r
308         ms  = parseInt( ( ary[ 1 ] + '000' ).substr( 0, 3 ) ) || 0;\r
309         \r
310         ary = ary[ 0 ].split( ':' );\r
311         if( 3 < ary.length ) return;\r
312         \r
313         switch( ary.length ){\r
314                 case 0 :\r
315                         break;\r
316                 case 1 :\r
317                         s = parseInt( ary[ 0 ] ) || 0;\r
318                         break;\r
319                 case 2 :\r
320                         m = parseInt( ary[ 0 ] ) || 0;\r
321                         s = parseInt( ary[ 1 ] ) || 0;\r
322                         if( 60 <= s ) alert( 'invalid time string ' + time );\r
323                         break;\r
324                 case 3 :\r
325                         h = parseInt( ary[ 0 ] ) || 0;\r
326                         m = parseInt( ary[ 1 ] ) || 0;\r
327                         s = parseInt( ary[ 2 ] ) || 0;\r
328                         if( 60 <= s ) alert( 'invalid time string ' + time );\r
329                         if( 60 <= m ) alert( 'invalid time string ' + time );\r
330                         break;\r
331                 default :\r
332                         alert( 'invalid time string ' + time );\r
333         };\r
334         ms = ( h * 3600 + m * 60 + s ) * 1000 + ms;\r
335         return ms < 0 ? 0 : ms;\r
336 };\r
337 \r
338 function X_AudioWrapper_getStartTime( audioWrapper, endTime, delSeekTime ){\r
339         var seek = audioWrapper.seekTime;\r
340         if( delSeekTime ) delete audioWrapper.seekTime;\r
341         \r
342         if( 0 <= seek ){\r
343                 if( audioWrapper.duration <= seek || endTime < seek ) return 0;\r
344                 return seek;\r
345         };\r
346         \r
347         if( audioWrapper.looped && 0 <= audioWrapper.loopStartTime ){\r
348                 if( audioWrapper.duration <= audioWrapper.loopStartTime || endTime < audioWrapper.loopStartTime ) return 0;\r
349                 return audioWrapper.loopStartTime;\r
350         };\r
351         \r
352         if( audioWrapper.startTime < 0 || audioWrapper.duration <= audioWrapper.startTime ) return 0;\r
353         return audioWrapper.startTime;\r
354 };\r
355 \r
356 function X_AudioWrapper_getEndTime( audioWrapper ){\r
357         var duration = audioWrapper.duration;\r
358         \r
359         if( audioWrapper.looped && 0 <= audioWrapper.loopEndTime ){\r
360                 if( duration <= audioWrapper.loopEndTime ) return duration;\r
361                 return audioWrapper.loopEndTime;\r
362         };\r
363         \r
364         if( audioWrapper.endTime < 0 || duration <= audioWrapper.endTime ) return duration;\r
365         return audioWrapper.endTime;\r
366 };\r
367 \r