OSDN Git Service

7133272335ab7780e5f24b00ab31f2918566755b
[pettanr/clientJs.git] / 0.6.x / js / 07_audio / 03_XSilverlightAudio.js
1 \r
2 /*\r
3  * original : uupaa-js SilverlightAudio.js\r
4  * https://code.google.com/p/uupaa-js/source/browse/trunk/0.8/src/Audio/SilverlightAudio.js?r=568\r
5  *\r
6  * Silverlight 4 → 5における不具合の状況\r
7  * http://www.slideshare.net/wakabayashiy/silverlight-4-5 \r
8  * \r
9  * IE10以降でSilverlightでF5押したらフリーズする不具合と対処\r
10  * http://katsuyuzu.hatenablog.jp/entry/2014/01/11/003550\r
11  * \r
12  * SilverlLight5 ie6&7(ietester,winxp), ie8(winxp) で動作確認。firefox32 では動作しない。(4以下の方がよい?)\r
13  */\r
14 \r
15 var X_SLAudio,\r
16         X_SLAudio_uid = 0;\r
17 \r
18 if( X[ 'Pulgin' ][ 'Silverlight' ] ){\r
19         \r
20         X_TEMP.slaudioInit = function(){\r
21                 //\r
22                 // http://blog.yuhiisk.com/archive/2014/12/20/dynamic-loading-and-complete-processing-of-script.html\r
23                 var s;\r
24                 \r
25                 if( X_UA[ 'IE' ] < 9 ){\r
26                         s = document.createElement( '<script id="silverlightaudio" type="text/xaml"></script>' );\r
27                 } else {\r
28                         s = document.createElement( 'script' );\r
29                         s.id   = 'silverlightaudio';\r
30                         s.type = 'text/xaml';\r
31                 };\r
32                 \r
33                 document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );\r
34                 s.text = '<Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"></Canvas>';\r
35                 \r
36                 delete X_TEMP.slaudioInit;\r
37         };\r
38         \r
39         // X.Node.inherits はできない。_rawObject は <object> でなく silverlight\r
40         X_SLAudio = X_AudioBase[ 'inherits' ](\r
41                 'X.SilverlightAudio',\r
42                 X_Class.POOL_OBJECT,\r
43                 {\r
44                         '_rawType'      : X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT,\r
45 \r
46                 _onload         : '',\r
47                 _callback       : null,                 \r
48                 xnodeObject     : null,\r
49                         _source         : '',\r
50                         _ended          : true,\r
51                         _paused         : false,\r
52                         _lastUserAction : '',\r
53                         _lastState      : '',\r
54                         _interval       : 0, // setInterval timer id\r
55                         \r
56                         'Constructor' : function( target, source, option ){\r
57                                 !X_SLAudio_uid && X_TEMP.slaudioInit();\r
58                                 \r
59                                 /*\r
60                                  * [Silverlight 2]JavaScriptコードからSilverlightのオブジェクトを利用するには?[C#、VB]\r
61                                  * http://www.atmarkit.co.jp/fdotnet/dotnettips/902slobjcallfromjs/slobjcallfromjs.html\r
62                                  * このページのサンプルは sl5+firefox32 環境で動いている。xaml を js から利用する形ではなく、.xap を sl4 以下で作るのがよさそう.\r
63                                  */\r
64                                 this.target      = target || this;\r
65                                 this._source     = source;\r
66                                 // X.Audio._slOnload_ は不可\r
67                         this._onload     = 'XAudioSilverlightOnLoad' + ( ++X_SLAudio_uid );\r
68                                 this._callback   = window[ this._onload ] = X_Closure_create( this, this.onSLReady );\r
69                                 \r
70                                 // TODO embed\r
71                         this.xnodeObject = X_Node_body\r
72                                 [ 'create' ]( 'object', {\r
73                                                 type   : 'application/x-silverlight-2',\r
74                                                 data   : 'data:application/x-silverlight-2,',\r
75                                                 width  : 1,\r
76                                                 height : 1\r
77                                         })\r
78                                         [ 'html' ](\r
79                                             '<param name="background" value="#00000000">' +      // transparent\r
80                                             '<param name="windowless" value="true">' +\r
81                                             '<param name="source" value="#silverlightaudio">' +  // XAML ID\r
82                                             '<param name="onload" value="' + this._onload + '">' // + // bond to global\r
83                                             //'<param value="2.0.31005.0" name="minRuntimeVersion">' +\r
84                                             //'<param value="true" name="autoUpgrade">' +\r
85                                             //'<param name="onerror" value="slerror">' // bond to global\r
86                                         );\r
87                                 this.setState( option );\r
88         \r
89                                 this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE );\r
90                         },\r
91                         \r
92                                 onSLReady : function( sender ){\r
93                                         if( !this._onload ) return;\r
94                                         \r
95                                         window[ this._onload ] = null;\r
96                                         delete this._onload;\r
97                                         X_Closure_correct( this._callback );\r
98                                         delete this._callback;\r
99         \r
100                                         sender[ 'children' ][ 'add' ](\r
101                                                 sender[ 'GetHost' ]()[ 'content' ][ 'CreateFromXaml' ](\r
102                                                 '<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">' +\r
103                                                         '<MediaElement x:Name="media" Source="' + this._source + '" Volume="' + this.gain + '" AutoPlay="false" />' +\r
104                                                 '</Canvas>'));\r
105                         \r
106                                         this[ '_rawObject' ] = sender[ 'findName' ]( 'media' ); // x:Name='media'\r
107         \r
108                                         this[ 'listen' ]( [ 'MediaFailed', 'MediaOpened', 'MediaEnded', 'CurrentStateChanged' ] );\r
109                                 },\r
110                         \r
111                         handleEvent : function( e ){\r
112                                 var lastState, currentState;\r
113                                 \r
114                                 console.log( e.type );\r
115                                 switch( e.type ){\r
116                                         \r
117                                         case 'MediaFailed' :\r
118                                                 this.error   = 4;\r
119                                                 this.playing = false;\r
120                                                 this._ended  = true;\r
121                                                 this._paused = false;\r
122                                                 if( this.playing ){\r
123                                                         //X_Timer_once( 16, this, this.actualPlay );\r
124                                                 } else {\r
125                                                         this.target[ 'dispatch' ]( X_EVENT_ERROR ); // open failed\r
126                                                         this[ 'kill' ]();                                                       \r
127                                                 };\r
128                                                 break;\r
129 \r
130                                         case 'MediaOpened' :\r
131                                                 // http://msdn.microsoft.com/ja-jp/library/bb979710(VS.95).aspx\r
132                                                 this.duration = this[ '_rawObject' ][ 'NaturalDuration' ][ 'Seconds' ] * 1000;\r
133                                 this.target[ 'asyncDispatch' ]( X_EVENT_READY );\r
134                                 \r
135                                 this.autoplay && X_Timer_once( 16, this, this.actualPlay );\r
136                                                 break;\r
137 \r
138                                         case 'MediaEnded' :\r
139                                                 //console.log( ' > ' +  this.autoLoop + ' error:' + this.error );\r
140                                                 //this.autoLoop && /* this.playing && */ this.actualPlay();\r
141                                                 this._ended   = true;\r
142                                                 break;\r
143 \r
144                                         case 'CurrentStateChanged' :\r
145                                                 lastState        = this._lastState,\r
146                                                 currentState = this[ '_rawObject' ][ 'CurrentState' ];\r
147                                 \r
148                                                 // ignore consecutive events or 'Closed' == 'Error'\r
149                                                 if( lastState === currentState\r
150                                                         || ( (lastState === 'Closed' || lastState === 'Error') && ( currentState === 'Closed' || currentState === 'Error') ) ){\r
151                                                         return;\r
152                                                 };\r
153                                                 this._lastState = currentState; // update last state\r
154                                 \r
155                                                 console.log( ' > ' + currentState + ' - ' + this._lastUserAction );\r
156                                 \r
157                                                 switch( currentState ){\r
158                                                         case 'Buffering' :\r
159                                                         case 'Opening' :\r
160                                                                 switch( this._lastUserAction ){\r
161                                                                         case 'play' :\r
162                                                                                 this.target[ 'dispatch' ]( X_EVENT_MEDIA_WAITING );\r
163                                                                                 break;\r
164                                                                         case 'seek' :\r
165                                                                                 this.target[ 'dispatch' ]( X_EVENT_MEDIA_SEEKING );\r
166                                                                                 break;\r
167                                                                         case 'pause' :\r
168                                                                                 break;\r
169                                                                 };\r
170                                                                 break;\r
171 \r
172                                                         // media.play(none supported file) -> 'Error'\r
173                                                         // media.play(file not found)     -> 'Closed'\r
174                                                         // media.load -> 'Error'\r
175                                                         case 'Error':\r
176                                                                 this.error   = 4;\r
177                                                         case 'Closed':\r
178                                                                 this.error   = this.error || 2;\r
179                                                                 this.playing = false;\r
180                                                                 this._ended  = true;\r
181                                                                 this._paused = false;\r
182                                                                 this.target[ 'dispatch' ]( X_EVENT_ERROR );\r
183                                                                 this[ 'kill' ]();\r
184                                                                 break;\r
185 \r
186                                                         // userAction.pause()              -> MediaState('Paused') -> x\r
187                                                         // userAction.stop()                    -> MediaState('Paused') -> x\r
188                                                         // userAction.play() + file end -> MediaState('Paused') -> uueventfire('ended')\r
189                                                         case 'Paused':\r
190                                                         \r
191                                                                 this.playing && X_Timer_once( 16, this, this.actualPlay );\r
192                                                                 //this.playing = false;\r
193                                                                 \r
194                                                                 switch( this._lastUserAction ){\r
195                                                                         case 'play': // play() -> file end -> event('ended')\r
196                                                                         case 'seek':\r
197                                                                                 //this.seekTime = 0;\r
198                                                                                 this._ended   = true;\r
199                                                                                 this._paused  = false;\r
200                                                                                 //this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );\r
201                                                                                 //this.setCurrentTime( this.startTime );\r
202                                                                                 break;\r
203                                                                         case 'pause':\r
204                                                                                 this._ended  = false;\r
205                                                                                 this._paused = true;\r
206                                                                                 break;\r
207                                                                         case 'stop':\r
208                                                                                 this._ended  = true;\r
209                                                                                 this._paused = false;\r
210                                                                 };\r
211                                                                 break;\r
212 \r
213                                                         // media.play -> 'Playing'\r
214                                                         case 'Playing':\r
215                                                                 this.error   = 0;\r
216                                                                 //this.playing = true;\r
217                                                                 this._ended  = false;\r
218                                                                 this._paused = false;\r
219                                                                 this.target[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );\r
220                                                                 break;\r
221 \r
222                                                         // stop()\r
223                                                         case 'Stopped':\r
224                                                                 this.playing && X_Timer_once( 16, this, this.actualPlay );\r
225                                                                 return;\r
226                                                                 \r
227                                                                 //this.playing = false;\r
228                                                                 this._ended  = true;\r
229                                                                 this._paused = false;\r
230                                                                 //this.setCurrentTime( this.startTime );\r
231                                                                 break;\r
232                                                 };\r
233                                                 break;\r
234 \r
235                                         case X_EVENT_KILL_INSTANCE :\r
236                                                 this.playing && this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );\r
237                                                 this.playing && this.actualPause();\r
238                                         \r
239                                                 if( this._onload ){\r
240                                                         // window への delete に ie5 は対応しないが、そもそも ie5 は Silverlight に非対応\r
241                                                         window[ this._onload ] = null;\r
242                                                         delete this._onload;\r
243                                                         X_Closure_correct( this._callback );\r
244                                                 };\r
245                                                 this.xnodeObject[ 'kill' ]();\r
246                                                 break;\r
247                                 };\r
248                         },\r
249                         \r
250                         // SilverlightAudio.play\r
251                         actualPlay : function(){\r
252                                 var begin, offset, end;\r
253 \r
254                                 // もし kill 後に autoplayTimer で呼ばれても、_closed==true なので平気\r
255                                 if( this.error ) return;\r
256                                 if( !this.duration ){\r
257                                         this.autoplay = true;\r
258                                         return;\r
259                                 };\r
260                                 \r
261                                 this._lastUserAction = 0 <= this.seekTime ? 'seek' : 'play';\r
262                                 \r
263                                 end   = X_Audio_getEndTime( this );\r
264                                 begin = X_Audio_getStartTime( this, end, true ) | 0;\r
265 \r
266                                 // 1 秒以下は指定できないため四捨五入\r
267                                 begin = ( begin / 1000 | 0 ) * 1000 + ( 500 < begin % 1000 ? 1000 : 0 ); \r
268 \r
269                             this[ '_rawObject' ][ 'Volume' ] = this.gain;\r
270                             \r
271                             this.setCurrentTime( this._beginTime = begin );\r
272                             \r
273                             console.log( '[play] ' + begin + ' -> ' + end );\r
274                             \r
275                             /*\r
276                             if( offset = begin - this.getActualCurrentTime() ){\r
277                                 this.setCurrentTime( begin + offset );\r
278                                 console.log( ' [差補正] ' + offset + ' ct:' + this.getActualCurrentTime() + ' begin:' + begin  );\r
279                                 this._beginTime = begin = this.getActualCurrentTime();\r
280                             };*/\r
281                             \r
282                             if( !this.playing || this._ended ){\r
283                                 console.log( '[play] play()' + begin + ' -> ' + end );\r
284                                     this[ '_rawObject' ].play();\r
285                             this.playing = true;\r
286                             this._ended  = false;\r
287                             };\r
288                     \r
289                     this._timerID && X_Timer_remove( this._timerID );\r
290                     \r
291                 this._timerID = X_Timer_once( end - begin, this, this._onEnded );\r
292                 \r
293                                 if( !this._interval ){\r
294                                         this._interval = X_Timer_add( 1000, 0, this, this._onInterval );\r
295                                 };\r
296                         },\r
297                                 \r
298                                 _onInterval : function(){\r
299                                         if( !this.playing ){\r
300                                                 delete this._interval;\r
301                                                 return X_CALLBACK_UN_LISTEN;\r
302                                         };\r
303                                         this.target[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING );\r
304                                 },\r
305                                 \r
306                                 _onEnded : function(){\r
307                                         var time, end;\r
308                                         delete this._timerID;\r
309                                         \r
310                             if( this.playing ){\r
311                                 //console.log( '> end ' + X_Audio_getEndTime( this ) + ' current:' + ( this.getActualCurrentTime() ) );\r
312                                 time = this.getActualCurrentTime();\r
313                                 \r
314                                 if( time < this._beginTime ){\r
315                                         console.log( '== waiting ' + time + ' < begin:' + this._beginTime );\r
316                                         this.setCurrentTime( this._beginTime );\r
317                                         time = this.getActualCurrentTime();\r
318                                         console.log( '    > ' + time );\r
319                                         this._ended && this[ '_rawObject' ].play();\r
320                                         this._ended = false;\r
321                                         this.target[ 'dispatch' ]( X_EVENT_MEDIA_WAITING );\r
322                                         this._timerID = X_Timer_once( X_Audio_getEndTime( this ) - time, this, this._onEnded );\r
323                                         return;\r
324                                 };\r
325                                 \r
326                                 time -= X_Audio_getEndTime( this );\r
327                                 if( time < 0 ){\r
328                                         console.log( ' > まだ終わらない ' + time );\r
329                                         this._ended && this[ '_rawObject' ].play();\r
330                                         this._ended = false;\r
331                                         this._timerID = X_Timer_once( -time, this, this._onEnded );\r
332                                         return;\r
333                                 };\r
334                                 \r
335                                 if( this.autoLoop ){\r
336                                         console.log( '========= loop?' );\r
337                                         if( !( this.target[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_CALLBACK_PREVENT_DEFAULT ) ){\r
338                                                 console.log( '========== loopした' );\r
339                                                 this.looped = true;\r
340                                                 this.target[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED );\r
341                                                 this.actualPlay();\r
342                                         };\r
343                                 } else {\r
344                                         console.log( '========= pause' );\r
345                                         this.actualPause();\r
346                                         this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED );\r
347                                 };\r
348                             };\r
349                                 },\r
350                         \r
351                         // SilverlightAudio.pause\r
352                         actualPause : function(){\r
353                                 if( this.error /*  || !this.playing */ ) return;\r
354                                 \r
355                                 this._lastUserAction = 'pause';\r
356                                 this.seekTime = this.getActualCurrentTime();\r
357                                 this.playing  = false;\r
358                                 this._paused  = true;\r
359                                 this._ended   = false;\r
360                                 \r
361                                 this[ '_rawObject' ].pause();\r
362                                 //this.target[ 'dispatch' ]( 'pause' );\r
363                         },\r
364 \r
365                         getActualCurrentTime : function(){\r
366                                 return this[ '_rawObject' ][ 'Position' ][ 'Seconds' ] * 1000 | 0;\r
367                         },\r
368                         \r
369                         afterUpdateState : function( result ){\r
370                                 if( result & 3 ){ // seek\r
371                         this.actualPlay();\r
372                                 } else\r
373                                 if( result & 1 ){\r
374                                         end     = X_Audio_getEndTime( this );\r
375                                         halfway = end < this.duration;\r
376                                         this._timerID && X_Timer_remove( this._timerID );\r
377                                         \r
378                                         if( halfway ){\r
379                                                 this._timerID = X_Timer_once( end - this.getActualCurrentTime(), this, this._onEnded );\r
380                                         } else {\r
381                                                 delete this._timerID;\r
382                                         };\r
383                                 } else\r
384                                 if( result & 4 ){\r
385                        this[ '_rawObject' ][ 'Volume' ] = this.gain;\r
386                                 };                      \r
387                         },\r
388                         \r
389                                 // SilverlightAudio.currentTime\r
390                                 setCurrentTime : function( time ){ // @param Number: time\r
391                                         var position = this[ '_rawObject' ][ 'Position' ]; // [!] create instance\r
392         \r
393                                         position[ 'Seconds' ] = time / 1000 | 0; // set current time\r
394                                 \r
395                                         this[ '_rawObject' ][ 'Position' ] = position; // [!] reattach instance\r
396                                 }\r
397 \r
398                 }\r
399         );\r
400 \r
401         /*\r
402         function slerror(){\r
403                 alert( 'slerror' );\r
404         }; */\r
405 \r
406         X_Audio_BACKENDS.push( {\r
407                 backendID   : 8,\r
408                 \r
409                 backendName : 'Silverlight',\r
410 \r
411                 canPlay : {\r
412                         'mp3' : true,\r
413                         'wma' : true,\r
414                         'wav' : true\r
415                 },\r
416 \r
417                 detect : function( proxy, source, ext ){\r
418                         proxy[ 'asyncDispatch' ]( { type : X_EVENT_COMPLETE, canPlay : ext === 'mp3' || ext === 'wma' || ext === 'wav' } );                             \r
419                 },\r
420                 \r
421                 klass : X_SLAudio\r
422                 \r
423         } );\r
424 \r
425 };